3

I want to build a test program for a repository of mine with undefined behavior sanitization enabled (at least with GCC and perhaps clang). I know how to do this manually:

  • Add -fsanitize=undefined to the compilation flags
  • Add -lubsan to the linking flags
  • Make sure an appropriate version of libubsan is installed.

Now, in CMake, I would expect something like the following to work:

find_package(ubsan)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU"   OR
    CMAKE_CXX_COMPILER_ID STREQUAL "Clang" )
    target_compile_options(my_test PRIVATE "-fsanitize=undefined")
endif()

target_link_libraries(ubsan::ubsan)

... but there is no such version (as of CMake 3.21.0-rc2). So, how should I go about it? Grab a FindUBSan.cmake from somewhere? Perhaps do something else?

PS - The question applies similarly to C

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • `but there is no such version` version of ... ? `Make sure an appropriate version of libubsan is installed.` What do you mean? `libubsan.so` should be distributed with compiler, at least on my distribution it's part of `gcc-libs` package. – KamilCuk Aug 02 '21 at 18:30

2 Answers2

4

Everything needs to be compiled with the sanitizers enabled (mix and match generally isn't possible), and enabling the santizers is compiler-specific; therefore, it's a toolchain option.

In a toolchain file, include the following:

set(CMAKE_C_FLAGS_INIT "-fsanitize=undefined")
set(CMAKE_CXX_FLAGS_INIT "-fsanitize=undefined")

You can also set CMAKE_<LANG>_FLAGS at the command line to include -fsanitize=undefined.


Full example:

alex@alex-ubuntu:~/test$ tree
.
├── CMakeLists.txt
├── main.cpp
└── ubsan.cmake

0 directories, 3 files

alex@alex-ubuntu:~/test$ cat CMakeLists.txt 
cmake_minimum_required(VERSION 3.21)
project(test)

add_executable(main main.cpp)

alex@alex-ubuntu:~/test$ cat main.cpp 
int main () { return 0; }

alex@alex-ubuntu:~/test$ cat ubsan.cmake 
set(CMAKE_C_FLAGS_INIT "-fsanitize=undefined")
set(CMAKE_CXX_FLAGS_INIT "-fsanitize=undefined")

alex@alex-ubuntu:~/test$ cmake -G Ninja -S . -B build --toolchain ubsan.cmake 
-- The C compiler identification is GNU 9.3.0
-- The CXX compiler identification is GNU 9.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/alex/test/build

alex@alex-ubuntu:~/test$ cmake --build build/ -- -v
[1/2] /usr/bin/c++   -fsanitize=undefined -MD -MT CMakeFiles/main.dir/main.cpp.o -MF CMakeFiles/main.dir/main.cpp.o.d -o CMakeFiles/main.dir/main.cpp.o -c /home/alex/test/main.cpp
[2/2] : && /usr/bin/c++ -fsanitize=undefined  CMakeFiles/main.dir/main.cpp.o -o main   && :

alex@alex-ubuntu:~/test$ ldd build/main 
    linux-vdso.so.1 (0x00007ffc9dd75000)
    libubsan.so.1 => /lib/x86_64-linux-gnu/libubsan.so.1 (0x00007f79a4b52000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f79a4960000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f79a495a000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f79a4937000)
    libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f79a4755000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f79a4738000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f79a550a000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f79a45e9000)
Alex Reinking
  • 16,724
  • 5
  • 52
  • 86
  • I believe you're speculating... as the above won't work. i.e. GCC typically also needs libubsan for the sanitation to work. And theoretically, that library could pull in the compiler flags in. Also, what about other compilers? – einpoklum Aug 02 '21 at 18:04
  • That worked for me with GCC 9.3.0. Those flags (`-fsanitize=undefined` via `CMAKE__FLAGS`) are also passed to the compiler when liking, and the resulting binary for a basic `int main () { return 0; }` is indeed linked to libubsan. – Alex Reinking Aug 02 '21 at 18:34
  • All that gets me is undefined symbols from libubsan. But then, I'm using GCC 10.2 – einpoklum Aug 02 '21 at 20:26
  • @einpoklum - I just tried it with 10.3 (from Ubuntu repos) and the executable is still linked to `libubsan` with this answer (just added `set(CMAKE_CXX_COMPILER g++-10)` and C equiv to toolchain). Can you provide an MRE? – Alex Reinking Aug 02 '21 at 20:34
  • 1
    I'll try sometime soon (but probably not before the weekend). – einpoklum Aug 02 '21 at 21:12
  • No rush. Thanks! – Alex Reinking Aug 02 '21 at 21:12
2

What's the idiomatic way of enabling UB sanitization in CMake?

There is no "idiomatic way" - it's compiler specific. Do:

  • Add -fsanitize=undefined to the compilation flags
target_compile_options(my_test PUBLIC -fsanitize=undefined)
target_link_options(my_test PUBLIC -fsanitize=undefined)

You could check if the option is supported with CheckCXXCompilerFlag.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • 1
    You will also need to call `target_link_options` if you take this approach... I tried it and it does not link to `libubsan` because the link line lacks this flag. – Alex Reinking Aug 02 '21 at 18:39
  • This is basically what I said I'm doing, sans the library dependency which you're missing. – einpoklum Aug 02 '21 at 20:27