A análise de código estático é mais eficaz ao fazer alterações em um projeto, uma vez que os bugs são sempre mais difíceis de corrigir no futuro do que evitar que ocorram nos estágios iniciais. Continuamos a expandir as opções de uso do PVS-Studio em sistemas de desenvolvimento contínuo e mostramos como configurar a análise de solicitações pull usando agentes auto-hospedados no Microsoft Azure DevOps, usando o exemplo do jogo Minetest.
Resumidamente sobre o que estamos lidando
Minetest é um mecanismo de jogo de plataforma cruzada de código aberto contendo cerca de 200.000 linhas de código C, C ++ e Lua. Ele permite que você crie diferentes modos de jogo no espaço voxel. Suporta multijogador e muitos mods de comunidade. O repositório do projeto está hospedado aqui: https://github.com/minetest/minetest .
As seguintes ferramentas foram usadas para configurar uma pesquisa de erro regular:
PVS-Studio é um analisador de código estático em C, C ++, C # e Java para encontrar erros e defeitos de segurança.
O Azure DevOps é uma plataforma baseada em nuvem que oferece a capacidade de desenvolver, executar aplicativos e armazenar dados em servidores remotos.
Você pode usar máquinas virtuais Windows e Linux para executar tarefas de desenvolvimento no Azure. No entanto, a execução de agentes em hardware local tem várias vantagens significativas:
- Localhost pode ter mais recursos do que a VM do Azure;
- O agente não "desaparece" após completar sua tarefa;
- A capacidade de personalizar diretamente o ambiente e controle mais flexível sobre os processos de construção;
- Armazenar arquivos intermediários localmente tem um efeito positivo na velocidade de construção;
- Você pode completar mais de 30 tarefas por mês gratuitamente.
Preparando-se para usar um agente auto-hospedado
O processo de introdução ao Azure é descrito em detalhes no artigo " PVS-Studio vai para as nuvens: Azure DevOps ", portanto, irei direto para a criação de um agente auto-hospedado.
Para que os agentes tenham o direito de se conectar a pools de projetos, eles precisam de um token de acesso especial. Você pode obtê-lo na página "Tokens de acesso pessoais", no menu "Configurações do usuário".

Após clicar em "Novo token", você precisa especificar um nome e selecionar Ler e gerenciar grupos de agentes (pode ser necessário expandir a lista completa em "Mostrar todos os escopos").

Você precisa copiar o token, pois o Azure não o mostrará mais e você terá que fazer um novo.

Um contêiner Docker baseado no Windows Server Core será usado como um agente. O host é meu computador de trabalho no Windows 10 x64 com Hyper-V.
Primeiro, você precisa expandir a quantidade de espaço em disco disponível para os contêineres do Docker.
No Windows, para isso, você precisa modificar o arquivo 'C: \ ProgramData \ Docker \ config \ daemon.json' da seguinte forma:
{
"registry-mirrors": [],
"insecure-registries": [],
"debug": true,
"experimental": false,
"data-root": "d:\\docker",
"storage-opts": [ "size=40G" ]
}
Para criar uma imagem Docker para agentes com um sistema de compilação e tudo que você precisa no diretório 'D: \ docker-agent', adicione um arquivo Docker com o seguinte conteúdo:
# escape=`
FROM mcr.microsoft.com/dotnet/framework/runtime
SHELL ["cmd", "/S", "/C"]
ADD https://aka.ms/vs/16/release/vs_buildtools.exe C:\vs_buildtools.exe
RUN C:\vs_buildtools.exe --quiet --wait --norestart --nocache `
--installPath C:\BuildTools `
--add Microsoft.VisualStudio.Workload.VCTools `
--includeRecommended
RUN powershell.exe -Command `
Set-ExecutionPolicy Bypass -Scope Process -Force; `
[System.Net.ServicePointManager]::SecurityProtocol =
[System.Net.ServicePointManager]::SecurityProtocol -bor 3072; `
iex ((New-Object System.Net.WebClient)
.DownloadString('https://chocolatey.org/install.ps1')); `
choco feature enable -n=useRememberedArgumentsForUpgrades;
RUN powershell.exe -Command `
choco install -y cmake --installargs '"ADD_CMAKE_TO_PATH=System"'; `
choco install -y git --params '"/GitOnlyOnPath /NoShellIntegration"'
RUN powershell.exe -Command `
git clone https://github.com/microsoft/vcpkg.git; `
.\vcpkg\bootstrap-vcpkg -disableMetrics; `
$env:Path += '";C:\vcpkg"'; `
[Environment]::SetEnvironmentVariable(
'"Path"', $env:Path, [System.EnvironmentVariableTarget]::Machine); `
[Environment]::SetEnvironmentVariable(
'"VCPKG_DEFAULT_TRIPLET"', '"x64-windows"',
[System.EnvironmentVariableTarget]::Machine)
RUN powershell.exe -Command `
choco install -y pvs-studio; `
$env:Path += '";C:\Program Files (x86)\PVS-Studio"'; `
[Environment]::SetEnvironmentVariable(
'"Path"', $env:Path, [System.EnvironmentVariableTarget]::Machine)
RUN powershell.exe -Command `
$latest_agent =
Invoke-RestMethod -Uri "https://api.github.com/repos/Microsoft/
azure-pipelines-agent/releases/latest"; `
$latest_agent_version =
$latest_agent.name.Substring(1, $latest_agent.tag_name.Length-1); `
$latest_agent_url =
'"https://vstsagentpackage.azureedge.net/agent/"' + $latest_agent_version +
'"/vsts-agent-win-x64-"' + $latest_agent_version + '".zip"'; `
Invoke-WebRequest -Uri $latest_agent_url -Method Get -OutFile ./agent.zip; `
Expand-Archive -Path ./agent.zip -DestinationPath ./agent
USER ContainerAdministrator
RUN reg add hklm\system\currentcontrolset\services\cexecsvc
/v ProcessShutdownTimeoutSeconds /t REG_DWORD /d 60
RUN reg add hklm\system\currentcontrolset\control
/v WaitToKillServiceTimeout /t REG_SZ /d 60000 /f
ADD .\entrypoint.ps1 C:\entrypoint.ps1
SHELL ["powershell", "-Command",
"$ErrorActionPreference = 'Stop';
$ProgressPreference = 'SilentlyContinue';"]
ENTRYPOINT .\entrypoint.ps1
O resultado será um sistema de compilação baseado em MSBuild para C ++, com Chocolatey para instalar PVS-Studio, CMake e Git. Para o gerenciamento conveniente das bibliotecas das quais o projeto depende, o Vcpkg é construído. E também a versão mais recente do Azure Pipelines Agent é baixada.
Para inicializar o agente a partir do arquivo ENTRYPOINT Docker, o script PowerShell 'entrypoint.ps1' é chamado, ao qual você precisa adicionar o URL da "organização" do projeto, token de pool do agente e parâmetros de licença PVS-Studio:
$organization_url = "https://dev.azure.com/< Microsoft Azure>"
$agents_token = "<token >"
$pvs_studio_user = "< PVS-Studio>"
$pvs_studio_key = "< PVS-Studio>"
try
{
C:\BuildTools\VC\Auxiliary\Build\vcvars64.bat
PVS-Studio_Cmd credentials -u $pvs_studio_user -n $pvs_studio_key
.\agent\config.cmd --unattended `
--url $organization_url `
--auth PAT `
--token $agents_token `
--replace;
.\agent\run.cmd
}
finally
{
# Agent graceful shutdown
# https://github.com/moby/moby/issues/25982
.\agent\config.cmd remove --unattended `
--auth PAT `
--token $agents_token
}
Comandos para construir a imagem e iniciar o agente:
docker build -t azure-agent -m 4GB .
docker run -id --name my-agent -m 4GB --cpu-count 4 azure-agent

O agente está em execução e pronto para executar tarefas.

Execução de análise em um agente auto-hospedado
Para análise de RP, um novo pipeline é criado com o seguinte script:

trigger: none
pr:
branches:
include:
- '*'
pool: Default
steps:
- script: git diff --name-only
origin/%SYSTEM_PULLREQUEST_TARGETBRANCH% >
diff-files.txt
displayName: 'Get committed files'
- script: |
cd C:\vcpkg
git pull --rebase origin
CMD /C ".\bootstrap-vcpkg -disableMetrics"
vcpkg install ^
irrlicht zlib curl[winssl] openal-soft libvorbis ^
libogg sqlite3 freetype luajit
vcpkg upgrade --no-dry-run
displayName: 'Manage dependencies (Vcpkg)'
- task: CMake@1
inputs:
cmakeArgs: -A x64
-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake
-DCMAKE_BUILD_TYPE=Release -DENABLE_GETTEXT=0 -DENABLE_CURSES=0 ..
displayName: 'Run CMake'
- task: MSBuild@1
inputs:
solution: '**/*.sln'
msbuildArchitecture: 'x64'
platform: 'x64'
configuration: 'Release'
maximumCpuCount: true
displayName: 'Build'
- script: |
IF EXIST .\PVSTestResults RMDIR /Q/S .\PVSTestResults
md .\PVSTestResults
PVS-Studio_Cmd ^
-t .\build\minetest.sln ^
-S minetest ^
-o .\PVSTestResults\minetest.plog ^
-c Release ^
-p x64 ^
-f diff-files.txt ^
-D C:\caches
PlogConverter ^
-t FullHtml ^
-o .\PVSTestResults\ ^
-a GA:1,2,3;64:1,2,3;OP:1,2,3 ^
.\PVSTestResults\minetest.plog
IF NOT EXIST "$(Build.ArtifactStagingDirectory)" ^
MKDIR "$(Build.ArtifactStagingDirectory)"
powershell -Command ^
"Compress-Archive -Force ^
'.\PVSTestResults\fullhtml' ^
'$(Build.ArtifactStagingDirectory)\fullhtml.zip'"
displayName: 'PVS-Studio analyze'
continueOnError: true
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'psv-studio-analisys'
publishLocation: 'Container'
displayName: 'Publish analysis report'
Este script será executado quando o PR for recebido e será executado nos agentes atribuídos ao pool padrão. Você só precisa dar permissão a ele para trabalhar com esta piscina.


O script salva a lista de arquivos alterados obtidos usando git diff. Em seguida, as dependências são atualizadas, a solução do projeto é gerada por meio do CMake e é construída.
Se a construção foi bem sucedida, a análise dos arquivos alterados é iniciada (flag '-f diff-files.txt'), ignorando os projetos auxiliares criados pelo CMake (selecione apenas o projeto requerido com o flag '-S minetest'). Para acelerar a busca por links entre o cabeçalho e os arquivos fonte C ++, um cache especial é criado, que será armazenado em um diretório separado (flag '-DC: \ caches').
Assim, podemos agora receber relatórios de análise de mudanças no projeto.


Como foi dito no início do artigo, um bônus agradável de usar agentes auto-hospedados é uma notável aceleração da execução de tarefas devido ao armazenamento local de arquivos intermediários.

Alguns bugs encontrados no Minetest
Resultado de substituição
V519 A variável 'color_name' recebe valores duas vezes sucessivamente. Talvez isso seja um erro. Verifique as linhas: 621, 627. string.cpp 627
static bool parseNamedColorString(const std::string &value,
video::SColor &color)
{
std::string color_name;
std::string alpha_string;
size_t alpha_pos = value.find('#');
if (alpha_pos != std::string::npos) {
color_name = value.substr(0, alpha_pos);
alpha_string = value.substr(alpha_pos + 1);
} else {
color_name = value;
}
color_name = lowercase(value); // <=
std::map<const std::string, unsigned>::const_iterator it;
it = named_colors.colors.find(color_name);
if (it == named_colors.colors.end())
return false;
....
}
Esta função deve analisar um nome de cor com um parâmetro de transparência (por exemplo, Verde # 77 ) e retornar seu código. Dependendo do resultado da verificação da condição, o resultado da divisão da linha ou uma cópia do argumento da função é passado para a variável color_name . No entanto, não a própria string resultante é convertida em minúsculas, mas o argumento original. Como resultado, ele não pode ser encontrado no dicionário de cores se o parâmetro de transparência estiver presente. Podemos consertar essa linha assim:
color_name = lowercase(color_name);
Verificações de condição
desnecessárias A expressão V547 'mais próximo_emergefull_d == -1' é sempre verdadeira. clientiface.cpp 363
void RemoteClient::GetNextBlocks (....)
{
....
s32 nearest_emergefull_d = -1;
....
s16 d;
for (d = d_start; d <= d_max; d++) {
....
if (block == NULL || surely_not_found_on_disk || block_is_invalid) {
if (emerge->enqueueBlockEmerge(peer_id, p, generate)) {
if (nearest_emerged_d == -1)
nearest_emerged_d = d;
} else {
if (nearest_emergefull_d == -1) // <=
nearest_emergefull_d = d;
goto queue_full_break;
}
....
}
....
queue_full_break:
if (nearest_emerged_d != -1) { // <=
new_nearest_unsent_d = nearest_emerged_d;
} else ....
}
A variável mais próxima_emergefull_d não muda durante a operação de loop, e sua verificação não afeta a execução do algoritmo. Ou isso é o resultado de copiar e colar impreciso ou eles se esqueceram de fazer alguns cálculos com ele.
V560 Uma parte da expressão condicional é sempre falsa: y> max_spawn_y. mapgen_v7.cpp 262
int MapgenV7::getSpawnLevelAtPoint(v2s16 p)
{
....
while (iters > 0 && y <= max_spawn_y) { // <=
if (!getMountainTerrainAtPoint(p.X, y + 1, p.Y)) {
if (y <= water_level || y > max_spawn_y) // <=
return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
// y + 1 due to biome 'dust'
return y + 1;
}
....
}
O valor da variável ' y ' é verificado antes da próxima iteração do loop. A comparação subsequente, oposta, sempre retornará falso e, em geral, não afeta o resultado do teste de condição.
Perda de verificação de ponteiro
V595 do ponteiro 'm_client' foi utilizado antes de ser verificado em relação a nullptr. Verifique as linhas: 183, 187. game.cpp 183
void gotText(const StringMap &fields)
{
....
if (m_formname == "MT_DEATH_SCREEN") {
assert(m_client != 0);
m_client->sendRespawn();
return;
}
if (m_client && m_client->modsLoaded())
m_client->getScript()->on_formspec_input(m_formname, fields);
}
Antes de acessar o ponteiro m_client , é verificado se ele não é nulo usando a macro assert . Mas isso só se aplica à compilação de depuração. Essa precaução, ao construir na liberação, é substituída por um manequim, e há o risco de desreferenciar um ponteiro nulo.
Um pouco ou não?
V616 A constante nomeada '(FT_RENDER_MODE_NORMAL)' com o valor 0 é usada na operação bit a bit. CGUITTFont.h 360
typedef enum FT_Render_Mode_
{
FT_RENDER_MODE_NORMAL = 0,
FT_RENDER_MODE_LIGHT,
FT_RENDER_MODE_MONO,
FT_RENDER_MODE_LCD,
FT_RENDER_MODE_LCD_V,
FT_RENDER_MODE_MAX
} FT_Render_Mode;
#define FT_LOAD_TARGET_( x ) ( (FT_Int32)( (x) & 15 ) << 16 )
#define FT_LOAD_TARGET_NORMAL FT_LOAD_TARGET_( FT_RENDER_MODE_NORMAL )
void update_load_flags()
{
// Set up our loading flags.
load_flags = FT_LOAD_DEFAULT | FT_LOAD_RENDER;
if (!useHinting()) load_flags |= FT_LOAD_NO_HINTING;
if (!useAutoHinting()) load_flags |= FT_LOAD_NO_AUTOHINT;
if (useMonochrome()) load_flags |=
FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO | FT_RENDER_MODE_MONO;
else load_flags |= FT_LOAD_TARGET_NORMAL; // <=
}
A macro FT_LOAD_TARGET_NORMAL se expande para zero, e o bit a bit "ou" não definirá nenhum sinalizador em load_flags , o outro branch pode ser removido.
Divisão de número inteiro arredondado
V636 A expressão 'rect.getHeight () / 16' foi convertida implicitamente do tipo 'int' para o tipo 'float'. Considere a utilização de um tipo explícito de fundição para evitar a perda de uma parte fracionária. Um exemplo: double A = (double) (X) / Y;. hud.cpp 771
void drawItemStack(....)
{
float barheight = rect.getHeight() / 16;
float barpad_x = rect.getWidth() / 16;
float barpad_y = rect.getHeight() / 16;
core::rect<s32> progressrect(
rect.UpperLeftCorner.X + barpad_x,
rect.LowerRightCorner.Y - barpad_y - barheight,
rect.LowerRightCorner.X - barpad_x,
rect.LowerRightCorner.Y - barpad_y);
}
Getters rect retorna um valor inteiro. O resultado da divisão de inteiros é gravado em uma variável de ponto flutuante e a parte fracionária é perdida. Parece que há tipos de dados incompatíveis nesses cálculos. Instruções de
ramificação de sequência suspeita
V646 Considere inspecionar a lógica do aplicativo. É possível que a palavra-chave 'else' esteja faltando. treegen.cpp 413
treegen::error make_ltree(...., TreeDef tree_definition)
{
....
std::stack <core::matrix4> stack_orientation;
....
if ((stack_orientation.empty() &&
tree_definition.trunk_type == "double") ||
(!stack_orientation.empty() &&
tree_definition.trunk_type == "double" &&
!tree_definition.thin_branches)) {
....
} else if ((stack_orientation.empty() &&
tree_definition.trunk_type == "crossed") ||
(!stack_orientation.empty() &&
tree_definition.trunk_type == "crossed" &&
!tree_definition.thin_branches)) {
....
} if (!stack_orientation.empty()) { // <=
....
}
....
}
Aqui estão as sequências else-if no algoritmo de geração de árvore. No meio, o próximo bloco if está na mesma linha com o parêntese de fechamento do else anterior . Talvez o código funcione corretamente: antes disso, se -a, são criados os blocos de troncos e depois as folhas; ou talvez eles tenham perdido o outro . Certamente isso só pode ser dito pelo desenvolvedor.
Verificação de alocação de memória incorreta
V668 Não faz sentido testar o ponteiro de 'nuvens' em relação a nulo, já que a memória foi alocada usando o operador 'novo'. A exceção será gerada no caso de erro de alocação de memória. game.cpp 1367
bool Game::createClient(....)
{
if (m_cache_enable_clouds) {
clouds = new Clouds(smgr, -1, time(0));
if (!clouds) {
*error_message = "Memory allocation error (clouds)";
errorstream << *error_message << std::endl;
return false;
}
}
}
No caso de new falhar ao criar um objeto, uma exceção std :: bad_alloc será lançada e deve ser tratada por um bloco try-catch . E verificar neste formulário é inútil.
Leitura de uma matriz fora dos
limites V781 O valor do índice 'i' é verificado após ser usado. Talvez haja um erro na lógica do programa. irrString.h 572
bool equalsn(const string<T,TAlloc>& other, u32 n) const
{
u32 i;
for(i=0; array[i] && other[i] && i < n; ++i) // <=
if (array[i] != other[i])
return false;
// if one (or both) of the strings was smaller then they
// are only equal if they have the same length
return (i == n) || (used == other.used);
}
Os elementos da matriz são acessados antes de verificar o índice, o que pode levar a um erro. Pode valer a pena reescrever o loop assim:
for (i=0; i < n; ++i) // <=
if (!array[i] || !other[i] || array[i] != other[i])
return false;
Outros erros
Este artigo é sobre a análise de solicitações pull no Azure DevOps e não se destina a fornecer uma visão geral detalhada dos erros no projeto Minetest. Aqui estão apenas alguns trechos de código que achei interessantes. Sugerimos que os autores do projeto não sigam este artigo para corrigir os erros e realizar uma análise mais aprofundada dos avisos que o PVS-Studio irá emitir.
Conclusão
Graças à configuração flexível no modo de linha de comando, a análise do PVS-Studio pode ser incorporada em uma ampla variedade de cenários de CI / CD. E fazer o uso correto dos recursos disponíveis compensa no aumento da produtividade.
Deve-se observar que o modo de verificação de solicitação pull está disponível apenas na edição Enterprise do analisador. Para obter uma licença Enterprise de demonstração, indique-a nos comentários ao solicitar uma licença na página de download . Mais detalhes sobre a diferença entre as licenças podem ser encontrados na página de pedido do PVS-Studio .

Se você deseja compartilhar este artigo com um público que fala inglês, por favor, use o link de tradução: Alexey Govorov. PVS-Studio: analisando solicitações pull no Azure DevOps usando agentes auto-hospedados .