Comprobación del OpenCvSharp Wrapper para OpenCV con PVS-Studio

Figura 2

OpenCV es una biblioteca de código abierto de visión artificial y algoritmos de procesamiento de imágenes y algoritmos numéricos de uso general. La biblioteca es bien conocida entre los desarrolladores de C ++. Además de C ++, también hay versiones para Python, Java, Ruby, Matlab, Lua y otros lenguajes. Como C #, que es el lenguaje en el que me especializo, no está en esa lista, elegí OpenCvSharp, un contenedor C # de OpenCV, para verificarlo con PVS-Studio. Los resultados de esa verificación se analizan en este artículo.

Introduccion


Antes de formar parte del equipo de PVS-Studio, había estado involucrado en la fabricación de robots para presentar en exposiciones. Mis tareas incluían el trabajo de reparación más básico (las fallas mayores fueron manejadas por otra persona), así como el desarrollo de software y utilidades de todo tipo.

Figura 1

Yo, cansado y nuevo en la ciudad, con un robot KIKI recién desempaquetado.

Por cierto, la parte de desarrollo fue bastante divertida. Cada vez que uno de nosotros tenía una idea sobre alguna nueva forma de sorprender a los visitantes de la exposición, la sacamos a discusión y, si a todos les gustaba, nos poníamos a trabajar. Una vez se nos ocurrió hacer un robot que pudiera reconocer un rostro humano y responder con un discurso de bienvenida.

Busqué en Google alguna biblioteca para mis necesidades y me topé con OpenCV, una biblioteca de algoritmos de visión por computadora. Pero me decepcioné muy pronto cuando descubrí que OpenCV se implementó en C ++. Mi conocimiento de C ++, que había estudiado en la universidad, obviamente no era suficiente. Así que busqué en Google un poco más y encontré OpenCvSharp, un contenedor de la biblioteca para C #, que es el lenguaje en el que me especializo. Ha pasado aproximadamente medio año desde entonces, el programa está escrito desde hace mucho tiempo y está en uso, y ahora finalmente decidí mirar "bajo el capó" de OpenCvSharp y escanear su código fuente con el analizador estático PVS-Studio.

El proyecto bajo análisis.


OpenCvSharp es un contenedor de OpenCV para usar en proyectos de C #. Por cierto, ya revisamos OpenCV en el pasado. Los puntos fuertes de OpenCvSharp son la gran colección de ejemplos de código, el soporte multiplataforma (se ejecuta en cualquier plataforma compatible con Mono) y la fácil instalación.

El contenedor es un pequeño proyecto de aproximadamente 112.200 líneas de código C # de largo. El 1,2% de estos son comentarios, que, debo decir, son sospechosamente pocos. Por otro lado, hay bastantes errores para un proyecto tan pequeño. Elegí más de 20 ejemplos para este artículo, pero el analizador encontró muchos más, que no son tan interesantes u obvios.

PVS-Studio


PVS-Studio es una herramienta para detectar errores y vulnerabilidades potenciales en el código fuente de programas escritos en C, C ++, C # y Java. Se ejecuta en Windows, Linux y macOS. Además de código inalcanzable, errores de programación y errores tipográficos, PVS-Studio, como ya se mencionó, es capaz de detectar posibles problemas de seguridad. Por lo tanto, se puede ver como una herramienta de prueba de seguridad de aplicaciones estáticas (SAST).

Las advertencias más interesantes.


Lo que hace que el método WriteableBitmapConverter sea especial es que activó cuatro advertencias del mismo tipo a la vez:

  • V3005 La variable 'opticalChannels [PixelFormats.Indexed1]' se asigna a sí misma. WriteableBitmapConverter.cs 22
  • V3005 La variable 'opticalChannels [PixelFormats.Indexed8]' se asigna a sí misma. WriteableBitmapConverter.cs 23
  • V3005 La variable 'opticalTypes [PixelFormats.Indexed1]' se asigna a sí misma. WriteableBitmapConverter.cs 50
  • V3005 La variable 'opticalTypes [PixelFormats.Indexed8]' se asigna a sí misma. WriteableBitmapConverter.cs 51

static WriteableBitmapConverter() { optimumChannels = new Dictionary <PixelFormat, int>(); optimumChannels[PixelFormats.Indexed1] = // <= optimumChannels[PixelFormats.Indexed8] = // <= optimumChannels[PixelFormats.Gray2] = optimumChannels[PixelFormats.Gray4] = optimumChannels[PixelFormats.Gray8] = optimumChannels[PixelFormats.Gray16] = optimumChannels[PixelFormats.Gray32Float] = optimumChannels[PixelFormats.Indexed1] = // <= optimumChannels[PixelFormats.Indexed2] = optimumChannels[PixelFormats.Indexed4] = optimumChannels[PixelFormats.Indexed8] = // <= .... optimumTypes = new Dictionary <PixelFormat, MatType>(); optimumTypes[PixelFormats.Indexed1] = // <= optimumTypes[PixelFormats.Indexed8] = // <= optimumTypes[PixelFormats.Gray2] = optimumTypes[PixelFormats.Gray4] = optimumTypes[PixelFormats.Gray8] = optimumTypes[PixelFormats.Indexed1] = // <= optimumTypes[PixelFormats.Indexed2] = optimumTypes[PixelFormats.Indexed4] = optimumTypes[PixelFormats.Indexed8] = // <= optimumTypes[PixelFormats.BlackWhite] = .... } .... public static class PixelFormats { .... public static PixelFormat Indexed8 { get; } .... public static PixelFormat Indexed1 { get; } .... } 

La clase PixelFormats se define en el espacio de nombres System.Windows.Media y es una colección de varios formatos de píxeles. El analizador señala que los elementos opticalChannels [PixelFormats.Indexed1] y opticalChannels [PixelFormats.Indexed8] son valores asignados por segunda vez en el método WriteableBitmapConverter , lo que no tiene ningún sentido. No está claro si esto es solo un error tipográfico o si el programador quiso decir algo más. Por cierto, este fragmento es un vívido ejemplo de cómo los analizadores estáticos pueden ser útiles: mirar un montón de líneas similares te hace menos enfocado, no es de extrañar que los errores tipográficos pasen desapercibidos a pesar de la revisión del código. Sin embargo, los analizadores estáticos no tienen problemas para mantener la atención y no necesitan descansar, por lo que pueden atrapar errores como ese sin esfuerzo.

Figura 5

Siente el poder del análisis estático.

Mensaje de diagnóstico de PVS-Studio : V3021 Hay dos declaraciones 'if' con expresiones condicionales idénticas. La primera instrucción 'if' contiene el método return. Esto significa que la segunda declaración 'if' no tiene sentido InputArray.cs 394

 private static MatType EstimateType(Type t) { .... if (t == typeof(Vec2b)) return MatType.CV_8UC2; if (t == typeof(Vec3b)) return MatType.CV_8UC3; if (t == typeof(Vec4b)) return MatType.CV_8UC4; if (t == typeof(Vec6b)) return MatType.CV_8UC(6); if (t == typeof(Vec2s)) // <= return MatType.CV_16SC2; .... if (t == typeof(Vec2s)) // <= return MatType.CV_32SC2; .... } 

Este error es algo similar al anterior. El desarrollador está comprobando la misma condición dos veces. No tiene sentido aquí como la rama entonces del "duplicado" si la declaración nunca se ejecutará porque:

  • si la primera condición es verdadera, el método regresará;
  • si la primera condición es falsa, la segunda también será falsa porque la variable que se verifica, t , no cambia entre las dos verificaciones.

Este código necesita revisión; Es muy probable que la segunda copia de Vec2s en realidad fuera otra variable.

Mensaje de diagnóstico de PVS-Studio : V3010 Se requiere el valor de retorno de la función 'ToString'. ImgProcTest.cs 80

 public static RectanglesIntersectTypes RotatedRectangleIntersection(RotatedRect rect1, RotatedRect rect2, out Point2f[] intersectingRegion) { using (var intersectingRegionVec = new VectorOfPoint2f()) { int ret = NativeMethods .imgproc_rotatedRectangleIntersection_vector( rect1, rect2, intersectingRegionVec.CvPtr); intersectingRegion = intersectingRegionVec.ToArray(); return (RectanglesIntersectTypes) ret; } } public void RotatedRectangleIntersectionVector() { var rr1 = new RotatedRect(new Point2f(100, 100), new Size2f(100, 100), 45); var rr2 = new RotatedRect(new Point2f(130, 100), new Size2f(100, 100), 0); Cv2.RotatedRectangleIntersection(rr1, rr2, out var intersectingRegion); .... intersectingRegion.ToString(); } 

Se accede al método RotatedRectangleIntersection a través del parámetro intersectingRegion y devuelve una matriz de elementos de tipo Point2f . Una vez que intersectionRegion se ha llenado de valores, se llama al método ToString () en la matriz. Esto no afecta los elementos de la matriz de ninguna manera y no se realiza ningún trabajo útil en la última línea, por lo que sería justo suponer que el desarrollador simplemente se olvidó de eliminar esa pieza.

Mensajes de diagnóstico de PVS-Studio:

  • V3021 Hay dos declaraciones 'if' con expresiones condicionales idénticas. La primera instrucción 'if' contiene el método return. Esto significa que la segunda instrucción 'if' no tiene sentido Cv2_calib3d.cs 1370
  • V3022 La expresión 'objectPoints == null' siempre es falsa. Cv2_calib3d.cs 1372

 public static double CalibrateCamera(....) { if (objectPoints == null) throw new ArgumentNullException(nameof(objectPoints)); if (objectPoints == null) throw new ArgumentNullException(nameof(objectPoints)); .... } 

Hemos clonado el código aquí, de ahí las dos advertencias. El primero dice que ambas declaraciones if verifican la misma condición. Si esa condición es verdadera, el método regresará en la rama de la primera instrucción if . En consecuencia, la segunda condición siempre será falsa, que es lo que nos dice la segunda advertencia. Parece que el programador clonó ese fragmento usando copiar y pegar, pero olvidó cambiarlo.

Figura 6


Lindo copiar y pegar.

Otras advertencias de este tipo:

  • V3021 Hay dos declaraciones 'if' con expresiones condicionales idénticas. La primera instrucción 'if' contiene el método return. Esto significa que la segunda declaración 'if' no tiene sentido Cv2_calib3d.cs 1444
  • V3022 La expresión 'objectPoints == null' siempre es falsa. Cv2_calib3d.cs 1446

Mensaje de diagnóstico de PVS-Studio: V3022 La expresión 'label == MarkerValue' siempre es falsa. Labeller.cs 135

 internal static class Labeller { .... private const int MarkerValue = -1; public static int Perform(Mat img, CvBlobs blobs) { .... int label = 0; int lastLabel = 0; CvBlob lastBlob = null; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { if (imgIn[x + y * step] == 0) continue; bool labeled = labels[y, x] != 0; if (....) { labeled = true; // Label contour. label++; if (label == MarkerValue) // <= throw new Exception(); .... } .... } .... } } } 

Una variable llamada etiqueta se crea y se inicializa a 0. Si cierta condición es verdadera, se incrementará en una. Además, esta variable nunca se decrementa en este fragmento. Por lo tanto, verificar la constante -1, como en la línea señalada por el analizador, no tiene ningún sentido.

Mensaje de diagnóstico de PVS-Studio: V3038 El argumento se pasó al método varias veces. Es posible que se pase otro argumento en su lugar. Cv2_photo.cs 124

 public static void FastNlMeansDenoisingMulti(....) { .... NativeMethods.photo_fastNlMeansDenoisingMulti( srcImgPtrs, srcImgPtrs.Length, dst.CvPtr, imgToDenoiseIndex, templateWindowSize, h, templateWindowSize, searchWindowSize); .... } 

Para comprender lo que nos dice el analizador, echemos un vistazo a los parámetros del método photo_fastNlMeansDenoisingMulti :

 public static extern void photo_fastNlMeansDenoisingMulti( IntPtr[] srcImgs, int srcImgsLength, IntPtr dst, int imgToDenoiseIndex, int temporalWindowSize, float h, int templateWindowSize, int searchWindowSize) 

Simplifiquemos aún más para hacerlo completamente sencillo. Compara estas líneas:

 NativeMethods.photo_fastNlMeansDenoisingMulti( .... templateWindowSize, .... templateWindowSize, ....); public static extern void photo_fastNlMeansDenoisingMulti( .... int temporalWindowSize, .... int templateWindowSize, ....) 

La variable templateWindowSize se declara dos veces, pero la primera vez que se menciona debería ser la declaración de temporalWindowSize . Otra cosa que no le gustó al analizador es que el valor de temporalWindowSize no se utiliza en absoluto en el método photo_fastNlMeansDenoisingMulti . Esta podría ser una decisión consciente, pero miraría más de cerca este código si fuera el autor.

Otras advertencias de este tipo:

  • V3038 El argumento se pasó al método varias veces. Es posible que se pase otro argumento en su lugar. Cv2_photo.cs 149
  • V3038 El argumento se pasó al método varias veces. Es posible que se pase otro argumento en su lugar. Cv2_photo.cs 180
  • V3038 El argumento se pasó al método varias veces. Es posible que se pase otro argumento en su lugar. Cv2_photo.cs 205

El siguiente ejemplo es algo similar al anterior.

Mensaje de diagnóstico de PVS-Studio: V3066 Posible orden incorrecto de argumentos pasados ​​al método 'calib3d_Rodrigues_MatToVec': 'matrixM.CvPtr' y 'vectorM.CvPtr'. Cv2_calib3d.cs 86

 public static void Rodrigues(double[,] matrix, out double[] vector, out double[,] jacobian) { .... using (var jacobianM = new Mat<double>()) { NativeMethods.calib3d_Rodrigues_MatToVec (matrixM.CvPtr, vectorM.CvPtr, jacobianM.CvPtr); .... } } 

Veamos los parámetros del método calib3d_Rodrigues_MatToVec :

 public static extern void calib3d_Rodrigues_MatToVec( IntPtr vector, IntPtr matrix, IntPtr jacobian) 

Parece que el método calib3d_Rodrigues_MatToVec se llama con los argumentos matrixM.CvPtr y vectorM.CvPtr intercambiados accidentalmente. Los autores deben verificar este fragmento: puede haber un error que obstaculice los cálculos correctos.

Mensaje de diagnóstico de PVS-Studio: V3063 Una parte de la expresión condicional siempre es falsa si se evalúa: datos == nulo. Mat.cs 3539

 private void CheckArgumentsForConvert(....) { .... if (data == null) throw new ArgumentNullException(nameof(data)); MatType t = Type(); if (data == null || (data.Length * dataDimension) // <= (data.Length * dataDimension) % t.Channels != 0) .... } 

El analizador informa que los datos de la segunda verificación == nulo nunca serán verdaderos porque si los datos son iguales a nulos en la primera condición, se generará una excepción y la ejecución nunca alcanzará la segunda verificación.

Figura 7

Sé que estás cansado, pero casi hemos terminado.

Mensaje de diagnóstico de PVS-Studio: V3127 Se encontraron dos fragmentos de código similares. Quizás, este es un error tipográfico y la variable 'ventana' debería usarse en lugar de 'src2' Cv2_imgproc.cs 1547

 public static Point2d PhaseCorrelateRes(....) { if (src1 == null) throw new ArgumentNullException(nameof(src1)); if (src2 == null) throw new ArgumentNullException(nameof(src2)); if (window == null) throw new ArgumentNullException(nameof(src2)); // <= .... } 

El analizador detectó un error tipográfico en este fragmento. Las variables se verifican para nulo y, si es verdadero, cada verificación arroja una excepción. Sin embargo, no funciona correctamente para la variable de ventana . Si su valor es igual a nulo , también se genera una excepción correspondiente pero con el texto incorrecto. No mencionará la ventana ; será src2 en su lugar. La condición aparentemente debería revisarse de la siguiente manera:

 if (window == null) throw new ArgumentNullException(nameof(window)); 

Mensaje de diagnóstico PVS-Studio: V3142 Código inalcanzable detectado. Es posible que haya un error presente. MatOfT.cs 873

Ahora, solo para variar, echemos un vistazo al caso en el que el analizador es técnicamente correcto sobre el código inalcanzable, pero en realidad no hay ningún error. Es una advertencia que se puede llamar verdadera y falsa al mismo tiempo.

 public new Mat<TElem> SubMat(params Range[] ranges) { Mat result = base.SubMat(ranges); return Wrap(result); } 

El analizador nos dice que la declaración de devolución es inalcanzable. Veamos el cuerpo del método SubMat para ver si el analizador dice la verdad.

 public Mat SubMat(params Range[] ranges) { throw new NotImplementedException(); /* if (ranges == null) throw new ArgumentNullException(); ThrowIfDisposed(); CvSlice[] slices = new CvSlice[ranges.Length]; for (int i = 0; i < ranges.Length; i++) { slices[i] = ranges[i]; } IntPtr retPtr = NativeMethods.core_Mat_subMat1(ptr, ranges.Length, ranges); Mat retVal = new Mat(retPtr); return retVal;*/ } 

Como puede ver, la función está actualmente incompleta y siempre arrojará una excepción. El analizador es absolutamente correcto al señalar el código inalcanzable, pero no es un error genuino.

Los siguientes tres defectos son del mismo tipo, pero son tan geniales que no pude evitar incluir los tres.

Mensaje de diagnóstico de PVS-Studio: V3022 La expresión 'String.IsNullOrEmpty ("winName")' siempre es falsa. Cv2_highgui.cs 46

 public static void DestroyWindow(string winName) { if (String.IsNullOrEmpty("winName")) .... } 

Mensaje de diagnóstico de PVS-Studio: V3022 La expresión 'string.IsNullOrEmpty ("fileName")' siempre es falsa. FrameSource.cs 37

 public static FrameSource CreateFrameSource_Video(string fileName) { if (string.IsNullOrEmpty("fileName")) .... } 

Mensaje de diagnóstico de PVS-Studio: V3022 La expresión 'string.IsNullOrEmpty ("fileName")' siempre es falsa. FrameSource.cs 53

 public static FrameSource CreateFrameSource_Video_CUDA(string fileName) { if (string.IsNullOrEmpty("fileName")) .... } 

A veces las advertencias V3022 (sobre expresiones siempre verdaderas / falsas) apuntan a errores realmente extraños o divertidos. Los tres ejemplos anteriores tienen el mismo error en ellos. El método tiene un parámetro de tipo cadena cuyo valor debe verificarse. Sin embargo, lo que se verifica es un literal de cadena cuyo texto es el nombre de la variable, es decir, el nombre de la variable entre comillas.

Figura 18

El programador debe haber escrito un bloque de código defectuoso una vez y luego clonarlo mediante copiar y pegar.

Conclusión


Los desarrolladores de OpenCvSharp han hecho un trabajo grande e importante y, como usuario de su biblioteca, estoy totalmente agradecido por eso. Gracias chicos

Pero ahora que me he convertido en parte del equipo de PVS-Studio y he visto el código de la biblioteca, debo decir que el aspecto de la calidad no recibió la atención adecuada. El proyecto no parece que se verifique regularmente con analizadores estáticos, y muchos de los errores aparentemente se corrigen utilizando técnicas más costosas (como pruebas o comentarios de los usuarios), y algunos de los errores simplemente siguen viviendo dentro del código y son ellos que atrapamos con nuestro analizador. Este tema se discute con más detalle en esta pequeña publicación sobre la filosofía del análisis estático.

Dado que OpenCvSharp es de código abierto y está disponible gratuitamente en GitHub, sus autores pueden usar una de las opciones de licencia gratuita para que PVS-Studio comience a usarla de manera regular.

Gracias por leer No dude en descargar una copia de prueba de PVS-Studio para verificar sus propios proyectos.

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


All Articles