Verificando o OpenCvSharp Wrapper para OpenCV com PVS-Studio

Figura 2

OpenCV é uma biblioteca de código aberto de visão computacional e algoritmos de processamento de imagem e algoritmos numéricos de uso geral. A biblioteca é bem conhecida entre os desenvolvedores de C ++. Além do C ++, também existem versões para Python, Java, Ruby, Matlab, Lua e outras linguagens. Como o C #, que é o idioma em que me especializo, não está nessa lista, escolhi o OpenCvSharp, um invólucro em C # do OpenCV, para verificá-lo com o PVS-Studio. Os resultados dessa verificação são discutidos neste artigo.

1. Introdução


Antes de me tornar parte da equipe do PVS-Studio, participei da fabricação de robôs para apresentar em exposições. Minhas tarefas incluíam o trabalho de reparo mais básico (falhas importantes foram tratadas por outra pessoa), bem como o desenvolvimento de software e utilitários de todos os tipos.

Figura 1

Eu, cansado e novo na cidade, com um robô KIKI recém-desembalado.

A propósito, a parte do desenvolvimento foi bem engraçada. Cada vez que um de nós tinha uma idéia de uma nova maneira de surpreender os visitantes da exposição, nós o trazíamos para discussão e, se todo mundo gostasse, começávamos a trabalhar. Uma vez, ocorreu-nos criar um robô capaz de reconhecer um rosto humano e responder com um discurso de boas-vindas.

Pesquisei algumas bibliotecas para minhas necessidades e me deparei com o OpenCV, uma biblioteca de algoritmos de visão computacional. Mas fiquei decepcionado logo que descobri que o OpenCV foi implementado em C ++. Meu conhecimento de C ++, que eu havia estudado na faculdade, obviamente não era suficiente. Pesquisei um pouco mais no Google e encontrei o OpenCvSharp, um invólucro da biblioteca para C #, que é o idioma em que me especializo. Faz cerca de meio ano desde então, o programa há muito escrito e em uso, e agora eu finalmente decidi espiar "por baixo" do OpenCvSharp e digitalizar seu código-fonte com o analisador estático PVS-Studio.

O projeto em análise


O OpenCvSharp é um invólucro do OpenCV para uso em projetos em C #. A propósito, já verificamos o OpenCV no passado. Os pontos fortes do OpenCvSharp são a grande coleção de exemplos de código, o suporte a várias plataformas (ele roda em qualquer plataforma suportada pelo Mono) e a fácil instalação.

O wrapper é um projeto pequeno com cerca de 112.200 linhas de código C #. 1,2% destes são comentários, que, devo dizer, são suspeitosamente poucos. Por outro lado, existem alguns bugs para um projeto tão pequeno. Escolhi mais de 20 exemplos para este artigo, mas o analisador realmente encontrou muitos outros, o que não é interessante ou óbvio.

PVS-Studio


O PVS-Studio é uma ferramenta para detectar bugs e possíveis vulnerabilidades no código fonte de programas escritos em C, C ++, C # e Java. É executado no Windows, Linux e macOS. Além do código inacessível, erros de programação e erros de digitação, o PVS-Studio, como já foi mencionado, é capaz de detectar possíveis problemas de segurança. Portanto, ele pode ser visto como uma ferramenta SAST (Static Application Security Testing).

Os avisos mais interessantes


O que torna o método WriteableBitmapConverter especial é que ele disparou quatro avisos do mesmo tipo ao mesmo tempo:

  • V3005 A variável 'optimumChannels [PixelFormats.Indexed1]' é atribuída a si mesma. WriteableBitmapConverter.cs 22
  • V3005 A variável 'optimumChannels [PixelFormats.Indexed8]' é atribuída a si mesma. WriteableBitmapConverter.cs 23
  • V3005 A variável 'optimumTypes [PixelFormats.Indexed1]' é atribuída a si mesma. WriteableBitmapConverter.cs 50
  • V3005 A variável 'optimumTypes [PixelFormats.Indexed8]' é atribuída a si mesma. 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; } .... } 

A classe PixelFormats é definida no espaço para nome System.Windows.Media e é uma coleção de vários formatos de pixel. O analisador ressalta que os elementos optimumChannels [PixelFormats.Indexed1] e optimumChannels [PixelFormats.Indexed8] recebem valores pela segunda vez no método WriteableBitmapConverter , o que não faz sentido. Não está claro se isso é apenas um erro de digitação ou se o programador quis dizer outra coisa. A propósito, esse trecho é um exemplo vívido de como os analisadores estáticos podem ser úteis: observar várias linhas semelhantes o deixa menos focado - não é de admirar que os erros de digitação passem despercebidos, apesar da revisão do código. Os analisadores estáticos, no entanto, não têm problemas para manter a atenção e não precisam de descanso, para que possam detectar bugs assim sem nenhum esforço.

Figura 5

Sinta o poder da análise estática.

Mensagem de diagnóstico do PVS-Studio : V3021 Existem duas instruções 'if' com expressões condicionais idênticas. A primeira instrução 'if' contém retorno de método. Isso significa que a segunda instrução 'if' não faz 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 bug é um pouco semelhante ao anterior. O desenvolvedor está verificando a mesma condição duas vezes. Não faz sentido aqui como o ramo então da instrução "duplicate" if nunca será executado porque:

  • se a primeira condição for verdadeira, o método retornará;
  • se a primeira condição for falsa, a segunda também será falsa porque a variável que está sendo verificada, t , não muda entre as duas verificações.

Esse código precisa ser revisado; é muito provável que a segunda cópia do Vec2s tenha realmente sido uma outra variável.

Mensagem de diagnóstico do PVS-Studio : V3010 O valor de retorno da função 'ToString' deve ser utilizado. 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(); } 

O método RotatedRectangleIntersection é acessado através do parâmetro intersectingRegion e retorna uma matriz de elementos do tipo Point2f . Depois que o intersectingRegion for preenchido com valores, o método ToString () é chamado na matriz. Isso não afeta os elementos da matriz de forma alguma e nenhum trabalho útil é realizado na última linha; portanto, seria justo supor que o desenvolvedor simplesmente se esqueceu de remover essa peça.

Mensagens de diagnóstico do PVS-Studio:

  • V3021 Existem duas instruções 'if' com expressões condicionais idênticas. A primeira instrução 'if' contém retorno de método. Isso significa que a segunda declaração 'if' não faz sentido Cv2_calib3d.cs 1370
  • A expressão V3022 'objectPoints == null' é sempre 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)); .... } 

Nós clonamos o código aqui, daí os dois avisos. O primeiro diz que ambas as instruções if verificam a mesma condição. Se essa condição for verdadeira, o método retornará no ramo da primeira instrução if . Consequentemente, a segunda condição será sempre falsa, que é o que o segundo aviso está nos dizendo. Parece que o programador clonou esse fragmento usando copiar e colar, mas esqueceu de alterá-lo.

Figura 6


Copiar e colar bonito.

Outros avisos deste tipo:

  • V3021 Existem duas instruções 'if' com expressões condicionais idênticas. A primeira instrução 'if' contém retorno de método. Isso significa que a segunda declaração 'if' não faz sentido Cv2_calib3d.cs 1444
  • A expressão V3022 'objectPoints == null' é sempre falsa. Cv2_calib3d.cs 1446

Mensagem de diagnóstico do PVS-Studio: A expressão V3022 'label == MarkerValue' é sempre 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(); .... } .... } .... } } } 

Uma variável chamada label é criada e inicializada como 0. Se uma determinada condição for verdadeira, ela será incrementada em uma. Além disso, essa variável nunca é diminuída nesse trecho. Portanto, verificar a constante -1, como na linha apontada pelo analisador, não faz sentido.

Mensagem de diagnóstico do PVS-Studio: V3038 O argumento foi passado para o método várias vezes. É possível que outro argumento seja passado. Cv2_photo.cs 124

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

Para entender o que o analisador está nos dizendo, vamos dar uma olhada nos parâmetros do 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) 

Vamos simplificá-lo ainda mais para torná-lo completamente direto. Compare estas linhas:

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

A variável templateWindowSize é declarada duas vezes, mas a primeira vez que é mencionada deve realmente ser a declaração de temporalWindowSize . Outra coisa que o analisador não gostou é que o valor de temporalWindowSize não é usado no método photo_fastNlMeansDenoisingMulti . Essa poderia ser uma decisão consciente, mas eu examinaria mais de perto esse código se eu fosse o autor.

Outros avisos deste tipo:

  • V3038 O argumento foi passado para o método várias vezes. É possível que outro argumento seja passado. Cv2_photo.cs 149
  • V3038 O argumento foi passado para o método várias vezes. É possível que outro argumento seja passado. Cv2_photo.cs 180
  • V3038 O argumento foi passado para o método várias vezes. É possível que outro argumento seja passado. Cv2_photo.cs 205

O próximo exemplo é um pouco semelhante ao anterior.

Mensagem de diagnóstico do PVS-Studio: V3066 Possível ordem incorreta de argumentos transmitida para o método 'calib3d_Rodrigues_MatToVec': 'matrixM.CvPtr' e '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); .... } } 

Vejamos os parâmetros do método calib3d_Rodrigues_MatToVec :

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

Parece que o método calib3d_Rodrigues_MatToVec é chamado com os argumentos matrixM.CvPtr e vectorM.CvPtr trocados acidentalmente. Os autores devem verificar este trecho: pode haver um erro que dificulta os cálculos corretos.

Mensagem de diagnóstico do PVS-Studio: V3063 Uma parte da expressão condicional é sempre falsa se for avaliada: data == null. 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) .... } 

O analisador relata que os dados da segunda verificação == null nunca serão verdadeiros, porque se os dados forem iguais a nulos na primeira condição, uma exceção será gerada e a execução nunca alcançará a segunda verificação.

Figura 7

Eu sei que você está cansado, mas estamos quase terminando.

Mensagem de diagnóstico do PVS-Studio: V3127 Dois fragmentos de código semelhantes foram encontrados. Talvez seja um erro de digitação e a variável 'window' deva ser usada em vez 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)); // <= .... } 

O analisador detectou um erro de digitação neste trecho. As variáveis ​​são verificadas como nulas e, se verdade, cada verificação gera uma exceção. No entanto, ele não funciona corretamente para a variável da janela . Se seu valor for igual a nulo , também será lançada uma exceção correspondente, mas com o texto incorreto. Não mencionará janela ; será src2 . Aparentemente, a condição deve ser revisada da seguinte maneira:

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

Mensagem de diagnóstico do PVS-Studio: V3142 Código inacessível detectado. É possível que haja um erro. MatOfT.cs 873

Agora, apenas para variar, vamos dar uma olhada no caso em que o analisador está tecnicamente correto quanto ao código inacessível, mas na verdade não há erro. É um aviso que pode ser chamado de verdadeiro e falso ao mesmo tempo.

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

O analisador nos diz que a declaração de retorno é inacessível. Vejamos o corpo do método SubMat para ver se o analisador está dizendo a verdade.

 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 você pode ver, a função está incompleta no momento e sempre lançará uma exceção. O analisador está absolutamente correto apontando o código inacessível - mas não é um bug genuíno.

Os próximos três defeitos são do mesmo tipo, mas são tão legais que não pude deixar de incluir os três.

Mensagem de diagnóstico do PVS-Studio: A expressão V3022 'String.IsNullOrEmpty ("winName")' é sempre falsa. Cv2_highgui.cs 46

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

Mensagem de diagnóstico do PVS-Studio: A expressão V3022 'string.IsNullOrEmpty ("fileName")' é sempre falsa. FrameSource.cs 37

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

Mensagem de diagnóstico do PVS-Studio: A expressão V3022 'string.IsNullOrEmpty ("fileName")' é sempre falsa. FrameSource.cs 53

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

Às vezes, os avisos do V3022 (sobre expressões sempre verdadeiras / falsas) apontam para erros realmente estranhos ou engraçados. Todos os três exemplos acima têm o mesmo erro neles. O método possui um parâmetro do tipo string cujo valor deve ser verificado. No entanto, o que é verificado é uma literal de cadeia de caracteres cujo texto é o nome da variável, ou seja, o nome da variável entre aspas.

Figura 18

O programador deve ter escrito um bloco de código com defeito uma vez e depois o clonado através de copiar e colar.

Conclusão


Os desenvolvedores do OpenCvSharp fizeram um trabalho grande e importante e, como usuário de sua biblioteca, sou totalmente grato por isso. Obrigado pessoal!

Mas agora que me tornei parte da equipe do PVS-Studio e vi o código da biblioteca, devo dizer que o aspecto da qualidade não recebeu a devida atenção. O projeto não parece ser um verificado regularmente com analisadores estáticos, e muitos dos bugs são aparentemente corrigidos usando técnicas mais caras (como testes ou feedback do usuário), e alguns deles continuam vivendo dentro do código e são eles. que pegamos com nosso analisador. Este assunto é discutido em mais detalhes neste pequeno post sobre a filosofia da análise estática.

Como o OpenCvSharp é de código aberto e disponível gratuitamente no GitHub, seus autores podem usar uma das opções de licenciamento gratuitas do PVS-Studio para começar a usá-lo regularmente.

Obrigado pela leitura. Não hesite em baixar uma cópia de avaliação do PVS-Studio para verificar seus próprios projetos.

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


All Articles