使用PVS-Studio通过OpenCV检查OpenCvSharp包装器

图2

OpenCV是计算机视觉算法,图像处理和具有开放源代码的通用数值算法的库,许多C ++开发人员都很熟悉。 除C ++外,还为Python,Java,Ruby,Matlab,Lua和其他语言开发了OpenCV。 由于在这些语言中没有我的主要语言C#,因此我决定关注C#下的OpenCvSharp-包装器库,并检查该项目。 可以在本文中找到这些内容。

引言


在加入PVS-Studio之前,我在展览中从事机器人技术的研究。 我的任务包括最基本的维修(如果发生重大故障,则将机器人交给另一个人),以及开发各种软件和实用程序。

图1

累了,最近刚到一个新城市,我和一个刚拆开的KIKI机器人一起。

说到发展。 真是有趣。 每当团队中的某个人想到一个主意时,还有什么会令展览的客人感到惊讶,我们将这个问题提出来进行一般性讨论;如果这个主意不错,我们便开始实施。 有一次,我们想到了制作一个能以热情洋溢的讲话回应人脸的生物。

在互联网上搜索了我需要的库之后,我遇到了OpenCV网站,即计算机视觉算法的库。 我很快就感到失望-OpenCV是用C ++实现的。 我对我从大学获得的收益的了解显然还不够。 因此,通过短暂的谷歌搜索,我遇到了OpenCvSharp-这个库的包装,在我的主要语言C#下。 从那时起已经过去了六个月,该程序已经编写并使用了很长时间,我决定进入OpenCvSharp的行列,并使用PVS-Studio静态分析器检查其源代码。

审核项目


OpenCvSharp是OpenCV的包装,用于在C#项目中使用该库。 顺便说一下, 我们还检查了 OpenCV库。 OpenCvSharp的优点包括大量的代码示例,跨平台(可以在Mono支持的任何平台上运行)和易于安装。

包装器是一个小项目,包含约112,200行C#代码。 在这些评论中,有1.2%是评论,顺便说一句,该评论很小。 但是对于这么小的项目,会有很多错误。 在这篇文章中,我写出了20多个错误,但是还有其他错误却不那么明显。

PVS-Studio代码分析器


PVS-Studio是用于检测用C,C ++,C#和Java编写的程序的源代码中的错误和潜在漏洞的工具。 在Windows,Linux和macOS上运行。 如上所述,除了无法实现的代码,错误和错别字之外,PVS-Studio还可检测潜在的漏洞。 因此,可以将其视为用于静态应用程序安全测试(静态应用程序安全测试,SAST)的工具。

检查分析器报告时引起注意的代码段


WriteableBitmapConverter方法通过四个相同类型的PVS-Studio警告立即引起注意:

  • V3005将'optimumChannels [PixelFormats.Indexed1]'变量分配给它自己。 WriteableBitmapConverter.cs 22
  • V3005将'optimumChannels [PixelFormats.Indexed8]'变量分配给它自己。 WriteableBitmapConverter.cs 23
  • V3005将'optimumTypes [PixelFormats.Indexed1]'变量分配给它自己。 WriteableBitmapConverter.cs 50
  • V3005将'optimumTypes [PixelFormats.Indexed8]'变量分配给它自己。 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; } .... } 

PixelFormats类在System.Windows.Media命名空间中定义,并且是各种像素格式的集合。 分析器引起注意的事实是WriteableBitmapConverter方法将值重新分配给optimumChannels [PixelFormats.Indexed1]optimumChannels [PixelFormats.Indexed8]元素 ,这没有实际意义。 目前尚不清楚这是简单的错字还是其他含义。 顺便说一下,这段代码清楚地展示了静态分析器的好处。 当您在眼前看到一堆相同类型的行时,您的眼睛开始“模糊”,并且您的注意力消散了-即使在经过代码审查后,错字也会潜入程序中,这也不足为奇。 而且,静态分析仪的注意力没有问题,也不需要休息,因此更容易发现此类错误。

图5

感受静态分析的力量。

PVS-Studio警告V3021有两个带有相同条件表达式的'if'语句。 第一个'if'语句包含方法return。 这意味着第二个'if'语句是没有意义的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; .... } 

该错误有点像上一个错误。 程序员提出了对相同条件进行两次检查的情况。 在这种情况下,这没有意义-然后将不会执行“重复” if语句的分支,因为:

  • 如果第一个条件表达式为true,则该方法将退出;否则,方法将退出。
  • 如果第一个条件为假,则第二个也将为假,因为要测试的变量-t-在条件表达式之间不改变。

开发人员应仔细检查这段代码。 代替第二变量Vec2s可能应该使用其他变量。

PVS-Studio警告V3010必须使用函数“ 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(); } 

RotatedRectangleIntersection方法通过intersectingRegion参数返回Point2f元素的数组。 程序值填充intersectingRegion之后,在此array上调用ToString()方法 对于数组的元素,此更改不会发生任何变化,最后一行也不会做任何有用的工作,因此有理由怀疑开发人员他只是忘记删除了它。

PVS-Studio警告

  • V3021有两个带有相同条件表达式的'if'语句。 第一个'if'语句包含方法return。 这意味着第二个'if'语句是毫无意义的Cv2_calib3d.cs 1370
  • V3022表达式'objectPoints == null'始终为false。 Cv2_calib3d.cs 1372

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

在这种情况下,一个代码片段被复制了,因此出现了两个警告。 第一个警告指示两个if语句具有相同的条件表达式。 如果此表达式为true,则该方法将从第一个if语句then-分支退出。 因此,第二个条件将始终为假,如以下警告所示。 显然,文本已被复制,但忘记更正了。

图6

可爱的复制粘贴。

其他类似的分析仪警告:

  • V3021有两个带有相同条件表达式的'if'语句。 第一个'if'语句包含方法return。 这意味着第二个'if'语句是毫无意义的Cv2_calib3d.cs 1444
  • V3022表达式'objectPoints == null'始终为false。 Cv2_calib3d.cs 1446

PVS-Studio 警告V3022表达式'label == MarkerValue'始终为false。 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(); .... } .... } .... } } } 

在这段代码中,创建的标签变量为零。 当满足特定条件时,可以将一个变量添加到该变量中。 在这种情况下,在代码中,变量标签的值不会向下变化。 在带有箭头的线中,将此变量与等于-1的常数进行比较,这没有实际意义。

PVS-Studio警告V3038参数多次传递给方法。 可能应改为传递其他参数。 Cv2_photo.cs 124

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

要了解分析器的含义,让我们看一下photo_fastNlMeansDenoisingMulti方法的参数:

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

进一步简化以使其变得显而易见。 比较这些行:

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

分析人员提请注意以下事实:开发人员两次使用templateWindowSize变量,尽管最有可能使用temporalWindowSize代替该变量的第一次提及。 还怀疑根本没有使用photo_fastNlMeansDenoisingMulti方法中的temporalWindowSize的值。 也许这是故意完成的,但是值得代替开发人员仔细研究此代码,其中是否存在错误?

类似的分析仪警告:

  • V3038参数多次传递给方法。 可能应改为传递其他参数。 Cv2_photo.cs 149
  • V3038参数多次传递给方法。 可能应改为传递其他参数。 Cv2_photo.cs 180
  • V3038参数多次传递给方法。 可能应改为传递其他参数。 Cv2_photo.cs 205

以下错误将与上一个错误类似。

警告PVS-StudioV3066传递给“ calib3d_Rodrigues_MatToVec”方法的参数的可能错误顺序:“ matrixM.CvPtr”和“ 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); .... } } 

让我们看一下参数calib3d_Rodrigues_MatToVec

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

也许当调用calib3d_Rodrigues_MatToVec方法时,参数matrixM.CvPtrvectorM.CvPtr混合了。 开发人员应仔细阅读此代码。 可能会出现错误,从而干扰正确的计算。

PVS-Studio警告V3063如果条件表达式的一部分被求值,则始终为false: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) .... } 

分析器指示第二个检查数据== null永远不会为true ,因为 如果在第一个条件下datanull ,则将引发异常,并且程序执行将无法进行第二次检查。

图7

我知道您已经累了,但所剩无几。

PVS-Studio警告V3127找到两个相似的代码片段。 也许,这是一个错字,应该使用“窗口”变量而不是“ 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)); // <= .... } 

然后分析仪发现了一个错字。 在代码的此部分,将检查变量的值是否为null ,如果满足此条件,则会为每个变量引发异常。 但是, window变量并不是那么简单。 如果此变量为null ,则也会为其生成一个异常,但是此异常的文本拼写错误。 变量窗口本身不会出现在此异常的文本中;而是在其中指示src2 。 显然,最后一个条件应该是这样的:

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

PVS-Studio警告V3142检测到无法访问的代码。 可能存在错误。 MatOfT.cs 873

现在,进行更改,让我们看一下在报告无法访问的代码时分析器实际上是正确的情况,但是没有错误。 可以说分析仪同时生成正确和错误的警告时就是这种情况。

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

分析器声称在这里无法返回return语句 。 若要进行检查,请查看SubMat方法的主体。

 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;*/ } 

如您所见,该函数尚未添加,并且始终会引发异常。 报告无法访问的代码时,分析仪是正确的。 但这不能称为真正的错误。

分析仪发现的以下三个错误属于同一类型,但是它们是如此酷,以至于我都无法将它们全部写出来。

PVS-Studio 警告V3022表达式'String.IsNullOrEmpty(“ winName”)'始终为false。 Cv2_highgui.cs 46

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

PVS-Studio 警告V3022表达式'string.IsNullOrEmpty(“ fileName”)'始终为false。 FrameSource.cs 37

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

PVS-Studio 警告V3022表达式'string.IsNullOrEmpty(“ fileName”)'始终为false。 FrameSource.cs 53

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

有时,在V3022分析仪的警告之后(表达式始终为true / false),确实有一些奇怪或有趣的事情。 在所有三种情况下,都观察到相同的情况。 方法代码具有类型为string的参数,必须检查其值。 但是,检查的不是变量的值,而是带有其名称的字符串文字,即 该名称被引用了。

图18

显然,开发人员密封了一次,并使用复制粘贴通过代码传播了此错误。

结论


OpenCvSharp开发人员已完成了重要且出色的工作。 作为这个库的用户,我非常感谢他们。 谢谢啦

但是,在PVS-Studio团队中研究代码时,我不得不承认其质量问题尚未得到充分解决。 在此项目中,很可能不定期使用静态代码分析器。 许多错误可以通过更昂贵的方法来纠正(例如,根据用户评论进行测试)。 而且有些通常可以在代码中保留很长时间,而我们只是找到了它们。 在有关使用静态分析方法论的哲学主题的简短注释中,将更详细地介绍此想法。

由于该项目是开放的,并且位于GitHub上,因此其开发人员有机会利用免费许可选项 PVS-Studio并开始定期应用分析。

谢谢您的关注。 使用试用版的PVS-Studio 下载并测试您的项目。



如果您想与讲英语的读者分享这篇文章,请使用以下链接:Ekaterina Nikiforova。 使用PVS-Studio检查OpenCv的OpenCvSharp包装

Source: https://habr.com/ru/post/zh-CN473476/


All Articles