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



Uno de los escenarios más relevantes para el uso del analizador PVS-Studio es su integración con sistemas CI. Y aunque el análisis de un proyecto PVS-Studio de casi cualquier sistema de integración continua puede integrarse 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 al formato de TeamCity - TeamCity Inspections Type. Vamos a ver cómo funciona.



Información sobre el software utilizado



PVS-Studio es un analizador estático de código , ++, 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 solo el analizador en sí, sino también algunas utilidades de su kit de distribución.



CLMonitor es un servidor de supervisión que supervisa los lanzamientos de compiladores. Debe ejecutarlo justo antes de comenzar a construir su proyecto. En el modo de vigilancia, el servidor interceptará las ejecuciones de todos los compiladores compatibles. Cabe señalar que esta utilidad solo se puede utilizar para analizar proyectos C / C ++.



PlogConverter es una utilidad para convertir el informe del analizador en diferentes formatos.



Información sobre el proyecto investigado



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



OpenRCT2 es una implementación de código abierto de RollerCoaster Tycoon 2 (RCT2), que la amplía con nuevas funciones y correcciones de errores. El juego gira en torno a la construcción y mantenimiento de un parque de atracciones, que alberga atracciones, tiendas e instalaciones. El jugador debe intentar obtener ganancias y mantener una buena reputación para el parque mientras mantiene contentos a los visitantes. OpenRCT2 te permite jugar tanto en escenarios como en sandbox. Los escenarios requieren que el jugador complete una tarea específica en un tiempo establecido, mientras que la caja de arena le permite al jugador construir un parque más flexible sin restricciones ni finanzas.



Configurar



Para ahorrar tiempo, probablemente omitiré el proceso de instalación y comenzaré desde el momento en que el servidor TeamCity se esté ejecutando en mi computadora. Necesitamos ir a: localhost: {el puerto especificado durante la instalación} (en mi caso, localhost: 9090) e ingresar los datos de autorización. Después de entrar nos encontraremos con:



image3.png


Haga clic en el botón Crear proyecto. A continuación, seleccione Manualmente, complete los campos.



image5.png


Después de hacer clic en el botón Crear , somos recibidos por una ventana con configuraciones.



image7.png


Haga clic en Crear configuración de compilación .



image9.png


Complete los campos, haga clic en Crear . Vemos una ventana que ofrece elegir un sistema de control de versiones. Dado que las fuentes ya se encuentran localmente, haga clic en Omitir .



image11.png


Finalmente, pasamos a la configuración del proyecto.



image13.png


Agregue pasos de compilación haciendo clic en: Pasos de compilación -> Agregar paso de compilación .



image15.png


Aquí seleccionamos:



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


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



image17.png


Más adelante nos detendremos en los pasos individuales. Es importante que cargar el analizador, construir el proyecto, analizarlo, generar el informe y formatearlo solo requieran once líneas de código.



Lo último que debemos hacer es configurar las variables de entorno, que he indicado de alguna manera para mejorar su legibilidad. Para hacer esto, vaya a: Parámetros -> Agregar nuevo parámetro y agregue tres variables:



image19.png


Queda por hacer clic en el botón Ejecutar en la esquina superior derecha. Mientras se ensambla y analiza el proyecto, les contaré sobre el guión.



Directamente guión



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



choco install pvs-studio -y


A continuación, iniciemos la utilidad de seguimiento del ensamblaje del proyecto CLMonitor.



%CLmon% monitor –-attach


Luego, crearemos el proyecto, la ruta a la versión de MSBuild que necesito compilar se usa como la variable de entorno MSB



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


Ingresemos el inicio de sesión y la clave de licencia de 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 una utilidad más de nuestro kit de distribución. PlogConverter convierte el informe de formato estándar a específico de TeamCity. Gracias a esto, podremos verlo directamente en la ventana de montaje.



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


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



type "C:\temp\ptest.plog_TeamCity.txt"


Código de script 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:\temp\ptest.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 verificar esto.



image21.png


Ahora haga clic en Inspecciones totales para ir a ver el informe del analizador:



image23.png


Las advertencias están agrupadas 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 de documentación. También puede navegar por el código haciendo clic en el número de línea de advertencia del analizador. La navegación desde una computadora remota es posible usando el marcador SourceTreeRoot . Quienes estén interesados ​​en este modo de funcionamiento del analizador pueden familiarizarse con el apartado correspondiente de la documentación .



Visualización de los resultados del analizador



Una vez que hayamos terminado de implementar y configurar la compilación, sugiero ver algunas advertencias interesantes que se encuentran en el proyecto en estudio.



Advertencia N1



V773 [CWE-401] La excepción se lanzó sin liberar el puntero 'resultado'. Es posible 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 la asignación de memoria dinámica en CreateObject , cuando se lanza una excepción, la memoria no se borra; en consecuencia, ocurre 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 Cheats.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),
  ....
};


Pocos, además de un analizador estático, podrían pasar esta prueba de atención. Este ejemplo de copiar y pegar es bueno para eso.



Alertas N3



V703 Es extraño que el campo 'banderas' en la clase derivada 'RCT12BannerElement' sobrescriba el campo en la clase base 'RCT12TileElementBase'. Verifique las líneas: RCT12.h: 570, RCT12.h: 259. libopenrct2 RCT12.h 570



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


Por supuesto, usar una variable con el mismo nombre en la clase base y en el heredero no siempre es un error. Sin embargo, la propia tecnología de herencia asume la presencia de todos los campos de la clase padre en el hijo. Al declarar un campo con el mismo nombre en el heredero, introducimos 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. La expresión imageDirection / 8 será falsa si imageDirection está en el rango de -7 a 7. La segunda parte: (imageDirection / 8)! = 3 comprueba que imageDirection esté fuera de rango: -31 a -24 y 24 a 31 respectivamente. Me parece bastante extraño verificar los números para ingresar un cierto rango de esta manera, e incluso si no hay ningún error en este fragmento de código, recomendaría reescribir estas condiciones para que sean más explícitos. Esto facilitaría mucho la vida a las personas que lean y mantengan este código.



Advertencia N5



V587Una 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, quedan un par de operaciones en cursorId , que tampoco tienen mucho sentido.



N6 Advertencia



V1004 [CWE-476] El puntero 'jugador' se usó de manera insegura después de que se verificó contra nullptr. Verifique las líneas: 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);                    // <=
  }
  ....
}


Es bastante simple corregir este código, debe verificar el reproductor por un puntero nulo por tercera vez o agregarlo al cuerpo del operador condicional. 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);
    }
  }
  ....
}


N7 Advertencia



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 la línea de código difícil de leer de una sola vez y resolver el problema con la verificación de nullptr . 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 se ve bastante extraño. Creo que hubo un error tipográfico o estar sujeto, o al reasignar el valor de la variable ColumnHeaderPressedCurrentState a falso .



Salida



Como podemos ver, integrar el analizador estático PVS-Studio en su proyecto de TeamCity es bastante simple. Es suficiente escribir solo un pequeño archivo de configuración para esto. La revisión del código le permitirá identificar problemas inmediatamente después de la compilación, lo que ayudará a eliminarlos cuando la complejidad y el costo de las ediciones aún sean pequeños.





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 .



All Articles