16

如果这是一个非常简单的概念,我很抱歉,但我发现很难获得正确的心态才能正确使用clang.

float foo(float f) { return (f / 0); }

我编译这个小片段

clang++ -fsanitize=float-divide-by-zero -std=c++11 -stdlib=libc++ -c source.cpp -o osan

而且我还编译了我的对象的“正常”版本而不使用消毒剂

clang++ -std=c++11 -stdlib=libc++ -c source.cpp -o onorm

我期待一些详细的输出,或者来自控制台的一些错误,但是在检查文件时nm我只发现了 1 个不同

nm o* --demangle

onorm:
0000000000000000 T foo(float)

osan:
                 U __ubsan_handle_divrem_overflow
0000000000000000 T foo(float)

因此,在经过消毒的版本中,有一个未定义的符号,其名称类似于我在编译时使用的消毒剂;但一切都是真正的“沉默”,铿锵前端根本没有输出。

我应该如何使用消毒剂以及正确的工作流程是什么?那个未定义的符号有什么意义?

4

2 回答 2

13

未定义符号是实现消毒剂检查的函数。如果您查看生成的代码:

无消毒剂:

_Z3foof:                                # @_Z3foof
    .cfi_startproc
# BB#0:
    xorps   %xmm1, %xmm1
    divss   %xmm1, %xmm0
    ret

使用消毒剂:

_Z3foof:                                # @_Z3foof
    .cfi_startproc
    .long   1413876459              # 0x54460aeb
    .quad   _ZTIFffE
# BB#0:
    pushq   %rax
.Ltmp1:
    .cfi_def_cfa_offset 16
    movss   %xmm0, 4(%rsp)          # 4-byte Spill
    movd    %xmm0, %esi
    movl    $__unnamed_1, %edi
    xorl    %edx, %edx
    callq   __ubsan_handle_divrem_overflow
    xorps   %xmm1, %xmm1
    movss   4(%rsp), %xmm0          # 4-byte Reload
    divss   %xmm1, %xmm0
    popq    %rax
    ret

您会看到它添加了使用该函数进行检查的代码。

编译器应该自动链接到适当的消毒剂库,然后对我来说下面的完整程序:

float foo(float f) { return (f / 0); }
int main() {
    foo(1.0f);
}

执行时产生以下输出:

main.cpp:1:32: runtime error: division by zero

我使用命令构建并运行clang++ -fsanitize=undefined main.cpp && ./a.out


如果您想要编译时检查,您想要启用更多编译器警告或静态分析器。但是,对于浮点除以零错误,似乎没有任何警告或静态分析检查。

这是一个生成分析器报告的程序:

#include <malloc.h>

int main() {
    int *i = (int*) malloc(sizeof(int));
}

用它编译clang++ -std=c++11 main.cpp不会产生诊断,但用clang++ -std=c++11 --analyze main.cpp它编译会报告以下内容:

main.cpp:4:10: warning: Value stored to 'i' during its initialization is never read
    int *i = (int*) malloc(sizeof(int));
         ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:5:1: warning: Potential leak of memory pointed to by 'i'
}
^

也可以使用 -Weverything [-Wunused-value] 检测到死存储,但泄漏仅由分析器检测到。

默认情况下,完整的分析结果将写入 plist 文件。您还可以使用以下命令运行分析器:

clang++ --analyze -Xanalyzer -analyzer-output=text main.cpp
clang++ --analyze -Xanalyzer -analyzer-output=html -o html-dir main.cpp

在标准输出或通过带注释的源代码的 html 显示,而不是在 plist 中获取检测到的问题的详细演练。

此处列出了分析器检查。

请注意,为了最好地工作,分析器需要分析整个程序,这意味着它需要与构建系统相关联。通常的界面是通过 IDE (Xcode) 或scan-build带有 make 的工具。CMake 具有一些 clang 功能,例如生成 clang JSON 编译数据库文件,但我不确定 CMake 是否有任何内置支持 clang 分析器。

于 2014-03-27T21:56:15.140 回答
5

因此,如果我们查看控制代码生成中的文档,它会说(强调我的):

为各种形式的未定义或可疑行为打开运行时检查。

此选项控制 Clang 是否为各种形式的未定义或可疑行为添加运行时检查,默认情况下禁用。如果检查失败,则会在运行时生成一条诊断消息来解释问题

所以这些是运行时检查而不是编译时检查。因此,如果您foo在代码中使用,那么您将看到以下输出:

运行时错误:除以零

使用以下方法实时查看此示例-fsanitize=undefined

float foo(float f) { return (f / 0); }

int main()
{
    int x = 1 << 100 ;
    foo( 2.0f ) ;
}

它生成两条运行时消息:

main.cpp:6:19:运行时错误:移位指数 100 对于 32 位类型“int”而言太大

main.cpp:2:36:运行时错误:除以零

更新

关于静态检查器,在我对检测未定义行为的 C++ 实现的回答中?我提到了几个工具:STACKkcc,当然还有Frama-C

显然clang允许您使用--analyze 来运行它的静态检查器,但它似乎最终可能会被禁用,并且运行它的正确方法是通过scan-build

同样在我的自我回答问题中,为什么常量表达式会排除未定义的行为?我展示了如何constexprs在编译时捕获未定义的行为。

于 2014-03-27T21:56:56.650 回答