PVS-Studio e Integración Continua: TeamCity. Análisis del proyecto Open RollerCoaster Tycoon 2

PVS-Studio e Integración Continua: TeamCity. Análisis del proyecto Open RollerCoaster Tycoon 2
Uno de los escenarios más actuales para utilizar el analizador PVS-Studio es su integración con sistemas CI. Y aunque el análisis de un proyecto PVS-Studio desde casi cualquier sistema de integración continua se puede integrar en unos pocos comandos, seguimos haciendo que este proceso sea aún más conveniente. PVS-Studio ahora admite la conversión de la salida del analizador a un formato para TeamCity: tipo de inspecciones TeamCity. Vamos a ver cómo funciona.

Información sobre el software utilizado.

PVS-Estudio — un analizador estático de código C, C++, C# y Java, diseñado para facilitar la tarea de encontrar y corregir varios tipos de errores. El analizador se puede utilizar en Windows, Linux y macOS. En este artículo utilizaremos activamente no sólo el analizador en sí, sino también algunas utilidades de su distribución.

CLMonitor - es un servidor de monitoreo que monitorea los lanzamientos del compilador. Debe ejecutarse inmediatamente antes de comenzar a construir su proyecto. En modo de vigilancia, el servidor interceptará las ejecuciones de todos los compiladores compatibles. Vale la pena señalar que esta utilidad sólo se puede utilizar para analizar proyectos C/C++.

PlogConvertidor – una utilidad para convertir informes del analizador a diferentes formatos.

Información sobre el proyecto en estudio.

Probemos esta funcionalidad en un ejemplo práctico: analicemos el proyecto OpenRCT2.

AbiertoRCT2 - una implementación abierta del juego RollerCoaster Tycoon 2 (RCT2), ampliándolo con nuevas funciones y corrigiendo errores. El juego gira en torno a la construcción y el mantenimiento de un parque de diversiones que contiene atracciones, tiendas e instalaciones. El jugador debe intentar obtener ganancias y mantener la buena reputación del parque mientras mantiene contentos a los visitantes. OpenRCT2 te permite jugar tanto en escenario como en sandbox. Los escenarios requieren que el jugador complete una tarea específica dentro de un tiempo determinado, mientras que Sandbox le permite al jugador construir un parque más flexible sin restricciones ni finanzas.

Ajuste

Para ahorrar tiempo, probablemente me saltearé el proceso de instalación y comenzaré desde el momento en que tenga el servidor TeamCity ejecutándose en mi computadora. Necesitamos ir a: localhost:{puerto especificado durante el proceso de instalación} (en mi caso, localhost:9090) e ingresar los datos de autorización. Luego de ingresar seremos recibidos por:

PVS-Studio e Integración Continua: TeamCity. Análisis del proyecto Open RollerCoaster Tycoon 2
Haga clic en el botón Crear proyecto. A continuación, seleccione Manualmente y complete los campos.

PVS-Studio e Integración Continua: TeamCity. Análisis del proyecto Open RollerCoaster Tycoon 2
Después de presionar el botón Crear, somos recibidos por una ventana con la configuración.

PVS-Studio e Integración Continua: TeamCity. Análisis del proyecto Open RollerCoaster Tycoon 2
hagamos clic Crear configuración de compilación.

PVS-Studio e Integración Continua: TeamCity. Análisis del proyecto Open RollerCoaster Tycoon 2
Complete los campos y haga clic Crear. Vemos una ventana que le pide que seleccione un sistema de control de versiones. Dado que las fuentes ya están ubicadas localmente, haga clic en omitir.

PVS-Studio e Integración Continua: TeamCity. Análisis del proyecto Open RollerCoaster Tycoon 2
Finalmente, pasamos a la configuración del proyecto.

PVS-Studio e Integración Continua: TeamCity. Análisis del proyecto Open RollerCoaster Tycoon 2
Agreguemos pasos de ensamblaje, para hacer esto haga clic: Pasos de compilación -> Agregar paso de compilación.

PVS-Studio e Integración Continua: TeamCity. Análisis del proyecto Open RollerCoaster Tycoon 2
Aquí elegimos:

  • Tipo de corredor -> Línea de comando
  • Ejecutar -> Script personalizado

Dado que realizaremos análisis durante la compilación del proyecto, el ensamblaje y el análisis deben ser un solo paso, así que complete el campo Script personalizado:

PVS-Studio e Integración Continua: TeamCity. Análisis del proyecto Open RollerCoaster Tycoon 2
Veremos los pasos individuales más adelante. Es importante que cargar el analizador, ensamblar el proyecto, analizarlo, generar el informe y formatearlo requiera solo once líneas de código.

Lo último que debemos hacer es configurar las variables de entorno, de las cuales he descrito algunas formas de mejorar su legibilidad. Para ello, sigamos adelante: Parámetros -> Agregar nuevo parámetro y agregue tres variables:

PVS-Studio e Integración Continua: TeamCity. Análisis del proyecto Open RollerCoaster Tycoon 2
Todo lo que tienes que hacer es presionar el botón Ejecutar en la esquina superior derecha. Mientras se monta y analiza el proyecto, os hablaré del guión.

Guión directo

Primero, necesitamos descargar la última distribución de PVS-Studio. Para ello utilizamos el administrador de paquetes Chocolatey. Para aquellos que quieran saber más sobre esto, hay un correspondiente artículo:

choco install pvs-studio -y

A continuación, iniciemos la utilidad de seguimiento de compilación de proyectos CLMonitor.

%CLmon% monitor –-attach

Luego construiremos el proyecto como una variable de entorno. MSB es la ruta a la versión de MSBuild que necesito construir

%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortable

Ingresemos la clave de inicio de sesión y licencia para PVS-Studio:

%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%

Una vez completada la compilación, ejecute CLMonitor nuevamente para generar archivos preprocesados ​​y análisis estático:

%CLmon% analyze -l "c:ptest.plog"

Luego usaremos otra utilidad de nuestra distribución. PlogConverter convierte un informe de un formato estándar a un formato específico de TeamCity. Gracias a esto, podremos verlo directamente en la ventana de compilación.

%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"

El último paso es mostrar el informe formateado en stdout, donde será recogido por el analizador de TeamCity.

type "C:tempptest.plog_TeamCity.txt"

Código de secuencia de comandos completo:

choco install pvs-studio -y
%CLmon% monitor --attach
set platform=x64
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortable
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%
%CLmon% analyze -l "c:ptest.plog"
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"
type "C:tempptest.plog_TeamCity.txt"

Mientras tanto, el montaje y análisis del proyecto se ha completado con éxito, podemos ir a la pestaña Proyectos y asegúrate de ello.

PVS-Studio e Integración Continua: TeamCity. Análisis del proyecto Open RollerCoaster Tycoon 2
Ahora hagamos clic en Inspecciones Totalespara ir a ver el informe del analizador:

PVS-Studio e Integración Continua: TeamCity. Análisis del proyecto Open RollerCoaster Tycoon 2
Las advertencias se agrupan por números de reglas de diagnóstico. Para navegar por el código, debe hacer clic en el número de línea con la advertencia. Al hacer clic en el signo de interrogación en la esquina superior derecha, se abrirá una nueva pestaña con documentación. También puede navegar por el código haciendo clic en el número de línea con la advertencia del analizador. La navegación desde una computadora remota es posible cuando se usa FuenteÁrbolRaíz marcador. Cualquiera que esté interesado en este modo de funcionamiento del analizador puede familiarizarse con el apartado correspondiente. documentación.

Ver los resultados del analizador

Ahora que hemos terminado de implementar y configurar la compilación, echemos un vistazo a algunas advertencias interesantes que se encuentran en el proyecto que estamos analizando.

Advertencia N1

V773 [CWE-401] La excepción se lanzó sin liberar el puntero de 'resultado'. Es posible que se produzca una pérdida de memoria. libopenrct2 ObjectFactory.cpp 443

Object* CreateObjectFromJson(....)
{
  Object* result = nullptr;
  ....
  result = CreateObject(entry);
  ....
  if (readContext.WasError())
  {
    throw std::runtime_error("Object has errors");
  }
  ....
}

Object* CreateObject(const rct_object_entry& entry)
{
  Object* result;
  switch (entry.GetType())
  {
    case OBJECT_TYPE_RIDE:
      result = new RideObject(entry);
      break;
    case OBJECT_TYPE_SMALL_SCENERY:
      result = new SmallSceneryObject(entry);
      break;
    case OBJECT_TYPE_LARGE_SCENERY:
      result = new LargeSceneryObject(entry);
      break;
    ....
    default:
      throw std::runtime_error("Invalid object type");
  }
  return result;
}

El analizador notó un error que después de asignar memoria dinámicamente en CreateObject, cuando ocurre una excepción, la memoria no se borra y se produce una pérdida de memoria.

Advertencia N2

V501 Hay subexpresiones idénticas '(1ULL << WIDX_MONTH_BOX)' a la izquierda y a la derecha de '|' operador. libopenrct2ui Trucos.cpp 487

static uint64_t window_cheats_page_enabled_widgets[] = 
{
  MAIN_CHEAT_ENABLED_WIDGETS |
  (1ULL << WIDX_NO_MONEY) |
  (1ULL << WIDX_ADD_SET_MONEY_GROUP) |
  (1ULL << WIDX_MONEY_SPINNER) |
  (1ULL << WIDX_MONEY_SPINNER_INCREMENT) |
  (1ULL << WIDX_MONEY_SPINNER_DECREMENT) |
  (1ULL << WIDX_ADD_MONEY) |
  (1ULL << WIDX_SET_MONEY) |
  (1ULL << WIDX_CLEAR_LOAN) |
  (1ULL << WIDX_DATE_SET) |
  (1ULL << WIDX_MONTH_BOX) |  // <=
  (1ULL << WIDX_MONTH_UP) |
  (1ULL << WIDX_MONTH_DOWN) |
  (1ULL << WIDX_YEAR_BOX) |
  (1ULL << WIDX_YEAR_UP) |
  (1ULL << WIDX_YEAR_DOWN) |
  (1ULL << WIDX_DAY_BOX) |
  (1ULL << WIDX_DAY_UP) |
  (1ULL << WIDX_DAY_DOWN) |
  (1ULL << WIDX_MONTH_BOX) |  // <=
  (1ULL << WIDX_DATE_GROUP) |
  (1ULL << WIDX_DATE_RESET),
  ....
};

Pocas personas, aparte de un analizador estático, podrían pasar esta prueba de atención. Este ejemplo de copiar y pegar es bueno precisamente por esta razón.

Advertencias N3

V703 Es extraño que el campo 'flags' en la clase derivada 'RCT12BannerElement' sobrescriba el campo en la clase base 'RCT12TileElementBase'. Líneas de verificación: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570

struct RCT12SpriteBase
{
  ....
  uint8_t flags;
  ....
};
struct rct1_peep : RCT12SpriteBase
{
  ....
  uint8_t flags;
  ....
};

Por supuesto, utilizar una variable con el mismo nombre en la clase base y en la descendiente no siempre es un error. Sin embargo, la propia tecnología de herencia supone que todos los campos de la clase principal están presentes en la clase secundaria. Al declarar campos con el mismo nombre en el heredero, creamos confusión.

Advertencia N4

V793 Es extraño que el resultado de la declaración 'imageDirection / 8' sea parte de la condición. Quizás esta afirmación debería haberse comparado con otra cosa. libopenrct2 ObservationTower.cpp 38

void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
  if ((imageDirection / 8) && (imageDirection / 8) != 3)
  {
    ....
  }
  ....
}

Miremos más de cerca. Expresión direcciónimagen/8 será falso si ImagenDirección está en el rango de -7 a 7. Segunda parte: (direcciónimagen / 8) != 3 cheques ImagenDirección por estar fuera del rango: de -31 a -24 y de 24 a 31, respectivamente. Me parece bastante extraño verificar la inclusión de números en un rango determinado de esta manera e, incluso si no hay ningún error en este código, recomendaría reescribir estas condiciones para que sean más explícitas. Esto haría la vida mucho más fácil a las personas que leerían y mantendrían este código.

Advertencia N5

V587 Una secuencia extraña de asignaciones de este tipo: A = B; B = A;. Líneas de verificación: 1115, 1118. libopenrct2ui MouseInput.cpp 1118

void process_mouse_over(....)
{
  ....
  switch (window->widgets[widgetId].type)
  {
    case WWT_VIEWPORT:
      ebx = 0;
      edi = cursorId;                                 // <=
      // Window event WE_UNKNOWN_0E was called here,
      // but no windows actually implemented a handler and
      // it's not known what it was for
      cursorId = edi;                                 // <=
      if ((ebx & 0xFF) != 0)
      {
        set_cursor(cursorId);
        return;
      }
      break;
      ....
  }
  ....
}

Este fragmento de código probablemente se obtuvo mediante descompilación. Luego, a juzgar por el comentario dejado, se eliminó parte del código que no funcionaba. Sin embargo, aún quedan un par de operaciones cursorId, que tampoco tiene mucho sentido.

Advertencia N6

V1004 [CWE-476] El puntero 'reproductor' se usó de manera insegura después de verificarlo con nullptr. Líneas de verificación: 2085, 2094. libopenrct2 Network.cpp 2094

void Network::ProcessPlayerList()
{
  ....
  auto* player = GetPlayerByID(pendingPlayer.Id);
  if (player == nullptr)
  {
    // Add new player.
    player = AddPlayer("", "");
    if (player)                                          // <=
    {
      *player = pendingPlayer;
       if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
       {
         _serverConnection->Player = player;
       }
    }
    newPlayers.push_back(player->Id);                    // <=
  }
  ....
}

Este código es bastante fácil de corregir; sólo necesitas verificarlo por tercera vez. jugador a un puntero nulo o agréguelo al cuerpo de la declaración condicional. Yo sugeriría la segunda opción:

void Network::ProcessPlayerList()
{
  ....
  auto* player = GetPlayerByID(pendingPlayer.Id);
  if (player == nullptr)
  {
    // Add new player.
    player = AddPlayer("", "");
    if (player)
    {
      *player = pendingPlayer;
      if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
      {
        _serverConnection->Player = player;
      }
      newPlayers.push_back(player->Id);
    }
  }
  ....
}

Advertencia N7

V547 [CWE-570] La expresión 'nombre == nullptr' siempre es falsa. libopenrct2 ServerList.cpp 102

std::optional<ServerListEntry> ServerListEntry::FromJson(...)
{
  auto name = json_object_get(server, "name");
  .....
  if (name == nullptr || version == nullptr)
  {
    ....
  }
  else
  {
    ....
    entry.name = (name == nullptr ? "" : json_string_value(name));
    ....
  }
  ....
}

Puede deshacerse de una línea de código difícil de leer de una sola vez y resolver el problema al verificar punto nulo. Sugiero cambiar el código de la siguiente manera:

std::optional<ServerListEntry> ServerListEntry::FromJson(...)
{
  auto name = json_object_get(server, "name");
  .....
  if (name == nullptr || version == nullptr)
  {
    name = ""
    ....
  }
  else
  {
    ....
    entry.name = json_string_value(name);
    ....
  }
  ....
}

Advertencia N8

V1048 [CWE-1164] A la variable 'ColumnHeaderPressedCurrentState' se le asignó el mismo valor. libopenrct2ui CustomListView.cpp 510

void CustomListView::MouseUp(....)
{
  ....
  if (!ColumnHeaderPressedCurrentState)
  {
    ColumnHeaderPressed = std::nullopt;
    ColumnHeaderPressedCurrentState = false;
    Invalidate();
  }
}

El código parece bastante extraño. Me parece que hubo un error tipográfico ya sea en la condición o al reasignar la variable EncabezadodecolumnaPresionadoEstadoActual significado false.

conclusión

Como podemos ver, integrar el analizador estático PVS-Studio en tu proyecto TeamCity es bastante sencillo. Para hacer esto, basta con escribir solo un pequeño archivo de configuración. Verificar el código le permitirá identificar problemas inmediatamente después del ensamblaje, lo que ayudará a eliminarlos cuando la complejidad y el costo de los cambios aún sean bajos.

PVS-Studio e Integración Continua: TeamCity. Análisis del proyecto Open RollerCoaster Tycoon 2
Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace de traducción: Vladislav Stolyarov. PVS-Studio e Integración Continua: TeamCity. Análisis del proyecto Open RollerCoaster Tycoon 2.

Fuente: habr.com

Añadir un comentario