
Uno de los escenarios más relevantes para el uso del analizador PVS-Studio es su integración con sistemas CI. Aunque el análisis de proyectos en PVS-Studio se puede integrar en prácticamente cualquier sistema de integración continua con solo unos pocos comandos, seguimos mejorando este proceso para que sea aún más conveniente. PVS-Studio ahora admite la conversión de la salida del analizador al formato de TeamCity: tipo de Inspecciones de TeamCity. Veamos cómo funciona.
Información sobre el software utilizado
— un analizador estático de código C, C++, C# y Java, diseñado para facilitar la tarea de encontrar y corregir diversos tipos de errores. El analizador se puede usar en Windows, Linux y macOS. En este artículo, usaremos activamente no solo el analizador en sí, sino también algunas utilidades de su distribución.
— es un servidor de monitoreo que rastrea el inicio de compiladores. Debe iniciarse justo antes de comenzar la compilación de su proyecto. En modo de monitoreo, el servidor interceptará el inicio de todos los compiladores compatibles. Cabe mencionar que esta utilidad solo se puede usar para analizar proyectos C/C++.
– una utilidad para convertir el informe del analizador a diferentes formatos.
Información sobre el proyecto investigado
Intentemos utilizar esta funcionalidad con un ejemplo práctico: analizaremos el proyecto OpenRCT2.
— es una implementación abierta del juego RollerCoaster Tycoon 2 (RCT2), que le añade nuevas funciones y corrige errores. El juego gira en torno a la construcción y mantenimiento de un parque de atracciones, que incluye atracciones, tiendas y otros establecimientos. El jugador debe tratar de obtener beneficios y mantener una buena reputación del parque, a la vez que mantiene felices a los visitantes. OpenRCT2 permite jugar tanto en escenarios como en modo sandbox. Los escenarios requieren que el jugador complete una tarea específica dentro de un tiempo determinado, mientras que el modo sandbox permite al jugador construir un parque más flexible sin restricciones ni preocupaciones financieras.
Configuración
Para ahorrar tiempo, omitiré el proceso de instalación y comenzaré en el momento en que tengo el servidor TeamCity funcionando en mi computadora. Debemos ir a: localhost:{puerto especificado durante el proceso de instalación}(en mi caso, localhost:9090) e ingresar los datos de autenticación. Una vez que iniciemos sesión, nos encontraremos con:

Hacemos clic en el botón Create Project. Luego seleccionamos Manually y completamos los campos.

Después de hacer clic en el botón Crear, nos recibe una ventana con la configuración.

Hacemos clic en Create build configuration.

Llenamos los campos y hacemos clic en Crear. Vemos una ventana que ofrece elegir el sistema de control de versiones. Como los archivos fuente ya están localmente, hacemos clic en Skip.

Finalmente, pasamos a la configuración del proyecto.

Agregaremos pasos de construcción, para ello hacemos clic en: Build steps -> Add build step.

Aquí seleccionamos:
- Runner type -> Command Line
- Run -> Custom Script
Como realizaremos el análisis durante la compilación del proyecto, la construcción y el análisis deben ser un solo paso, así que llenamos el campo Custom Script:

Detendremos en pasos separados más adelante. Importante es que la carga del analizador, la construcción del proyecto, su análisis, la salida del informe y su formateo ocupen solo once líneas de código.
Lo último que necesitamos hacer es establecer las variables de entorno, donde he marcado algunas rutas para mejorar su legibilidad. Para ello vamos a: Parameters -> Add new parameter y agregaremos tres variables:

Solo queda hacer clic en el botón Ejecutar en la esquina superior derecha. Mientras se lleva a cabo la construcción y el análisis del proyecto, les hablaré sobre el script.
El script en sí
Primero necesitamos descargar la versión más reciente de PVS-Studio. Para ello utilizamos el gestor de paquetes Chocolatey. Para quienes deseen saber más al respecto, hay una referencia correspondiente. :
choco install pvs-studio -yLuego ejecutaremos la utilidad de seguimiento de la construcción del proyecto CLMonitor.
%CLmon% monitor --attachLuego realizaremos la construcción del proyecto, como variable de entorno MSB sirve como la ruta necesaria para la versión de MSBuild que necesito
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortableIntroduciremos el nombre de usuario y la clave de licencia de PVS-Studio:
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%Tras finalizar la construcción, volveremos a ejecutar CLMonitor para generar archivos preprocesados y realizar el análisis estático:
%CLmon% analyze -l "c:ptest.plog"Luego utilizaremos otra utilidad de nuestro paquete. PlogConverter convierte el informe del formato estándar al formato específico de TeamCity. Gracias a esto, podremos verlo directamente en la ventana de construcción.
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"Como última acción, mostraremos el informe formateado en stdout, donde será recogido por el parser de TeamCity.
type "C:tempptest.plog_TeamCity.txt"El código completo del script:
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, la construcción y análisis del proyecto se han completado con éxito, podemos pasar a la pestaña Projects y verificar esto.

Ahora hagamos clic en Inspecciones Totales, para ir a la vista del informe del analizador:

Las advertencias están agrupadas por números de reglas diagnósticas. Para navegar por el código, hay que hacer clic en el número de línea con el aviso. Hacer clic en el signo de interrogación en la parte superior derecha abrirá una nueva pestaña con la documentación. También se puede navegar por el código haciendo clic en el número de línea con el aviso del analizador. La navegación desde una computadora remota es posible al usar SourceTreeRoot marcador. Aquellos interesados en este modo de operación del analizador pueden consultar la sección correspondiente .
Ver resultados del analizador
Una vez que hayamos terminado con la implementación y configuración de la construcción, propongo ver algunas advertencias interesantes detectadas en el proyecto analizado.
Advertencia N1
[CWE-401] Se lanzó la excepción sin liberar el puntero 'result'. Es posible una fuga de memoria. libopenrct2 ObjectFactory.cpp 443
Object* CreateObjectFromJson(....)
{
Object* result = nullptr;
....
result = CreateObject(entry);
....
if (readContext.WasError())
{
throw std::runtime_error("El objeto tiene errores");
}
....
}
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("Tipo de objeto inválido");
}
return result;
}El analizador notó un error que indica que después de la asignación dinámica de memoria en CreateObject, al ocurrir una excepción, la memoria no se libera, por lo tanto, se produce una fuga de memoria.
Advertencia N2
Existen subexpresiones idénticas '(1ULL << WIDX_MONTH_BOX)' a la izquierda y a la derecha del 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),
....
};Pocas personas, aparte de un analizador estático, podrían pasar esta prueba de atención. Este ejemplo de copia y pega es bueno precisamente por eso.
Advertencias N3
Es curioso que el campo 'flags' en la clase derivada 'RCT12BannerElement' sobrescriba el campo en la clase base 'RCT12TileElementBase'. Verifica 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 derivado no siempre es un error. Sin embargo, la tecnología de herencia supone la presencia de todos los campos de la clase padre en la hija. Al declarar en la clase hija campos con el mismo nombre, generamos confusión.
Advertencia N4
Es extraño que el resultado de la declaración 'imageDirection / 8' sea parte de la condición. Quizás, esta declaración debería haberse comparado con algo más. libopenrct2 ObservationTower.cpp 38
void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
if ((imageDirection / 8) && (imageDirection / 8) != 3)
{
....
}
....
}Vamos a analizarlo con más detalle. La expresión imageDirection / 8 será falsa en el caso de que imageDirection esté en el rango de -7 a 7. La segunda parte: (imageDirection / 8) != 3 verifica imageDirection si se encuentra fuera del rango: de -31 a -24 y de 24 a 31 respectivamente. Me parece bastante extraño comprobar números para que pertenezcan a un rango determinado de esta manera y, aunque no hay error en este fragmento de código, te recomendaría reescribir estas condiciones de manera más explícita. Esto facilitaría mucho la vida a las personas que leerán y mantendrán este código.
Advertencia N5
Una extraña secuencia de asignaciones de este tipo: A = B; B = A;. Verifica las líneas: 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 fue obtenido mediante decompilación. Luego, según el comentario dejado, se eliminó parte del código no funcional. Sin embargo, quedaron un par de operaciones sobre cursorId, que tampoco tienen mucho sentido.
Advertencia N6
[CWE-476] El puntero 'player' se utilizó de forma insegura después de ser verificado contra nullptr. Verifique las líneas: 2085, 2094. libopenrct2 Network.cpp 2094
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
//< Añadir nuevo jugador.
player = AddPlayer("", "");
if (player) //Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
}
newPlayers.push_back(player->Id); //<=
}
....
}Este código se puede corregir bastante fácilmente, necesita comprobar\n por tercera vez el puntero a nulo, o incluirlo en el cuerpo del operador condicional. Yo sugeriría la segunda opción: player en el puntero nulo, o incluirlo en el cuerpo del operador condicional. Yo sugeriría la segunda opción:
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
//Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
newPlayers.push_back(player->Id);
}
}
....
}Advertencia N7
[CWE-570] La expresión 'name == nullptr' siempre es falsa. libopenrct2 ServerList.cpp 102
std::optional ServerListEntry::FromJson(...)
{
auto name = json_object_get(server, "name");
.....
if (name == nullptr || version == nullptr)
{
....
}
else
{
....
entry.name = (name == nullptr ? "" : json_string_value(name));
....
}
....
}Se puede eliminar de una vez la línea de código de difícil lectura y resolver el problema de la verificación en nullptr. Propongo cambiar el código de la siguiente manera:
std::optional ServerListEntry::FromJson(...)
{
auto name = json_object_get(server, "name");
.....
if (name == nullptr || version == nullptr)
{
name = ""
....
}
else
{
....
entry.name = json_string_value(name);
....
}
....
}Advertencia N8
[CWE-1164] La variable 'ColumnHeaderPressedCurrentState' fue asignada al 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. Me parece que hubo un error tipográfico ya sea en la condición o en la reasignación de la variable ColumnHeaderPressedCurrentState valores false.
Salida
Como podemos ver, integrar el analizador estático PVS-Studio en su proyecto en TeamCity es bastante sencillo. Todo lo que necesita hacer es escribir un pequeño archivo de configuración. La verificación del código permitirá identificar problemas inmediatamente después de la compilación, lo que ayudará a resolverlos en un momento en que la complejidad y el costo de las correcciones aún son bajos.
Si desea compartir este artículo con una audiencia de habla inglesa, le pido que utilice el enlace a la traducción: Vladislav Stolyarov. .
Fuente: habr.com
