Características de la configuración y ejecución de PVS-Studio en Docker en el ejemplo del código de Azure Service Fabric

 C Programming >> Programación C >  >> Tags >> Azure
Características de la configuración y ejecución de PVS-Studio en Docker en el ejemplo del código de Azure Service Fabric

Se utiliza activamente una tecnología de contenedorización para construir y probar el software. Con la aparición de PVS-Studio para Linux, la capacidad de agregar el análisis estático a otros métodos de prueba de un proyecto en esta plataforma, incluido Docker, estuvo disponible para los usuarios. El artículo describirá las características del trabajo con el analizador PVS-Studio en Docker, lo que mejorará la calidad del análisis y la usabilidad. El artículo también proporcionará los errores encontrados en el proyecto Azure Service Fabric.

Introducción

Docker es un programa que permite que el sistema operativo ejecute procesos en un entorno aislado sobre la base de imágenes especialmente creadas. La tecnología de contenedorización se ha vuelto muy común para muchas tareas, incluido el desarrollo y prueba de software. El análisis estático generalmente se realiza en el mismo entorno que la compilación del proyecto, por lo que su uso se implementa muy fácilmente en los contenedores ya existentes en Docker.

Los ejemplos de integración y ejecución del analizador estático PVS-Studio se darán para la versión de Linux. Además, las posibilidades descritas de personalización del analizador se recomiendan incluso en otra plataforma. La versión del analizador bajo macOS, que se presentó recientemente al público, es generalmente idéntica en el uso de PVS-Studio para Linux.

El proyecto Azure Service Fabric fue elegido como proyecto de integración y lanzamiento del analizador en Docker. Service Fabric es una plataforma de sistemas distribuidos para empaquetar, implementar y administrar aplicaciones y contenedores distribuidos sin estado y con estado a gran escala. Service Fabric se ejecuta en Windows y Linux, en cualquier nube, en cualquier centro de datos, en regiones geográficas o en su computadora portátil.

Implementación por fases del analizador

Para comenzar, echemos un vistazo a la forma en que se realiza la compilación para elegir la forma de integración del analizador. El orden de las secuencias de comandos y llamadas de comandos se ve así:

El siguiente fragmento del script build.sh donde se genera el archivo del proyecto:

cmake ${CMakeGenerator} \
  -DCMAKE_C_COMPILER=${CC} \
  -DCMAKE_CXX_COMPILER=${CXX} \
  -DCMAKE_BUILD_TYPE=${BuildType} \
  -DBUILD_THIRD_PARTY=${BuildThirdPartyLib} \
  ${DisablePrecompileFlag} ${ScriptPath}/$DirName

Para analizar el proyecto, decidí usar el método de la documentación descrita en la sección Quick run/CMake-project:

diff --git a/src/build.sh b/src/build.sh
index 290c57d..5901fd6 100755
--- a/src/build.sh
+++ b/src/build.sh
@@ -179,6 +179,7 @@ BuildDir()
               -DCMAKE_CXX_COMPILER=${CXX} \
               -DCMAKE_BUILD_TYPE=${BuildType} \
               -DBUILD_THIRD_PARTY=${BuildThirdPartyLib} \
+              -DCMAKE_EXPORT_COMPILE_COMMANDS=On \
               ${DisablePrecompileFlag} ${ScriptPath}/$DirName
         if [ $? != 0 ]; then
             let TotalErrors+=1

La adición de la instalación del analizador:

diff --git a/src/build.sh b/src/build.sh
index 290c57d..581cbaf 100755
--- a/src/build.sh
+++ b/src/build.sh
@@ -156,6 +156,10 @@ BuildDir()
         CXX=${ProjRoot}/deps/third-party/bin/clang/bin/clang++
     fi
 
+    dpkg -i /src/pvs-studio-6.23.25754.2246-amd64.deb
+    apt -f install -y
+    pvs-studio --version
+

El directorio origen es parte del proyecto y está montado en /src . Coloqué el archivo de configuración del analizador PVS-Studio.cfg en el mismo lugar. Entonces la llamada al analizador se puede realizar de la siguiente manera:

diff --git a/src/build.sh b/src/build.sh
index 290c57d..2a286dc 100755
--- a/src/build.sh
+++ b/src/build.sh
@@ -193,6 +193,9 @@ BuildDir()
     
     cd ${ProjBinRoot}/build.${DirName}
 
+    pvs-studio-analyzer analyze --cfg /src/PVS-Studio.cfg \
+      -o ./service-fabric-pvs.log -j4
+
     if [ "false" = ${SkipBuild} ]; then
         if (( $NumProc <= 0 )); then
             NumProc=$(($(getconf _NPROCESSORS_ONLN)+0))

Ejecuté el analizador antes de la compilación del proyecto. Esta no es la decisión correcta, pero en el script hay muchas condiciones bajo las cuales se ejecuta una compilación, por lo que simplifiqué un poco la tarea y compilé el proyecto por adelantado. Los desarrolladores que conocen mejor la estructura de su proyecto deberían integrar el analizador después construyendo el proyecto.

Ahora es posible construir y analizar un proyecto con el siguiente comando:

sudo ./runbuild.sh -release -j4

Los primeros resultados del análisis no nos agradan debido a las advertencias sobre numerosas macros, archivos inexistentes, rutas incorrectas a archivos de código fuente, etc. En la siguiente sección, hablaré sobre el contenido del archivo PVS -Estudio.cfg , donde agregué algunas configuraciones, lo que mejoró significativamente el análisis.

Configuración del analizador adicional

La ruta relativa al directorio con los archivos de origen

Para ver un solo informe en diferentes computadoras, el analizador puede generar un informe con rutas relativas a los archivos. Puede restaurarlos en otra computadora usando el convertidor.

Se debe realizar una configuración de analizador similar para extraer un informe con las rutas de archivo correctas de un contenedor.

El directorio raíz está montado en la raíz, por lo que el parámetro del analizador sería el siguiente:

sourcetree-root=/

El directorio raíz se selecciona aquí porque en un contenedor y en un host es el directorio del proyecto.

Advertencias para archivos inexistentes

En el contenedor un /externo se expande el catálogo, que no existe en el repositorio. Lo más probable es que algunas dependencias del proyecto estén compiladas en él y simplemente puedan excluirse del análisis:

exclude-path=/external

Advertencias para archivos compiladores, pruebas y bibliotecas

En Docker, un compilador se puede colocar en una ubicación no estándar y sus bibliotecas pueden incluirse en un informe. Deben ser eliminados también. Para ello, el directorio /deps y el directorio con las pruebas se excluyen de la verificación:

exclude-path=/deps
exclude-path=/src/prod/test

Lucha contra miles de falsos positivos que surgen debido a macros fallidas

El analizador admite una configuración de diferentes diagnósticos mediante comentarios. Puedes leer sobre ellos aquí y aquí.

Puede colocar la configuración en el código del proyecto o crear un archivo separado, como hice yo:

rules-config=/src/service-fabric.pvsconfig

El contenido del archivo service-fabric.pvsconfig:

#V501
//-V:CODING_ERROR_ASSERT:501
//-V:TEST_CONFIG_ENTRY:501
//-V:VERIFY_IS_TRUE:501
//-V:VERIFY_ARE_EQUAL:501
//-V:VERIFY_IS_FALSE:501
//-V:INTERNAL_CONFIG_ENTRY:501
//-V:INTERNAL_CONFIG_GROUP:501
//-V:PUBLIC_CONFIG_ENTRY:501
//-V:PUBLIC_CONFIG_GROUP:501
//-V:DEPRECATED_CONFIG_ENTRY:501
//-V:TR_CONFIG_PROPERTIES:501
//-V:DEFINE_SECURITY_CONFIG_ADMIN:501
//-V:DEFINE_SECURITY_CONFIG_USER:501
//-V:RE_INTERNAL_CONFIG_PROPERTIES:501
//-V:RE_CONFIG_PROPERTIES:501
//-V:TR_INTERNAL_CONFIG_PROPERTIES:501
#V523
//-V:TEST_COMMIT_ASYNC:523
#V640
//-V:END_COM_INTERFACE_LIST:640

Unas pocas líneas de marcado especial eliminan del informe miles de advertencias para macros.

Otras configuraciones

La ruta al archivo de licencia y habilitando solo diagnósticos de propósito general (para acelerar el análisis):

lic-file=/src/PVS-Studio.lic
analysis-mode=4

Todo el archivo PVS-Studio.cfg

lic-file=/src/PVS-Studio.lic
rules-config=/src/service-fabric.pvsconfig
exclude-path=/deps
exclude-path=/external
exclude-path=/src/prod/test
analysis-mode=4
sourcetree-root=/

Podría ser necesario en otros proyectos

Otra forma de probar un proyecto requiere tener un seguimiento de la utilidad del sistema. Lo más probable es que no se presente en el contenedor y tendrás que agregar el paso de instalar esta utilidad desde un repositorio.

El contenedor puede incluir un compilador no estándar, por ejemplo, un compilador cruzado. Ya he escrito que es necesario excluir el directorio del compilador del análisis, pero en este caso tendrás que pasarle al analizador el nombre del nuevo compilador también:

pvs-studio-analyzer analyze ... --compiler COMPILER_NAME...

Puede duplicar una bandera para especificar varios compiladores.

Vista de un Informe en Linux o Windows

Para ver el informe del analizador en Linux, puede agregar un comando en un script para generar el informe en el formato necesario.

Por ejemplo, para ver en QtCreator:

plog-converter -t tasklist -r "~/Projects/service-fabric" \
  ./service-fabric-pvs.log -o ./service-fabric-pvs.tasks

O en el navegador:

plog-converter -t fullhtml -r "~/Projects/service-fabric" \
  ./service-fabric-pvs.log -o ./

Para ver el informe en Windows, simplemente abra el .log archivo en la utilidad Standalone, que se incluye en el paquete de distribución para Windows.

Ejemplos de errores de Azure Service Fabric

Errores tipográficos clásicos

V501 CWE-571 Hay subexpresiones idénticas a la izquierda y a la derecha del operador '==':iter->PackageName ==iter->PackageName DigestedApplicationDescription.cpp 247

ErrorCode
DigestedApplicationDescription::ComputeAffectedServiceTypes(....)
{
  ....
  if (iter->PackageName == iter->PackageName &&
    originalRG != this->ResourceGovernanceDescriptions.end() &&
    targetRG != targetDescription.ResourceGovernanceDes....end())
  {
    ....
  }
  ....
}

La variable iter->Nombre del paquete debe compararse con iter2->PackageName o paquetes de código .

V501 CWE-571 Hay subexpresiones idénticas '(dataSizeInRecordIoBuffer> 0)' a la izquierda ya la derecha del operador '&&'. OverlayStream.cpp 4966

VOID
OverlayStream::AsyncMultiRecordReadContextOverlay::FSMContinue(
    __in NTSTATUS Status
    )
{
  ULONG dataSizeInRecordMetadata = 0;
  ULONG dataSizeInRecordIoBuffer = 0;
  ....
  if ((dataSizeInRecordIoBuffer > 0) &&
      (dataSizeInRecordIoBuffer > 0))
  {
    ....
  }
  ....
}

Debido a Copiar-Pegar el tamaño del búfer dataSizeInRecordMetadata no está marcado.

V534 CWE-691 Es probable que se esté comparando una variable incorrecta dentro del operador 'for'. Considere revisar 'ix0'. RvdLoggerVerifyTests.cpp 2395

NTSTATUS
ReportLogStateDifferences(....)
{
  ....
  for (ULONG ix0=0; ix0 < RecoveredState._NumberOfStreams; ix0++)
  {
    KWString    streamId(....);
    ULONG       ix1;

    for (ix1 = 0; ix0 < LogState._NumberOfStreams; ix1++)
    {
      ...
    }
    ....
  }
  ....
}

Probablemente, en una condición de bucle anidado, la variable ix1 debe comprobarse en lugar de ix0 uno.

V570 La variable 'statusDetails_' se asigna a sí misma. ComposeDeploymentStatusQueryResult.cpp 49

ComposeDeploymentStatusQueryResult &
ComposeDeploymentStatusQueryResult::operator = (
  ComposeDeploymentStatusQueryResult && other)        // <=
{
  if (this != & other)
  {
    deploymentName_ = move(other.deploymentName_);
    applicationName_ = move(other.applicationName_);
    dockerComposeDeploymentStatus_ = move(other....);
    statusDetails_ = move(statusDetails_);            // <=
  }

  return *this;
}

Lo más probable es que un programador quisiera tomar el valor del campo statusDetails_ de otros.statusDetails_ , pero cometió un error tipográfico.

V606 Token sin propietario 'falso'. CryptoUtility.Linux.h 81

template <typename TK, typename TV>
static bool MapCompare(const std::map<TK, TV>& lhs,
                       const std::map<TK, TV>& rhs)
{
  if (lhs.size() != rhs.size()) { false; }

  return std::equal(lhs.begin(), lhs.end(), rhs.begin());
}

Falta una palabra clave return ha resultado en un código no óptimo. Debido a un error tipográfico, la comprobación rápida del tamaño de las colecciones no funciona como pretendía el autor.

V607 CWE-482 Expresión sin propietario. EnvironmentOverrideDescription.cpp 60

bool EnvironmentOverridesDescription::operator == (....) const
{
  bool equals = true;
  for (auto i = 0; i < EnvironmentVariables.size(); i++)
  {
    equals = EnvironmentVariables[i] ==
             other.EnvironmentVariables[i];
    if (!equals) { return equals; }
  }
  this->CodePackageRef == other.CodePackageRef; // <=
  if (!equals) { return equals; }
  return equals;
}

Un error tipográfico es similar al ejemplo anterior, pero conduce a un error más grave. El resultado de una de las comparaciones nunca se guarda. El código correcto debería ser así:

equals = this->CodePackageRef == other.CodePackageRef;
if (!equals) { return equals; }

Uso incorrecto de funciones

V521 CWE-480 Tales expresiones que usan el operador ',' son peligrosas. Asegúrate de que la expresión sea correcta. ReplicatedStore.SecondaryPump.cpp 1231

ErrorCode
ReplicatedStore::SecondaryPump::ApplyOperationsWithRetry(....)
{
 ....
 if (errorMessage.empty())
 {
  errorMessage = L"error details missing: LSN={0}", operationLsn;

  Assert::TestAssert("{0}", errorMessage);
 }
 ....
}

El analizador ha detectado un código extraño para generar un mensaje en la variable errorMessage . A juzgar por los fragmentos vecinos, aquí debe escribirse lo siguiente:

WriteInfo(errorMessage, L"error ....: LSN={0}", operationLsn);

V547 CWE-570 La expresión 'nwrite <0' siempre es falsa. El valor de tipo sin firmar nunca es <0. File.cpp 1941

static void* ScpWorkerThreadStart(void* param)
{
  ....
  do
  {
    size_t nwrite = fwrite(ptr, 1, remaining, destfile);
    if (nwrite < 0)
    {
      pRequest->error_.Overwrite(ErrorCode::FromErrno(errno));
      break;
    }
    else
    {
      remaining -= nwrite;
      ptr += nwrite;
      pRequest->szCopied_ += nwrite;
    }
  } while (remaining != 0);
  ....
}

Comprobación incorrecta del valor de retorno de la función fwrite() . La documentación para esta función se puede encontrar en cppreference.com y cplusplus.com.

V547 CWE-571 La expresión 'len>=0' siempre es verdadera. El valor de tipo sin firmar siempre es>=0. Types.cpp 121

size_t BIO_ctrl_pending(BIO *b);

template <typename TBuf>
TBuf BioMemToTBuf(BIO* bio)
{
  char* data = NULL;
  auto len = BIO_ctrl_pending(bio);
  Invariant(len >= 0);
  ....
}

Comprobación incorrecta del valor de retorno de una función de la biblioteca OpenSSL. Esto bien puede ser un error grave, o incluso una vulnerabilidad.

Acerca de los punteros y la memoria

V603 CWE-665 El objeto se creó pero no se está utilizando. Si desea llamar al constructor, debe usar 'this->JsonBufferManager2::JsonBufferManager2(....)'. JsonReader.h 48

class JsonBufferManager2
{
    template<typename T>
    friend struct JsonBufferManagerTraits;
public:
    JsonBufferManager2()
    {
        JsonBufferManager2(nullptr, 0);
    }
    ....
}

Probablemente un programador quería llamar a un constructor de otro. En realidad, un objeto temporal de una clase JsonBufferManager2 se crea y se destruye inmediatamente. Este tipo de error se describe en detalle en el artículo "No vadees en aguas desconocidas. Primera parte". Este artículo también explica cómo puede llamar a un constructor desde otro.

V568 Es extraño que el operador 'sizeof()' evalúe el tamaño de un puntero a una clase, pero no el tamaño del objeto de la clase 'thisPtr'. TimerQueue.cpp 443

void TimerQueue::SigHandler(int sig, siginfo_t *si, void*)
{
  TimerQueue* thisPtr = (TimerQueue*)si->si_value.sival_ptr;

  auto written = write(thisPtr->pipeFd_[1],
                       &thisPtr, sizeof(thisPtr));

  Invariant(written == sizeof(thisPtr));           // <=
}

El tamaño() correcto se pasa a la función write(), pero el resultado de la función de lectura, muy probablemente, tiene que ser comparado con el tamaño del objeto escrito:

Invariant(written == sizeof(*thisPtr));

V595 CWE-476 El puntero 'globalDomain' se utilizó antes de que se verificara contra nullptr. Verificar líneas:196, 197. PlacementReplica.cpp 196

void PlacementReplica::ForEachWeightedDefragMetric(....) const
{
  ....
  size_t metricIndexInGlobalDomain =
    totalMetricIndexInGloba.... - globalDomain->MetricStartIndex;
  if (globalDomain != nullptr &&
    globalDomain->Metrics[metricIndexInGlobalDomain].Weight > 0)
  {
    if (!processor(totalMetricIndexInGlobalDomain))
    {
      break;
    }
  }
}

Un error clásico con el puntero globalDomain :primero una desreferencia, luego un cheque.

V611 CWE-762 La memoria se asignó con el operador 'nueva T[]' pero se liberó con el operador 'eliminar'. Considere inspeccionar este código. Probablemente sea mejor usar 'eliminar [] grupos;'. PAL.cpp 4733

NET_API_STATUS NetUserGetLocalGroups(....)
{
  string unameA = utf16to8(UserName).substr(0, ACCT_NAME_MAX);
  int ngroups = 50;
  gid_t *groups = new gid_t[ngroups];
  gid_t gid;
  ....
  delete groups;
  return NERR_Success;
}

Se encontraron muchos lugares donde la memoria, asignada para una matriz, se liberó incorrectamente. eliminar[] había que usar.

Ejecución del analizador en los contenedores con Windows

En este caso, la ejecución del analizador no es muy diferente de la automatización del análisis, por ejemplo, en Jenkins en una computadora real. Nosotros mismos usamos Docker para probar PVS-Studio para Windows. Simplemente puede realizar la instalación del analizador:

START /w PVS-Studio_setup.exe /VERYSILENT /SUPPRESSMSGBOXES \
  /NORESTART /COMPONENTS=Core,Standalone

y ejecuta el análisis de tu proyecto:

"C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" ...

Conclusión

El enfoque del artículo se centró en la emocionante tecnología de contenerización, que no es un obstáculo para la integración del análisis estático en su proyecto. Por lo tanto, las advertencias encontradas de PVS-Studio se redujeron en el artículo, pero están completamente disponibles para descargar en el formato para el navegador:service-fabric-pvs-studio-html .7z.

Sugiero a aquellos que estén interesados ​​que prueben PVS-Studio en sus proyectos. ¡El analizador funciona en Windows, Linux y macOS!