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.
Información sobre el proyecto en estudio.
Probemos esta funcionalidad en un ejemplo práctico: analicemos el proyecto OpenRCT2.
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:
Haga clic en el botón Crear proyecto. A continuación, seleccione Manualmente y complete los campos.
Después de presionar el botón Crear, somos recibidos por una ventana con la configuración.
hagamos clic Crear configuración de compilación.
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.
Finalmente, pasamos a la configuración del proyecto.
Agreguemos pasos de ensamblaje, para hacer esto haga clic: Pasos de compilación -> Agregar paso de compilación.
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:
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:
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
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.
Ahora hagamos clic en Inspecciones Totalespara ir a ver el informe del analizador:
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.
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
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
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
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
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
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
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
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
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.
Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace de traducción: Vladislav Stolyarov.
Fuente: habr.com