PVS-Studio va a las nubes: Azure DevOps

Cuadro 9

Este es el segundo artículo sobre el uso del analizador estático PVS-Studio en sistemas de CI en la nube, y esta vez consideraremos la plataforma Azure DevOps, una solución de CI \ CD en la nube de Microsoft. Como un proyecto analizado esta vez, considere ShareX.

Necesitaremos tres componentes. El primero es el analizador estático PVS-Studio. El segundo es Azure DevOps, con el que integraremos el analizador. El tercero es un proyecto que verificaremos para demostrar las capacidades de PVS-Studio cuando trabaje en la nube. Entonces comencemos.

PVS-Studio es un analizador de código estático para buscar errores y defectos de seguridad. Realiza análisis de código en C, C ++, C # y Java.

Azure DevOps . La plataforma Azure DevOps incluye herramientas como Azure Pipeline, Azure Board, Azure Artifacts y otras, que aceleran el proceso de creación de software y mejoran su calidad.

ShareX es una aplicación gratuita que le permite capturar y grabar cualquier parte de la pantalla. El proyecto está escrito en C # y es excelente para demostrar cómo ejecutar el analizador estático. El código fuente del proyecto está disponible en GitHub .

La salida del comando cloc para el proyecto ShareX:
Idioma
archivos
en blanco
comentar
Código
C #
696
20658
24423
102565
Script MSBuild
11
1
77
5859
En otras palabras, el proyecto es pequeño, pero suficiente para demostrar el trabajo de PVS-Studio junto con una plataforma en la nube.

Vamos a configurar


Para comenzar en Azure DevOps, haga clic en el enlace y haga clic en el botón "Comenzar gratis con GitHub".

Imagen 2

Dar a la aplicación de Microsoft acceso a los datos de la cuenta de GitHub.

Imagen 1

Para completar el registro tendrá que crear una cuenta de Microsoft.

Cuadro 12

Después del registro, cree un proyecto:

Cuadro 5

A continuación, debemos ir a la sección "Tuberías" - "Construcciones" y crear una nueva tubería de construcción

Cuadro 8

A la pregunta de dónde se encuentra nuestro código, responderemos: GitHub.

Cuadro 13

Autorizamos la aplicación Azure Pipelines y seleccionamos el repositorio con el proyecto para el cual configuraremos el lanzamiento del analizador estático.

Cuadro 7

En la ventana de selección de plantilla, seleccione "Tubería de inicio".

Cuadro 17

Podemos ejecutar análisis estáticos del código del proyecto de dos maneras: usando agentes alojados en Microsoft o autohospedados.

En la primera versión, utilizaremos agentes alojados en Microsoft. Dichos agentes son máquinas virtuales ordinarias que comienzan cuando comenzamos nuestra canalización y se eliminan después del final de la tarea. El uso de dichos agentes le permite no perder el tiempo apoyándolos y actualizándolos, pero impone algunas restricciones, por ejemplo, la imposibilidad de instalar software adicional que se utiliza para construir el proyecto.

Reemplace nuestra configuración predeterminada con la siguiente para usar agentes alojados en Microsoft:

#    #      master- trigger: - master #         # ,   Docker-, #      Windows Server 1803 pool: vmImage: 'win1803' container: microsoft/dotnet-framework:4.7.2-sdk-windowsservercore-1803 steps: #    - task: PowerShell@2 inputs: targetType: 'inline' script: 'Invoke-WebRequest -Uri https://files.viva64.com/PVS-Studio_setup.exe -OutFile PVS-Studio_setup.exe' - task: CmdLine@2 inputs: workingDirectory: $(System.DefaultWorkingDirectory) script: | #      nuget restore .\ShareX.sln #  ,        md .\PVSTestResults #   PVS-Studio_setup.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /COMPONENTS=Core #        "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" credentials -u $(PVS_USERNAME) -n $(PVS_KEY) #        html. "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" -t .\ShareX.sln -o .\PVSTestResults\ShareX.plog "C:\Program Files (x86)\PVS-Studio\PlogConverter.exe" -t html -o .\PVSTestResults\ .\PVSTestResults\ShareX.plog #    - task: PublishBuildArtifacts@1 inputs: pathToPublish: PVSTestResults artifactName: PVSTestResults 

Nota: según la documentación , el contenedor utilizado debe almacenarse en caché en la imagen de la máquina virtual, pero al momento de escribir este artículo no funciona y el contenedor se descarga cada vez que se inicia la tarea, lo que afecta negativamente el tiempo de ejecución.

Guarde la canalización y cree las variables que se utilizarán para crear el archivo de licencia. Para hacer esto, abra la ventana de edición de tubería y en la esquina superior derecha haga clic en el botón "Variables".

Cuadro 14

Agregue dos variables: PVS_USERNAME y PVS_KEY , que contienen el nombre de usuario y la clave de licencia, respectivamente. Al crear la variable PVS_KEY , no olvide comprobar el elemento "Mantener este valor secreto" para cifrar el valor de la variable con una clave RSA de 2048 bits, así como suprimir la salida del valor de la variable al registro de ejecución de la tarea.

Cuadro 15

Guardamos las variables y comenzamos la canalización con el botón "Ejecutar".

La segunda opción para ejecutar el análisis es usar un agente autohospedado. Los agentes autohospedados son agentes que configuramos y administramos nosotros mismos. Dichos agentes brindan más oportunidades para instalar software, que es necesario para el ensamblaje y prueba de nuestro producto de software.

Antes de usar dichos agentes, deben configurarse de acuerdo con las instrucciones y debe instalarse y configurarse un analizador estático.

Para iniciar la tarea en un agente autohospedado, reemplazamos la configuración predeterminada propuesta con lo siguiente:

 #    #    master- trigger: - master #    self-hosted    'MyPool' pool: 'MyPool' steps: - task: CmdLine@2 inputs: workingDirectory: $(System.DefaultWorkingDirectory) script: | #      nuget restore .\ShareX.sln #  ,        md .\PVSTestResults #        html. "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" -t .\ShareX.sln -o .\PVSTestResults\ShareX.plog "C:\Program Files (x86)\PVS-Studio\PlogConverter.exe" -t html -o .\PVSTestResults\ .\PVSTestResults\ShareX.plog #    - task: PublishBuildArtifacts@1 inputs: pathToPublish: PVSTestResults artifactName: PVSTestResults 

Después de completar la tarea, el archivo con los informes del analizador se puede descargar en la pestaña Resumen, o podemos usar la extensión Enviar correo , que le permite configurar el envío de correos electrónicos, o buscar una herramienta más conveniente para nosotros en el Mercado .

Cuadro 21

Sobre los resultados del análisis


Ahora veamos algunos de los errores que se encontraron en el proyecto verificado: ShareX.

Cheques redundantes

Para calentar, comencemos con fallas simples en el código, es decir, con verificaciones redundantes:

 private void PbThumbnail_MouseMove(object sender, MouseEventArgs e) { .... IDataObject dataObject = new DataObject(DataFormats.FileDrop, new string[] { Task.Info.FilePath }); if (dataObject != null) { Program.MainForm.AllowDrop = false; dragBoxFromMouseDown = Rectangle.Empty; pbThumbnail.DoDragDrop(dataObject, DragDropEffects.Copy | DragDropEffects.Move); Program.MainForm.AllowDrop = true; } .... } 

Advertencia de PVS-Studio : V3022 [CWE-571] La expresión 'dataObject! = Null' siempre es verdadera. TaskThumbnailPanel.cs 415

Preste atención a verificar la variable dataObject para nulo . ¿Para qué está ella aquí? dataObject simplemente no puede ser nulo en este caso, ya que se inicializa con una referencia al objeto creado. Como resultado, tenemos verificación redundante. ¿Es crítico? No Se ve conciso? No Esta verificación es claramente mejor eliminada para no saturar el código.

Echemos un vistazo a otro fragmento de código, al que puede hacer comentarios similares:

 private static Image GetDIBImage(MemoryStream ms) { .... try { .... return new Bitmap(bmp); .... } finally { if (gcHandle != IntPtr.Zero) { GCHandle.FromIntPtr(gcHandle).Free(); } } .... } private static Image GetImageAlternative() { .... using (MemoryStream ms = dataObject.GetData(format) as MemoryStream) { if (ms != null) { try { Image img = GetDIBImage(ms); if (img != null) { return img; } } catch (Exception e) { DebugHelper.WriteException(e); } } } .... } 

Advertencia de PVS-Studio : V3022 [CWE-571] La expresión 'img! = Null' siempre es verdadera. ClipboardHelpers.cs 289

El método GetImageAlternative nuevamente verifica que la variable img no sea nula inmediatamente después de crear una nueva instancia de la clase Bitmap . La diferencia con el ejemplo anterior aquí es que para inicializar la variable img , no usamos el constructor, sino el método GetDIBImage . El autor del código supone que puede ocurrir una excepción en este método, pero declara que solo intenta y finalmente bloquea, omitiendo catch . Por lo tanto, si se produce una excepción, el método de llamada - GetImageAlternative - no recibirá una referencia a un objeto de tipo Bitmap, pero se verá obligado a manejar la excepción en su propio bloque catch . En este caso, la variable img no se inicializará, y el hilo de ejecución ni siquiera alcanzará el check img! = Null , sino que inmediatamente caerá en el bloque catch . Por lo tanto, el analizador indicó una validación redundante.

Considere el siguiente ejemplo de advertencia con el código V3022 :

 private void btnCopyLink_Click(object sender, EventArgs e) { .... if (lvClipboardFormats.SelectedItems.Count == 0) { url = lvClipboardFormats.Items[0].SubItems[1].Text; } else if (lvClipboardFormats.SelectedItems.Count > 0) { url = lvClipboardFormats.SelectedItems[0].SubItems[1].Text; } .... } 

Advertencia de PVS-Studio : V3022 [CWE-571] La expresión 'lvClipboardFormats.SelectedItems.Count> 0' siempre es verdadera. AfterUploadForm.cs 155

Veamos la segunda expresión condicional. Allí verificamos el valor de la propiedad Count de solo lectura. Esta propiedad muestra el número de elementos en una instancia de la colección SelectedItems . La condición se cumple solo si la propiedad Count es mayor que cero. Todo estaría bien, pero solo en la declaración externa si la Cuenta ya está marcada. Una instancia de la colección SelectedItems no puede tener el número de elementos menor que cero, por lo tanto, Count toma un valor igual a cero o mayor que cero. Como ya hemos realizado una verificación en la primera declaración if de que Count es cero, y resultó ser falsa, no tiene sentido escribir otra verificación en la rama else de que Count es mayor que cero.

El último ejemplo del número de error V3022 es el siguiente fragmento de código:

 private void DrawCursorGraphics(Graphics g) { .... int cursorOffsetX = 10, cursorOffsetY = 10, itemGap = 10, itemCount = 0; Size totalSize = Size.Empty; int magnifierPosition = 0; Bitmap magnifier = null; if (Options.ShowMagnifier) { if (itemCount > 0) totalSize.Height += itemGap; .... } .... } 

Advertencia de PVS-Studio : la expresión V3022 'itemCount> 0' siempre es falsa. RegionCaptureForm.cs 1100.

El analizador notó que la condición itemCount> 0 siempre será falsa, ya que se realiza una declaración un poco más alta y la variable itemCount se establece en cero al mismo tiempo. Hasta la misma condición, esta variable no se usa en ninguna parte y no cambia, por lo tanto, el analizador llegó a la conclusión correcta sobre la expresión condicional, cuyo valor siempre es falso.

Bueno, veamos ahora algo realmente interesante.

La mejor manera de entender el error es visualizarlo.

Nos parece que se encontró un error bastante interesante en este lugar:

 public static void Pixelate(Bitmap bmp, int pixelSize) { .... float r = 0, g = 0, b = 0, a = 0; float weightedCount = 0; for (int y2 = y; y2 < yLimit; y2++) { for (int x2 = x; x2 < xLimit; x2++) { ColorBgra color = unsafeBitmap.GetPixel(x2, y2); float pixelWeight = color.Alpha / 255; r += color.Red * pixelWeight; g += color.Green * pixelWeight; b += color.Blue * pixelWeight; a += color.Alpha * pixelWeight; weightedCount += pixelWeight; } } .... ColorBgra averageColor = new ColorBgra((byte)(b / weightedCount), (byte)(g / weightedCount), (byte)(r / weightedCount), (byte)(a / pixelCount)); .... } 

No quiero revelar de inmediato todas las tarjetas y mostrar lo que nuestro analizador encontró aquí, así que pospongamos este momento por un momento.

Por el nombre del método, es fácil adivinar lo que hace: envía una imagen o un fragmento de la imagen como entrada y realiza su pixelación. El código del método es bastante largo, por lo que no lo daremos aquí en su totalidad, sino que simplemente trataremos de explicar su algoritmo y explicar qué tipo de error PVS-Studio encontró aquí.

Este método acepta dos parámetros como entrada: un objeto de tipo Bitmap y un valor de tipo int , que indica el tamaño de los píxeles. El algoritmo de operación es bastante simple:

1) Rompemos el fragmento de la imagen recibida en la entrada en cuadrados con un lado igual al tamaño de la pixelación. Por ejemplo, si tenemos un tamaño de pixelización de 15, obtenemos un cuadrado que contiene 15x15 = 225 píxeles.

2) Luego, rodeamos cada píxel en este cuadrado y acumulamos los valores de los campos Rojo , Verde , Azul y Alfa en variables intermedias, y previamente multiplicamos el valor de color correspondiente y el valor del canal alfa por pixelWeight , obtenido al dividir el valor Alfa por 255 (la variable Alfa tiene tipo byte ). Además, al atravesar píxeles, sumamos los valores registrados en pixelWeight en una variable llamada weightedCount .

El fragmento de código que realiza los pasos anteriores es el siguiente:

 ColorBgra color = unsafeBitmap.GetPixel(x2, y2); float pixelWeight = color.Alpha / 255; r += color.Red * pixelWeight; g += color.Green * pixelWeight; b += color.Blue * pixelWeight; a += color.Alpha * pixelWeight; weightedCount += pixelWeight; 

Por cierto, tenga en cuenta que si el valor de la variable Alpha es cero, entonces pixelWeight no agregará ningún valor a la variable weightedCount para este píxel. Necesitaremos esto en el futuro.

3) Después de haber pasado por alto todos los píxeles en el cuadrado actual, podemos hacer el color general "promedio" para este cuadrado. El código que realiza estas acciones es el siguiente:

 ColorBgra averageColor = new ColorBgra((byte)(b / weightedCount), (byte)(g / weightedCount), (byte)(r / weightedCount), (byte)(a / pixelCount)); 

4) Ahora que tenemos el color final y lo escribimos en la variable averageColor , podemos volver alrededor de cada píxel en el cuadrado y asignarle un valor de averageColor .

5) Volvemos al paso 2, siempre que haya cuadrados en bruto.

Una vez más, observamos que la variable weightedCount no es igual al número de todos los píxeles al cuadrado. Por ejemplo, si se produce un píxel absolutamente transparente en la imagen (el valor es cero en el canal alfa), la variable pixelWeight será cero para este píxel ( 0/255 = 0), por lo tanto, este píxel no contribuirá a la formación del valor de la variable weightedCount . Esto es lógico: no tiene sentido tener en cuenta los colores de un píxel absolutamente transparente.

Todo parece bastante razonable: la pixelación debería funcionar correctamente. Y realmente funciona bien. Eso no es para imágenes png que tienen píxeles con valores en el canal alfa inferiores a 255 y desiguales a cero. Presta atención a la imagen pixelada a continuación:

Cuadro 3


¿Viste pixelación? Y no lo somos. Bueno, ahora revelemos esta pequeña intriga y expliquemos dónde se oculta exactamente el error en este método. El error se deslizó en la cadena de cálculo de la variable pixelWeight :

 float pixelWeight = color.Alpha / 255; 

El hecho es que el autor del código, al declarar la variable pixelWeight como flotante , implicó que al dividir el campo Alfa entre 255, además de cero y uno, se deben obtener números fraccionarios. Aquí es donde radica el problema, ya que la variable Alpha es de tipo byte , y al dividirla por 255 obtenemos un valor entero, y solo entonces se convertirá implícitamente en flotante , por lo tanto, la parte fraccional se pierde.

La incapacidad de pixelizar imágenes PNG que tienen cierto grado de transparencia es fácil de explicar. Dado que los valores del canal alfa para estos píxeles se encuentran en el rango 0 <Alfa <255, al dividir la variable Alfa entre 255, siempre obtendremos 0. Por lo tanto, los valores de las variables pixelWeight , r , g , b , a , weightedCount también son siempre será cero Como resultado, nuestro color promedio averageColor tendrá valores cero en todos los canales: rojo - 0, azul - 0, verde - 0, alfa - 0. Al llenar el cuadrado con este color, no cambiamos el color original de los píxeles, ya que averageColor es absolutamente transparente . Para corregir este error, solo necesita emitir explícitamente el campo Alfa a tipo flotante . La línea de código corregida puede verse así:

 float pixelWeight = (float)color.Alpha / 255; 

Y es hora de citar el mensaje que PVS-Studio dio al código incorrecto:

Advertencia de PVS-Studio : V3041 [CWE-682] La expresión se convirtió implícitamente de tipo 'int' a tipo 'flotante'. Considere utilizar un molde de tipo explícito para evitar la pérdida de una parte fraccional. Un ejemplo: doble A = (doble) (X) / Y;. ImageHelpers.cs 1119.

Y para comparar, damos una captura de pantalla de una imagen verdaderamente pixelada obtenida en una versión fija de la aplicación:

Cuadro 6

Potencial NullReferenceException

 public static bool AddMetadata(Image img, int id, string text) { .... pi.Value = bytesText; if (pi != null) { img.SetPropertyItem(pi); return true; } .... } 

Advertencia de PVS-Studio: V3095 [CWE-476] El objeto 'pi' se usó antes de que se verificara como nulo. Verifique las líneas: 801, 803. ImageHelpers.cs 801

Este fragmento de código muestra que su autor esperaba que la variable pi fuera nula , razón por la cual la verificación pi! = Null se realiza antes de llamar al método SetPropertyItem . Es extraño que antes de esta comprobación, se asigne una matriz de bytes a la propiedad pi.Value , porque si pi es nulo , se generará una excepción de tipo NullReferenceException .

Una situación similar se vio en otra parte:

 private static void Task_TaskCompleted(WorkerTask task) { .... task.KeepImage = false; if (task != null) { if (task.RequestSettingUpdate) { Program.MainForm.UpdateCheckStates(); } .... } .... } 

Advertencia de PVS-Studio: V3095 [CWE-476] El objeto 'tarea' se usó antes de que se verificara como nulo. Líneas de verificación: 268, 270. TaskManager.cs 268

PVS-Studio encontró otro error similar. El significado sigue siendo el mismo, por lo que no hay una gran necesidad de dar un fragmento de código, nos restringimos al mensaje del analizador.

Advertencia de PVS-Studio: V3095 [CWE-476] El objeto 'Config.PhotobucketAccountInfo' se usó antes de que se verificara como nulo. Líneas de verificación: 216, 219. UploadersConfigForm.cs 216

El mismo valor de retorno

Se descubrió un fragmento de código sospechoso en el método EvalWindows de la clase WindowsList , que devuelve verdadero en cualquier circunstancia:

 public class WindowsList { public List<IntPtr> IgnoreWindows { get; set; } .... public WindowsList() { IgnoreWindows = new List<IntPtr>(); } public WindowsList(IntPtr ignoreWindow) : this() { IgnoreWindows.Add(ignoreWindow); } .... private bool EvalWindows(IntPtr hWnd, IntPtr lParam) { if (IgnoreWindows.Any(window => hWnd == window)) { return true; // <= } windows.Add(new WindowInfo(hWnd)); return true; // <= } } 

Advertencia de PVS-Studio: V3009 Es extraño que este método siempre devuelva el mismo valor de 'verdadero'. WindowsList.cs 82

Parece lógico que si se encuentra un puntero con el mismo valor que hWnd en la lista con el nombre IgnoreWindows , entonces el método debería devolver falso .

La lista IgnoreWindows se puede completar llamando al constructor WindowsList (IntPtr ignoreWindow) o directamente a través del acceso a la propiedad, ya que es pública. De una forma u otra, según Visual Studio, en este momento en el código esta lista no está llena de ninguna manera. Este es otro lugar extraño de este método.

Llamada insegura a los controladores de eventos

 protected void OnNewsLoaded() { if (NewsLoaded != null) { NewsLoaded(this, EventArgs.Empty); } } 

Advertencia de PVS-Studio: V3083 [CWE-367] La ​​invocación insegura del evento 'NewsLoaded', NullReferenceException es posible. Considere asignar un evento a una variable local antes de invocarlo. NewsListControl.cs 111

En este caso, puede ocurrir la siguiente situación desagradable: después de verificar la variable NewsLoaded para determinar la desigualdad nula , el método que procesa el evento puede darse de baja, por ejemplo, en otro hilo, y cuando ingresamos al cuerpo de la instrucción if condicional, la variable NewsLoaded ya estará es igual a nulo Intentar llamar a los suscriptores en un evento NewsLoaded que sea nulo arrojará una NullReferenceException . Es mucho más seguro usar el operador condicional nulo y reescribir el código anterior de la siguiente manera:

 protected void OnNewsLoaded() { NewsLoaded?.Invoke(this, EventArgs.Empty); } 

El analizador indicó 68 lugares más similares. No los describiremos aquí: el patrón de la llamada del evento en ellos es similar.

Devolver nulo de ToString

No hace mucho tiempo, por el interesante artículo de un colega , descubrí que Microsoft no recomienda devolver nulo de un método ToString anulado. PVS-Studio es muy consciente de esto:

 public override string ToString() { lock (loggerLock) { if (sbMessages != null && sbMessages.Length > 0) { return sbMessages.ToString(); } return null; } } 

Advertencia de PVS-Studio: V3108 No se recomienda devolver 'nulo' del método 'ToSting ()'. Logger.cs 167

¿Por qué apropiado si no se usa?

 public SeafileCheckAccInfoResponse GetAccountInfo() { string url = URLHelpers.FixPrefix(APIURL); url = URLHelpers.CombineURL(APIURL, "account/info/?format=json"); .... } 

Advertencia de PVS-Studio: V3008 A la variable 'url' se le asignan valores dos veces seguidas. Quizás esto sea un error. Líneas de verificación: 197, 196. Seafile.cs 197

Como puede ver en el ejemplo, al declarar la variable url , se le asigna un valor devuelto por el método FixPrefix . En la siguiente línea, "trituramos" el valor resultante, incluso sin usarlo en ningún lado. Obtenemos algo similar al "código muerto": hace el trabajo, no afecta el resultado final. Este error es probablemente el resultado de copiar y pegar, ya que dichos fragmentos de código se encuentran en 9 métodos más.
Por ejemplo, damos dos métodos con una primera línea similar:

 public bool CheckAuthToken() { string url = URLHelpers.FixPrefix(APIURL); url = URLHelpers.CombineURL(APIURL, "auth/ping/?format=json"); .... } .... public bool CheckAPIURL() { string url = URLHelpers.FixPrefix(APIURL); url = URLHelpers.CombineURL(APIURL, "ping/?format=json"); .... } 

Total


Como podemos ver, la complejidad de configurar la verificación automática por parte del analizador no depende del sistema de CI seleccionado: en solo 15 minutos y con unos pocos clics del mouse, configuramos la verificación de nuestro código de proyecto con un analizador estático.

En conclusión, le sugerimos que descargue y pruebe el analizador en sus proyectos.



Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace a la traducción: Oleg Andreev, Ilya Gainulin. PVS-Studio en las nubes: Azure DevOps .

Source: https://habr.com/ru/post/467359/


All Articles