继续:侮辱关于静态代码分析器的意见

image1.png


按照计划,写了一篇文章“这是对静态代码分析器的意见的耻辱”,我们将大声说出来,并从容地放开这个话题。但出乎意料的是,本文引起了强烈的反响。不幸的是,讨论出错了,现在我们将再次尝试解释我们对局势的看法。



轶事学



因此,这一切都始于文章“这是有关静态代码分析器的意见的耻辱”。它开始在某些资源上进行了积极的讨论,并且该讨论与下面的旧轶事非常相似。
我们为一些苛刻的西伯利亚伐木工人买了一把日本电锯。

伐木工人围成一个圈,决定对其进行测试。

他们把她带进来,给了她一棵树。

日本人说:“拉链。”

“哦,他妈的……”-伐木工人说。

他们给她滑了一棵粗壮的树。“ Vzh-zh-zhik!” -锯说。

“哇,该死!” -伐木工人说。

他们把厚重的雪松滑入她的体内。“ VZH-ZH-ZH-ZH-ZH-ZH-ZHIK !!!” -锯说。

“哇,他妈的!!” -伐木工人说。

他们给她滑了一块铁。“裂纹!” -锯说。

“是的,他妈的!” -严厉的西伯利亚伐木工人责备地说!然后他们离开,用斧头砍伐森林...
一对一的故事。人们看了一下代码:



if (A[0] == 0)
{
  X = Y;
  if (A[0] == 0)
    ....
}


他们开始发明可能合理的情况,这意味着PVS-Studio分析仪的警告为假阳性。关于两次检查之间的内存变化的推理过程涉及到:



  • 并行流的工作;
  • 信号/中断处理程序;
  • 变量X是元素A [0]的引用
  • 硬件,例如执行DMA操作;
  • 等等


讨论了并非分析仪能够理解的所有情况后,他们就离开了用斧头砍伐森林。也就是说,我们找到了一个借口,为什么在我们的工作中可以继续不使用静态代码分析器。



我们对形势的看法



这种方法适得其反。不完善的工具可能很有用,并且在经济上可行。



是的,任何静态分析仪都会产生误报。而且对此无能为力。但是,这种不幸被大大夸大了。在实践中,静态分析器可以被配置并以各种方式来抑制和工作与假阳性(参见1234)。另外,在这里应该回顾一下文章“误报是我们的敌人,但可能仍然是您的朋友”。



但是,即使这不是主要内容。根本不考虑外来代码的特殊情况!您可以将分析仪与复杂的代码混淆吗?是的你可以。但是,对于一种这样的情况,将有数百个有用的分析器触发器。在很早的阶段就可以发现并纠正许多错误。可以安全地抑制一两个误报,而不再关注它们。



PVS-Studio再次正确



在这里,文章可以完成。但是,有些人可能认为上一节不是合理的考虑,而是试图隐藏PVS-Studio工具的弱点和缺点。因此,您必须继续。



考虑包含变量声明的具体编译代码



void SetSynchronizeVar(int *);

int foo()
{
    int flag = 0;
    SetSynchronizeVar(&flag);

    int X, Y = 1;

    if (flag == 0)
    {
        X = Y;
        if (flag == 0)
            return 1;
    }
    return 2;
}


PVS-Studio分析仪会合理地发出警告:V547表达式'flag == 0'始终为true。



分析仪绝对正确。如果有人开始保证某个变量可以在另一个线程,信号处理程序等中更改,那么他根本就不会理解C和C ++语言。你不能这样写。



出于优化目的,编译器有权放弃第二项检查,因此绝对正确。从语言的角度来看,变量不能更改。它的背景变化仅是未定义行为。



为了使检查保持不变,必须将变量声明为volatile



void SetSynchronizeVar(volatile int *);

int foo()
{
    volatile int flag = 0;
    SetSynchronizeVar(&flag);
    ....
}


PVS-Studio分析仪知道这一点,并且不再为此类代码发出警告



在这里,我们回到第一篇文章中讨论的内容没有问题。但是有批评或误解,为什么分析仪有权发出警告。



给最细心的读者的提示



一些读者可能会从第一篇文章回到综合示例:



char get();
int foo(char *p, bool arg)
{
    if (p[1] == 1)
    {
        if (arg)
            p[0] = get();
        if (p[1] == 1)          // Warning
            return 1;
    }
    // ....
    return 3;
}


并添加volatile



char get();
int foo(volatile char *p, bool arg)
{
    if (p[1] == 1)
    {
        if (arg)
            p[0] = get();
        if (p[1] == 1)          // Warning :-(
            return 1;
    }
    // ....
    return 3;
}


此后,可以说分析器仍发出警告V547表达式'p [1] == 1'始终为真。



万岁,终于证明了分析仪还是错误的:)。这是误报!



如您所见,我们没有隐藏任何缺陷。在分析数组元素的数据流时,这种不良的挥发物丢失了。该缺陷已被发现并修复。该修复程序将在分析仪的下一版本中提供。不会有误报。



为什么没有较早地发现此错误?因为实际上,这又是在真实项目中找不到的虚幻代码。实际上,尽管我们已经检查了许多开源项目,但到目前为止,我们还没有遇到过这样的代码



为什么代码不切实际?首先,在实践中,两次检查之间会存在某种计时或延迟功能。其次,除非绝对必要,否则任何人都不会在他们的头脑中创建由易失性元素组成的数组。使用这样的阵列会极大地降低性能。



让我们总结一下。创建解析器失败的示例很容易。但是从实际的角度来看,已识别的缺陷实际上不会影响代码分析的质量和检测到的实际错误的数量。毕竟,实际应用程序的代码只是分析器和人员可以同时理解的代码,而不是重蹈覆辙或困惑的代码。如果代码令人困惑,则没有时间使用分析器了。



感谢您的关注。





其他连结









如果您想与说英语的读者分享这篇文章,请使用翻译链接:Andrey Karpov。第2部分:关于静态分析器的Up恼意见



All Articles