Learning how to use and deploy 'scan-build'

I am working on adding ‘scan-build’ to the set of tools we include with our release of ‘clang’, and trying to determine the minimal set of components needed to be able to successfully use ‘scan-build’. The documentation for this is fairly thin, so there are many things I am having to learn.

Currently we just provide the compiler itself, along with the headers and libraries for our SHAVE target. I would like to add a lot of the additional tools and utilities that CLang/LLVM provide with future versions, ‘scan-build’ is one of these, and ‘clang-tidy’, etc., but not a complete set.

In addition to the ‘scan-build’ Perl script itself, I also need the ‘ccc-analyzer’ and ‘c+±analyzer’ Perl scripts, and this appears to run without error; but I would like to know are their other components that I need to ensure that it is fully functional?

I would like to know what is the “core set” of analysers? I listed the full set of analysers in ‘clang’ v3.9.0 and some of these names are prefixed with ‘core.’ so I presume these are the ones that comprise the core set? When enabling others I presume I have to add a comma-separated list to the ‘-enable-checker’ option, for example ‘debug.Stats,unix.Malloc’. The names in the list of checks are formed with separating dot characters, does selecting something like ‘security’ enable all the checkers in the ‘security.*’ group, or do I have to enable them one at a time?

Sorry if this is all answered in some part of the documentation I haven’t found, and please point me at it.

Thanks,

MartinO

As I remember, Perl scan-build doesn’t have any additional dependencies. The “core” set is responsible for basic modelling of some functions in path-sensitive mode, they are not the analyzer core itself. Shortly, analyzer has two kinds of checkers - AST/CFG based (fast) and path-sensitive (slow but more powerful). If you need any path-sensitive checkers, you should also enable “core” checkers for more precise modelling. // TODO: enable “core” automatically in such cases? To enable a full package, you should just point it. Example: “-enable-checker core,security” You can find some additional information on

you will need the share directory content too, otherwise the html report won’t work as expected… but the CMakeLists.txt file has an install target, which is a very good reference what you need to install. to use that has an additional benefit: your distribution is going to be very similar to other distributions, so users can use documentation written elsewhere.

I would recommend to start with the default set of checkers and disable the ones you do not find useful for your codebase (if any).

Thanks very much for your very helpful feedback, and I now have it mostly working. However, I must still be doing something wrong as I am not seeing any analysis reports, and it would be great if someone could point out what I am doing wrong.

I am using ‘clang’ v3.9.0 built with Visual Studio 2013 on Windows, but running the test under Cygwin. In the description below I use the terms ‘<tools-cygpath>’, ‘<tools-WINPATH>’, ‘<test-cygpath>’ and ‘<test-WINPATH>’ to indicate the paths to the tools and test source from both Cygwin’s and Windows’ perspectives. The actual paths are too long to trouble you with, and not useful. My version of Perl on Cygwin is v5.14.4.

The following describes how I am invoking ‘scan-build’ and my observations of its execution - sorry for the long description, but I did not want to elide anything that may be useful:

// How I invoke ‘scan-build’::

<tools-cygpath>/bin/scan-build \

–use-cc clang \

–use-c++ clang++ \

-enable-checker “core,security” \

-v -v -v \

-o check \

–html-title “Testing ScanBuild” \

make build

// 'scan-build**’ invokes ‘c+±analyzer’ as follows (these are the parameters I pass to the compiler normally)::**

<tools-cygpath>/bin/…/libexec/c+±analyzer \

-v -c -Wall -fverbose-asm -UNDEBUG \

-O3 -std=c++14 testingScanBuild.cpp -o testingScanBuild.o

// 'c+±analyzer**’ invokes my ‘clang++’ as follows (these are the normal options we expect)::**

“<tools-WINPATH>\bin\clang++.exe” -cc1 -triple shave -S \

-disable-free -disable-llvm-verifier -discard-value-names \

-main-file-name testingScanBuild.cpp \

-mrelocation-model static -mthread-model posix \

-mdisable-fp-elim -masm-verbose -no-integrated-as -funroll-loops \

-mllvm -unroll-allow-partial \

-mno-zero-initialized-in-bss -fno-rtti \

-mllvm -enable-misched \

-mllvm -enable-aa-sched-mi \

-mllvm -misched-bottomup \

-mllvm -misched=ilpmax \

-v -dwarf-column-info -debugger-tuning=gdb \

-coverage-file “<test-WINPATH>\ScanBuild\testingScanBuild.o” \

-resource-dir “<tools-WINPATH>\bin\…\lib\clang\3.9.0” \

-U NDEBUG \

-internal-externc-isystem “<tools-WINPATH>\include\c++” \

-internal-externc-isystem “<tools-WINPATH>\include” \

-O3 -Wall -std=c++14 \

-fdeprecated-macro -fno-dwarf-directory-asm \

-fdebug-compilation-dir “<test-WINPATH>\ScanBuild” \

-ferror-limit 19 -fmessage-length 0 -ffreestanding \

-fallow-half-arguments-and-returns -fobjc-runtime=gcc \

-fdiagnostics-show-option -vectorize-loops -vectorize-slp \

-o “D:\tmp\testingScanBuild-413704.s” \

-x c++ testingScanBuild.cpp

// Then ‘c+±analyzer’ reports this following which mirrors the usual

// invocation, but also includes many Analysis options (indented)::

[LOCATION]: <test-cygpath>/ScanBuild

#SHELL (cd ‘<test-cygpath>/ScanBuild’ && ‘<tools-cygpath>/bin/clang’ \

‘-cc1’ ‘-triple’ ‘shave’ ‘-analyze’ ‘-disable-free’ \

‘-disable-llvm-verifier’ ‘-discard-value-names’ \

‘-main-file-name’ ‘testingScanBuild.cpp’ \

‘-analyzer-store=region’ \

‘-analyzer-opt-analyze-nested-blocks’ \

‘-analyzer-eagerly-assume’ \

‘-analyzer-checker=core’ \

‘-analyzer-checker=unix’ \

‘-analyzer-checker=deadcode’ \

‘-analyzer-checker=cplusplus’ \

‘-analyzer-checker=security.insecureAPI.UncheckedReturn’ \

‘-analyzer-checker=security.insecureAPI.getpw’ \

‘-analyzer-checker=security.insecureAPI.gets’ \

‘-analyzer-checker=security.insecureAPI.mktemp’ \

‘-analyzer-checker=security.insecureAPI.mkstemp’ \

‘-analyzer-checker=security.insecureAPI.vfork’ \

‘-analyzer-checker=nullability.NullPassedToNonnull’ \

‘-analyzer-checker=nullability.NullReturnedFromNonnull’ \

‘-analyzer-output’ \

‘plist’ \

‘-w’ ‘-mrelocation-model’ ‘static’ ‘-mthread-model’ ‘posix’ \

‘-mdisable-fp-elim’ ‘-masm-verbose’ ‘-no-integrated-as’ ‘-funroll-loops’ \

‘-mllvm’ ‘-unroll-allow-partial’ \

‘-mno-zero-initialized-in-bss’ ‘-fno-rtti’ \

‘-mllvm’ ‘-enable-misched’ \

‘-mllvm’ ‘-enable-aa-sched-mi’ \

‘-mllvm’ ‘-misched-bottomup’ \

‘-mllvm’ ‘-misched=ilpmax’ \

‘-v’ ‘-dwarf-column-info’ ‘-debugger-tuning=gdb’ \

‘-resource-dir’ ‘<tools-WINPATH>\bin..\lib\clang\3.9.0’ \

‘-U’ ‘NDEBUG’ \

‘-internal-externc-isystem’ ‘<tools-WINPATH>\include\c++’ \

‘-internal-externc-isystem’ ‘<tools-WINPATH>\include’ \

‘-O3’ ‘-std=c++14’ \

‘-fdeprecated-macro’ ‘-fno-dwarf-directory-asm’ \

‘-fdebug-compilation-dir’ ‘<test-WINPATH>\ScanBuild’ \

‘-ferror-limit’ ‘19’ ‘-fmessage-length’ ‘0’ ‘-ffreestanding’ \

‘-fallow-half-arguments-and-returns’ ‘-fobjc-runtime=gcc’ \

‘-fdiagnostics-show-option’ ‘-vectorize-loops’ ‘-vectorize-slp’ \

‘-analyzer-display-progress’ \

‘-analyzer-checker’ ‘core,security’ \

‘-analyzer-opt-analyze-headers’ \

‘-analyzer-output=html’ \

‘-o’ ‘<test-cygpath>/ScanBuild/check/2016-11-12-114955-13712-1’ \

‘-x’ ‘c++’ ‘testingScanBuild.cpp’)

// The analyser reports the following progress, I have elided references to

// headers and other non-relevant files::

ANALYZE (Syntax): testingScanBuild.cpp verifyResult

ANALYZE (Syntax): testingScanBuild.cpp verifyResult

ANALYZE (Syntax): testingScanBuild.cpp main

ANALYZE (Syntax): testingScanBuild.cpp generateInputData

ANALYZE (Syntax): testingScanBuild.cpp generateOutputData

ANALYZE (Path, Inline_Regular): testingScanBuild.cpp main

// Finally it concludes with::

scan-build: Removing directory ‘<test-cygpath>/ScanBuild/check/2016-11-12-115552-7812-1’ because it contains no reports.

scan-build: No bugs found.

But in my test, I have deliberately seeded it with unreachable code that I would expect the dead-code checker to find:

bool flag = someTestCondition == true;

if (flag) {

doSomethingInteresting();

} else if (flag) { // Same test as above

unreachableCode(); // Expecting an SA diagnostic here

} else {

doNothing();

}

I’m sure that there is something really simple that I am missing, but I have no idea what it is.

Thanks,

MartinO

hey Martin, the checker name what you are looking for is ‘alpha.deadcode.UnreachableCode’
also lower the bar for your test. (actually you are testing the checker not the scan-build tool now :))

/* this will trigger warning */

void foo() {
int i = 0;
return;
++i;
}

Thanks very much for your very helpful feedback, and I now have it mostly working. However, I must still be doing something wrong as I am not seeing any analysis reports, and it would be great if someone could point out what I am doing wrong.

I am using ‘clang’ v3.9.0 built with Visual Studio 2013 on Windows, but running the test under Cygwin. In the description below I use the terms ‘<tools-cygpath>’, ‘<tools-WINPATH>’, ‘<test-cygpath>’ and ‘<test-WINPATH>’ to indicate the paths to the tools and test source from both Cygwin’s and Windows’ perspectives. The actual paths are too long to trouble you with, and not useful. My version of Perl on Cygwin is v5.14.4.

The following describes how I am invoking ‘scan-build’ and my observations of its execution - sorry for the long description, but I did not want to elide anything that may be useful:

// How I invoke 'scan-build'::
<tools-cygpath>/bin/scan-build \
        --use-cc clang \
        --use-c++ clang++ \
        -enable-checker “core,security" \

I recommend not limiting the analysis to these 2 packages unless you see that the other packages turned on by default report false positives on your codebase.

        -v -v -v \
        -o check \
        --html-title "Testing ScanBuild" \
        make build

// 'scan-build' invokes 'c++-analyzer' as follows (these are the parameters I pass to the compiler normally)::
<tools-cygpath>/bin/../libexec/c++-analyzer \
    -v -c -Wall -fverbose-asm -UNDEBUG \
    -O3 -std=c++14 testingScanBuild.cpp -o testingScanBuild.o

// 'c++-analyzer' invokes my 'clang++' as follows (these are the normal options we expect)::
"<tools-WINPATH>\\bin\\clang++.exe" -cc1 -triple shave -S \
    -disable-free -disable-llvm-verifier -discard-value-names \
    -main-file-name testingScanBuild.cpp \
    -mrelocation-model static -mthread-model posix \
    -mdisable-fp-elim -masm-verbose -no-integrated-as -funroll-loops \
    -mllvm -unroll-allow-partial \
    -mno-zero-initialized-in-bss -fno-rtti \
    -mllvm -enable-misched \
    -mllvm -enable-aa-sched-mi \
    -mllvm -misched-bottomup \
    -mllvm -misched=ilpmax \
    -v -dwarf-column-info -debugger-tuning=gdb \
    -coverage-file "<test-WINPATH>\\ScanBuild\\testingScanBuild.o" \
    -resource-dir "<tools-WINPATH>\\bin\\..\\lib\\clang\\3.9.0" \
    -U NDEBUG \
    -internal-externc-isystem "<tools-WINPATH>\\include\\c++" \
    -internal-externc-isystem "<tools-WINPATH>\\include" \
    -O3 -Wall -std=c++14 \
    -fdeprecated-macro -fno-dwarf-directory-asm \
    -fdebug-compilation-dir "<test-WINPATH>\\ScanBuild" \
    -ferror-limit 19 -fmessage-length 0 -ffreestanding \
    -fallow-half-arguments-and-returns -fobjc-runtime=gcc \
    -fdiagnostics-show-option -vectorize-loops -vectorize-slp \
    -o "D:\\tmp\\testingScanBuild-413704.s" \
    -x c++ testingScanBuild.cpp

// Then 'c++-analyzer' reports this following which mirrors the usual
// invocation, but also includes many Analysis options (indented)::
[LOCATION]: <test-cygpath>/ScanBuild
#SHELL (cd '<test-cygpath>/ScanBuild' && '<tools-cygpath>/bin/clang' \
    '-cc1' '-triple' 'shave' '-analyze' '-disable-free' \
    '-disable-llvm-verifier' '-discard-value-names' \
    '-main-file-name' 'testingScanBuild.cpp' \
        '-analyzer-store=region' \
        '-analyzer-opt-analyze-nested-blocks' \
        '-analyzer-eagerly-assume' \
        '-analyzer-checker=core' \
        '-analyzer-checker=unix' \
        '-analyzer-checker=deadcode' \
        '-analyzer-checker=cplusplus' \
        '-analyzer-checker=security.insecureAPI.UncheckedReturn' \
        '-analyzer-checker=security.insecureAPI.getpw' \
        '-analyzer-checker=security.insecureAPI.gets' \
        '-analyzer-checker=security.insecureAPI.mktemp' \
        '-analyzer-checker=security.insecureAPI.mkstemp' \
        '-analyzer-checker=security.insecureAPI.vfork' \
        '-analyzer-checker=nullability.NullPassedToNonnull' \
        '-analyzer-checker=nullability.NullReturnedFromNonnull' \
        '-analyzer-output' \
        'plist' \
    '-w' '-mrelocation-model' 'static' '-mthread-model' 'posix' \
    '-mdisable-fp-elim' '-masm-verbose' '-no-integrated-as' '-funroll-loops' \
    '-mllvm' '-unroll-allow-partial' \
    '-mno-zero-initialized-in-bss' '-fno-rtti' \
    '-mllvm' '-enable-misched' \
    '-mllvm' '-enable-aa-sched-mi' \
    '-mllvm' '-misched-bottomup' \
    '-mllvm' '-misched=ilpmax' \
    '-v' '-dwarf-column-info' '-debugger-tuning=gdb' \
    '-resource-dir' '<tools-WINPATH>\bin\..\lib\clang\3.9.0' \
    '-U' 'NDEBUG' \
    '-internal-externc-isystem' '<tools-WINPATH>\include\c++' \
    '-internal-externc-isystem' '<tools-WINPATH>\include' \
    '-O3' '-std=c++14' \
    '-fdeprecated-macro' '-fno-dwarf-directory-asm' \
    '-fdebug-compilation-dir' '<test-WINPATH>\ScanBuild' \
    '-ferror-limit' '19' '-fmessage-length' '0' '-ffreestanding' \
    '-fallow-half-arguments-and-returns' '-fobjc-runtime=gcc' \
    '-fdiagnostics-show-option' '-vectorize-loops' '-vectorize-slp' \
        '-analyzer-display-progress' \
        '-analyzer-checker' 'core,security' \
        '-analyzer-opt-analyze-headers' \
        '-analyzer-output=html' \
        '-o' '<test-cygpath>/ScanBuild/check/2016-11-12-114955-13712-1' \
    '-x' 'c++' 'testingScanBuild.cpp')

// The analyser reports the following progress, I have elided references to
// headers and other non-relevant files::
ANALYZE (Syntax): testingScanBuild.cpp verifyResult
ANALYZE (Syntax): testingScanBuild.cpp verifyResult
ANALYZE (Syntax): testingScanBuild.cpp main
ANALYZE (Syntax): testingScanBuild.cpp generateInputData
ANALYZE (Syntax): testingScanBuild.cpp generateOutputData
ANALYZE (Path, Inline_Regular): testingScanBuild.cpp main

// Finally it concludes with::
scan-build: Removing directory '<test-cygpath>/ScanBuild/check/2016-11-12-115552-7812-1' because it contains no reports.
scan-build: No bugs found.

But in my test, I have deliberately seeded it with unreachable code that I would expect the dead-code checker to find:

bool flag = someTestCondition == true;

if (flag) {
  doSomethingInteresting();
} else if (flag) { // Same test as above
  unreachableCode(); // Expecting an SA diagnostic here

The checkers you’ve enabled will not find this bug. Try testing with devision by zero or null pointer dereference:

int x = 0;
return 5/x;

or

int *x = 0;
*x = 5;

I changed my test case to just:

int main () {

int zero = 0;

return 5 / 0;

}

and invoked ‘scan-build’ as follows:

scan-build --use-cc clang --use-c++ = clang++ -v -v -v -o check make build

and this causes a warning from the compiler:

ANALYZE (Syntax): testScanBuild.cpp main

ANALYZE (Path, Inline_Regular): testScanBuild.cpp main

testScanBuild.cpp:350:12: warning: Division by zero

return 5 / zero;

^~~~

1 warning generated.

But I still get the message stating:

scan-build: Removing directory ‘<test-cygpath>/ScanBuild/check/2016-11-12-190818-7508-1’ because it contains no reports.

scan-build: No bugs found.

Omitting the ‘-enable-checker’ option enables the following checkers (by default):

-analyzer-store=region

-analyzer-opt-analyze-nested-blocks

-analyzer-eagerly-assume

-analyzer-checker=core

-analyzer-checker=unix

-analyzer-checker=deadcode

-analyzer-checker=cplusplus

-analyzer-checker=security.insecureAPI.UncheckedReturn

-analyzer-checker=security.insecureAPI.getpw

-analyzer-checker=security.insecureAPI.gets

-analyzer-checker=security.insecureAPI.mktemp

-analyzer-checker=security.insecureAPI.mkstemp

-analyzer-checker=security.insecureAPI.vfork

-analyzer-checker=nullability.NullPassedToNonnull

-analyzer-checker=nullability.NullReturnedFromNonnull

This is strange, because the compiler clearly detects the issue and warns, but I am not getting a report, so something else must be broken in my configuration. I have not altered any of the Perl scripts.

I get the same results running on Linux (CentOS 7).

MartinO

Thanks, but not luck with this either :frowning:

Typo:

and invoked ‘scan-build’ as follows:

scan-build --use-cc clang --use-c++ clang++ -v -v -v -o check make build

Please, use the examples I provided in the previous email. The analyzer tries to avoid reporting the issues that can be found by the compiler.

Annas-MBP-3:compiler-rt anna$ clang --analyze ~/tmp/ex.c
/Users/anna/tmp/ex.c:6:11: warning: Division by zero
  return 5/x + 5/0;
         ~^~
1 warning generated.
Annas-MBP-3:compiler-rt anna$ clang -fsyntax-only ~/tmp/ex.c
/Users/anna/tmp/ex.c:6:17: warning: division by zero is undefined [-Wdivision-by-zero]
  return 5/x + 5/0;
                ^~
1 warning generated.

Thanks Anna,

Actually there was a typo in my message and I actually had the identifier ‘zero’ not the constant ‘0’:

int main () {

int zero = 0;

return 5 / zero;

}

but I have changed this to just contain:

int foo () {

int x = 0;

return 5/x;

}

Compiling this as:

clang++ --analyze testScanBuild.cpp

does report the warning, and creates the file ‘testScanBuild.plist’ which on inspection appears to contain the expected diagnostic information, and compiling it as:

clang++ -c testScanBuild.cpp

does not produce a warning (no ‘-Wall’ option).

I just changed my Makefile to simply:

build: testScanBuild.o

%o.%cpp:

$(CXX) -c $<

and:

scan-build --use-cc clang --use-cxx clang++ -v -v -v -o check --keep-empty make build

I had not realised that the analyzers would not produce a report if the compiler already warned.

Thanks again,

MartinO

Hi again. I have solved the problem that I was having but it is obscure so I will explain here.

Turns out that the problem I am having is a consequence of Windows versus Cygwin paths. Running the same scenario on Linux is fine (after I had an example with something worth reporting).

In summary the issue is the path that is passed using ‘-o’ to the Visual Studio built ‘clang.exe’ which only understands Windows paths, but at line #1490 in ‘scan-build’ the path is constructed using:

$Options{OutputDir} = abs_path($OutDir);

In my case the path for ‘-o check’ becomes:

/src/tests/ScanBuild/check

But in my Cygwin setup, ‘/src/’ is actually a ‘mount’ of the directory ‘S:\Projects\’ - I use mounts so that my scripts are portable across Linux and Windows systems. I tend to live in the ‘bash’ shell on Windows and Linux. If I was passing the path to ‘clang.exe’ from a Makefile, I would typically use ‘cygpath -m’ to get the Windows path, but because this is happening in the generic ‘scan-build’ Perl wrapper, it passes the Linux/Cygwin version of the path, but to the VS built ‘clang.exe’.

So the data for the reports “is” being created by ‘clang’, but it is being placed at:

D:\src\tests\ScanBuild\check

which is not the same place. The when ‘scan-build’ tries to collate the data and generate the report, the directory Perl looks in is empty. I can fix this, I just need to swot up my OS Perl knowledge and I should be able to fix it. I may need to do something similar in ‘ccc-analyzer’. I will feedback any changes that may be useful to others once I have it figured out.

Thanks for your help, I would never have found this if you hadn’t given me good pointers to prove that I could generate the PList files.

MartinO

Following up on this, I have made a change to my local copy of ‘ccc-analyzer’ that compensates for the Windows vs Cygwin path differences. In the subroutine ‘sub Analyze’ I have changed the lines:

Create arguments for doing static analysis.

if (defined $ResultFile) {

push @Args, ‘-o’, $ResultFile;

}

elsif (defined $HtmlDir) {

push @Args, ‘-o’, $HtmlDir;

}

to:

Create arguments for doing static analysis.

if (defined $ResultFile) {

push @Args, ‘-o’, $ResultFile;

}

elsif (defined $HtmlDir) {

  • Convert the output path to the Windows form on Cygwin

  • if ($^O =~/cygwin/) {

  • my $winHtmlDir = cygpath -m $HtmlDir;

  • $winHtmlDir =~ tr/\n\r//d; # Strip newlines

  • push @Args, ‘-o’, $winHtmlDir;

  • } else {

push @Args, ‘-o’, $HtmlDir;

  • }

}

I have used ‘tr’ rather than ‘chomp’ because I have found that ‘chomp’ does not always remove CRs and is not as robust as using this ‘tr’ pattern. Also, I use the ‘-m’ option to ‘cygpath’ rather than ‘-w’ because it uses the ‘/’ character for directory path separators rather than ‘\’. The latter causes all sorts of trouble in Perl, Python, Bash and so on; while Windows applications are perfectly happy with the ‘/’ character (WIN32’s file open functions accept either).

All the best and thanks to each of you for your advice and help,

MartinO

Great to hear that you found a solution!

Feel free to submit a patch; it might help others with the same setup:
http://llvm.org/docs/Phabricator.html
http://llvm.org/docs/DeveloperPolicy.html

Anna.

Thanks Anna,

I have it all working really well now, but I want to make sure that I have not broken anything else, so I will wait until I have tried some other scenarios before proposing a general patch; one case that occurred to me is when ‘clang.exe’ is built with the Cygwin or MINGW version of GCC my patch might break them, or at the very least result in warnings about the path conventions. I think I can make this more adaptive by examining the output from the ‘-###’ invocation and make the ‘cygpath’ trick automatic and appropriate to the actual compiler.

The feedback I got from you all on this has my build working very nicely now J

MartinO