不久前,基于机器学习的静态分析器DeepCode开始支持C和C ++项目的验证。现在,我们可以在实践中看到经典静态分析和基于机器学习的静态分析结果之间的差异。我们已经在文章“ 在静态分析程序源代码中使用机器学习 ”中提到了DeepCode 。很快,他们发表了文章“ DeepCode为C和C ++添加了基于AI的静态代码分析支持 ”,并宣布支持对用C和C ++编写的项目进行分析。为了查看DeepCode分析的结果,我决定检查PhysX项目。在此,我无意介绍此项目中由PVS-Studio或DeepCode发现的错误,但我只是想参加一些第三方项目,并查看其示例,即分析仪的工作方式。运行DeepCode分析非常简单。将自动开始分析发布在GitHub上的项目。尽管存在问题,但由于整个存储库是连续检查的,因此尽管其中包含各种项目,但对它们的警告最终却被混淆了。同样,分析速度表明未编译代码,并且许多错误可能会从分析器中隐藏。DeepCode的总体结果如下:这并不能完全反映操作的数量,因为它们被分组为一个警告,我将在后面说明。当然,很明显,DeepCode中的C / C ++静态分析是一项创新,并且它的研究才刚刚开始,但是我认为已经可以得出一些结论。开始检查DeepCode触发器时,我遇到了以下警告,分析器遇到的错误不少于31次:由于PVS-Studio具有类似的诊断,而且我在另一篇文章中研究了这种反应,所以这种反应引起了我的注意。当我第一次遇到他时,在我看来,很少有人会“手工”编写一个数学常数。PVS-Studio也发现了这样粗心的常量使用方法,但这种类型的尝试次数不超过31次,但有52次,几乎马上,有一瞬间机器学习不如经典方法。如果错误代码至少在某种程度上偏离了这种非常典型的错误,发现它将变得更加困难。对于经典的静态分析,无需多次发生错误-诊断开发人员只需为出现错误提供一般原则就足够了。此外,PVS-Studio诊断不仅可以作用于Pi编号或其他常用常数,还可以作用于特定的常数(例如,Landau-Ramanujan常数),由于使用稀少,即使在庞大的培训基础上也很难发现错误的用法。由于DeepCode严格使用3.1415形式的常量,因此我在代码中的某个位置添加了兴趣,将其添加到常量4个小数位(3.14159263),该操作消失了。PVS-Studio在舍入常数的各种选项上工作。包括对更改后的部分的响应:V624'3.14159263 '常量中可能存在打印错误。考虑使用<math.h>中的M_PI常量。Crab.cpp 219这里的要点并不在于这是一种可怕的错误/粗略取整/可能出现错字,而是它确认了对代码的培训将仅限于这种代码,并且如果发生某些情况脱离常规模式或仅在极少数情况下出现,错误或缺点可能会被忽略。让我们考虑另一种操作。DeepCode对以下代码发出了一条警告:bool Shader::loadShaderCode(const char* fragmentShaderCode, ....)
{
....
if (mFragmentShaderSource != NULL)
free(mFragmentShaderSource);
....
if (mFragmentShaderSource != NULL)
free(mFragmentShaderSource);
....
}
PVS-Studio对以下代码有更多投诉:- V809 Verifying that a pointer value is not NULL is not required. The 'if (mFragmentShaderSource != 0)' check can be removed. Shader.cpp 178
- V809 Verifying that a pointer value is not NULL is not required. The 'if (mFragmentShaderSource != 0)' check can be removed. Shader.cpp 194
- V774 The 'mFragmentShaderSource' pointer was used after the memory was released. Shader.cpp 194
- V586 The 'free' function is called twice for deallocation of the same memory space. Inspect the first argument. Check lines: 179, 195. Shader.cpp 195
您可能会认为DeepCode触发只是简单的触发,PVS-Studio产生了很多不必要的触发,但事实并非如此,每种触发都有其自身的含义。前两个操作与过多的指针检查有关,因为使用free()函数不需要进行这种检查。第三次激活指示释放指针后使用指针是不安全的。即使指针本身没有被取消引用,而是仅被检查,它仍然是可疑的,并且通常指示输入错误。好吧,最后一个操作仅指向DeepCode发现的相同问题。DeepCode文章中有一个屏幕截图,介绍了C / C ++支持的开始,在我们看来,其中包含误报。该警告指示一个可能的缓冲区溢出,但对于所述缓冲器中的存储器分配考虑到的长度家和线的长度,它是由+ 1进一步添加因此,在缓冲器中为肯定足够的空间。这里的问题可能是缺少验证是否成功分配了内存。也许在这种情况下,DeepCode警告还说明了其他内容:警告的第一句话并没有特别帮助您理解错误是什么以及分析仪实际发誓的东西。在这种情况下,也许触发了我们写过的另一个因素-创建有意义的警告的难度。对受过培训的分析人员的回答进行分类后,警告要么由人们编写,要么由有关提交/文档的评论形成。但是,如果错误模式是通过机器学习而不是开发人员推导出的,则很难发出良好的警告。我想知道此代码中需要更改什么,以便警告消失。但是,很奇怪的是,在新文件中完全相似的代码段中,没有发生这样的警告,并且出现了另一个警告。也许关键是分析仪所针对的环境已经消失,或者我做错了什么。但是,我认为警告的不稳定(其外观难以预测的事实)不仅会给用户带来不便,还会给分析仪本身的开发人员带来不便,并使开发过程复杂化。总的来说,尽管大多数DeepCode响应还不是错误的,但是它们的数量是如此之小,以至于目前其工作结果相当匮乏而不是准确的。原则上,少量假阳性伴随着少量阳性。我会给您一些PVS-Studio发现的有趣错误。片段N1V773在不释放“线”指针的情况下退出了该函数。可能发生内存泄漏。第166章bool BmpLoader::loadBmp(PxFileHandle f)
{
....
int lineLen = ....;
unsigned char* line = static_cast<unsigned char*>(malloc(lineLen));
for(int i = info.Height-1; i >= 0; i--)
{
num = fread(line, 1, static_cast<size_t>(lineLen), f);
if (num != static_cast<size_t>(lineLen)) { fclose(f); return false; }
....
}
free(line);
return true;
}
在此,如果从文件中读取被中断,则会发生内存泄漏,因为在从函数返回之前不会释放内存。片段N2V595在针对nullptr对其进行验证之前,已使用了'gSphereActor'指针。检查行:228、230。SnippetContactReportCCD.cpp 228void initScene()
{
....
gSphereActor = gPhysics->createRigidDynamic(spherePose);
gSphereActor->setRigidBodyFlag(PxRigidBodyFlag::eENABLE_CCD, true);
if (!gSphereActor)
return;
....
}
gSphereActor
指针被取消引用,但之后立即检查它是否为nullptr,函数将退出。也就是说,此处可以使用空指针,但仍会取消引用。在PhysX项目中有24种此类错误。DeepCode仅针对特定类型的情况(根据我的理解)给出肯定的结果,在这种情况下,指针最初被初始化为零,并且视野中没有其他分配。 PVS-Studio不能使用这样的代码,因为大多数情况下这种触发是错误的,因为指针可以在另一个转换单元中获取值(大多数触发是针对全局变量)。此示例表明,经过训练的静态分析中的假阳性较少,不一定是正确的。在这里,由于某种原因,DeepCode最有可能指示错误的初始化。相反,初始化gCooking突出显示了初始化gFoundation,尽管该索引没有进一步阐明。片段N3V517检测到使用'if(A){...} else if(A){...}'模式。存在逻辑错误的可能性。检查行:266,268。AABox.cpp 266bool AABox::initRT(int depthSamples, int coverageSamples)
{
....
int query;
....
if (....)
{
....
if ( query < coverageSamples)
ret = false;
else if ( query > coverageSamples)
coverageSamples = query;
....
if ( query < depthSamples)
ret = false;
else if ( query > depthSamples)
depthSamples = query;
}
else {
....
if ( query < depthSamples)
ret = false;
else if ( query < depthSamples)
depthSamples = query;
....
}
....
}
在这里,似乎复制粘贴错误已经蔓延。分析器在if和else中检测到相同的条件。从前面的if-else中,您可以看到通常检查了if中的“>” 和else中的“ <” 。尽管该模式看起来非常简单,但在DeepCode警告中我没有找到这样的响应,尽管它似乎是一个非常简单的检测模式。结论当然,DeepCode刚刚宣布了对C / C ++的支持,这是他们刚刚开始开发的产品的一部分。您也可以在您的项目上尝试使用它,在它们的服务中,已实施了方便的反馈窗口,以防您收到错误警告。但是,我们现在可以在静态分析中看到与机器学习相关的缺点。因此,我们对将机器学习添加到静态分析仪的想法持怀疑态度,因为这种增长令人怀疑:完全相同的分析仪支持,编写或编辑文档以及自行进行诊断工作是必要的。此外,在开发过程中遇到的问题需要开发人员提供更复杂的解决方案和特定技能,从而在扩大团队规模时减少了潜在候选人的数量。实际上,在这种情况下,开发人员不仅应该是所分析语言的专家,而且还应该拥有机器学习领域的知识。如果您想与说英语的读者分享这篇文章,请使用翻译链接:Victoria Khanieva。DeepCode:外部透视图。