Xazax-hun / csa-testbench

Toolset to do differential testing on the Clang Static Analyzer
MIT License
19 stars 6 forks source link

Clang Static Analyzer Test Bench

The CSA test bench is a collection of tools that aim to ease Clang Static Analyzer development by finding projects on which certain checks or analyzer engine changes can be tested and by making the evaluation of the results easier.

Generate Project List utility

The generate_project_list.py utility can help discover relevant projects on which a check or a change can be tested. It generates a list of projects that use a certain API or language construct extensively, based on SearchCode query results.

Example usage:

python generate_project_list.py 'pthread_mutex_t' 'C C++' 5 --output pthread.json

The above command will generate a list of 5 projects written in either C or C++ that use pthread_mutex_t.

Generate Project List from Debian packages

It is also possible to create a project list file from Debian source packages. The resulting file will contain lots of unsupported projects, so we advise filtering the list afterward.

python project_list_from_debian.py -u ftp://ftp.se.debian.org/debian/ --output debian.json

We do plan to include additional facilities to help with filtering the list and building the packages in the future.

Running Experiments

The run_experiments.py script runs the Clang Static Analyzer on a set of projects and generates a detailed report from the results. It downloads the projects specified in a JSON config file from a git repository or a tarball, infers the build system, generates a build log, runs the Static Analyzer, collects the output, and generates an HTML report that holds all information needed to reproduce the same experiment.

Example usage:

python run_experiments.py --config projects.json --jobs 8

Note that the CodeChecker server at the URL specified in the config file needs to be started separately before running an experiment.

Example configuration:

{
  "projects": [
    {
      "name": "tmux",
      "url": "https://github.com/tmux/tmux.git",
      "tag": "2.6"
    },
    {
      "name": "curl",
      "url": "https://github.com/curl/curl.git"
    }
  ],
  "configurations": [
    {
      "name": "baseline",
      "clang_sa_args": "-Xclang -analyzer-stats"
    },
    {
      "name": "unroll",
      "clang_sa_args": "-Xclang -analyzer-stats -Xclang -analyzer-config -Xclang unroll-loops=true,cfg-loopexit=true"
    }
  ],
  "CodeChecker": {
    "url": "http://localhost:15010/Default"
  }
}

Example report:

Example report

Dependencies

In order for this set of scripts to work, CodeChecker needs to be installed and available in the PATH. Packages from the python_requirements file should also be installed after CodeChecker's virtual environment was activated. Example setup below:

cd path/to/CodeChecker
make venv
source venv/bin/activate
make package
export PATH="$PWD/build/CodeChecker/bin:$PATH"
cd path/to/csa-testbench
pip install -r python_requirements

If the cloc utility is in the path, the script will also count the lines of code of the analyzed projects and include them in the final report.

If clang is compiled with statistics enabled, the scripts will collect and include them in the final report.

If line based code coverage support is present, the script can collect coverage data and include it in the final report. Note that this requires a patched version of clang, this feature is not upstreamed yet. For the code coverage collection support to work, you need to have the MergeCoverage.py script and gcovr utility in the PATH.

Add the "coverage": true line to the configuration in which you want to record the coverage:

{
  "projects": [ ... ],
  "configurations": [
    {
      "name": "your_configuration",
      "coverage": true
    },
    ...
  ],
  "CodeChecker": { ... }
}

Example coverage report:

Coverage report

Configuration

A minimal configuration should contain a list of projects and a CodeChecker URL. Each project should at least contain a git or tarball URL and a name. Other configuration values are optional.

{
  "projects": [
    {
      "name": "tmux",
      "url": "https://github.com/tmux/tmux.git",
      "tag": "2.6",
      "skip": [ "-*/cmd-break-pane.c*", "-*/cmd-bind-key.c*"],
      "configure_command": "sh autogen.sh && ./configure",
      "configurations": [
        {
          "name": "original"
        },
        {
          "name": "with_stats",
          "clang_sa_args": "-Xclang -analyzer-stats"
        }
      ]
    },
    {
      "name": "SQLite",
      "url": "https://www.sqlite.org/2018/sqlite-autoconf-3230000.tar.gz"
    },
    {
      "name": "bitcoin",
      "url": "https://github.com/bitcoin/bitcoin.git",
      "tag": "v0.15.1",
      "clang_sa_args": "-Xclang -analyzer-stats"
    },
    {
      "name": "redis",
      "url": "https://github.com/antirez/redis.git",
      "tag": "727dd43614ec45e23e2dedbba08b393323feaa4f",
      "make_command": "make -j $JOBS",
      "binary_dir": "build"
    },
    {
      "name": "xerces-c",
      "url": "https://github.com/apache/xerces-c.git",
      "prepared": true
    },
    {
      "name": "icu",
      "url": "http://download.icu-project.org/files/icu4c/61.1/icu4c-61_1-src.tgz",
      "source_dir": "source"
    },
    {
      "name": "llvm",
      "url": "https://github.com/llvm-mirror/llvm.git",
      "binary_dir": "build",
      "subprojects": [
        {
          "name": "clang",
          "subdir": "tools/clang",
          "url": "https://github.com/llvm-mirror/clang.git"
        }
      ]
    },
    {
      "name": "zlibconan",
      "package": "zlib/1.2.11@conan/stable",
      "package_type": "conan"
    },
    {
      "name": "zlibvcpkg",
      "package": "zlib",
      "package_type": "vcpkg"
    }
  ],
  "configurations": [
    {
      "name": "original",
      "clang_sa_args": "",
      "clang_tidy_args": "",
      "analyze_args": "",
      "store_args": "",
      "clang_path": ""
    },
    {
      "name": "with_stats",
      "clang_sa_args": "-Xclang -analyzer-stats",
      "coverage": true
    }
  ],
  "charts": ["Coverage", "Duration", "Result count"],
  "CodeChecker": {
    "url": "http://localhost:8001/Default",
    "analyze_args": "",
    "store_args": "",
    "analyzers": "clangsa clang-tidy"
  }
}

Optional configuration values

Limitations

These scripts will not figure out the dependencies of a project. It is the user's responsibility to make sure that the projects in the configuration file can be compiled on the machine on which the experiments are run.

Profiling Clang-Tidy checks

Clang-Tidy version 7.0 and onwards supports profiling each individual check executed, per translation unit. This can be useful to generate statistics when the performance of Tidy is in question. You can ask Clang-Tidy to generate an output by specifying the following extra arguments to the clang_tidy_args of the configuration:

  "configurations": [
    {
      "name": "tidy-original",
      "clang_tidy_args": "--enable-check-profile --store-check-profile=tidy-original"
    }
  ]

The parameter of the --store-check-profile MUST be the same as the name of the configuration. Additional options for configuring Clang SA or the entire analysis itself can be specified, profiling Clang-Tidy isn't mutually exclusive with the "normal" run_experiments.py features.

Once configured appropriately, please execute run_experiments.py normally. After the analyses had concluded, execute tidy_profiling_sum.py with the same --config file and the projects directory, created by run_experiments.py as --dir.

The second script, tidy_profiling_sum.py will collect and collate the results emitted by Tidy, and create tidy_results.html under the --dir directory that contains details about checks' execution time and graphs.

Configuration

To specify a custom human-readable title for the generated charts, an optional tidy_chart_title can be given for each configurations entry:

  "configurations": [
    {
      "name": "tidy-original",
      "tidy_chart_title": "Tidy (stock)",
      "clang_tidy_args": "--enable-check-profile --store-check-profile=tidy-original"
    }
  ]

Measuring bug path length statistics

The bug_stats.py file can be used to calculate descriptive statistics from the results of the analysis. It takes the "product URL" argument of a running CodeChecker server (--url http://localhost:8001/Default) and some project names (--name Project1 Project2, or --all) and generates statistics and histograms for each project given.

bug_stats.py --url http://example.org:8080/MyProduct --name my_run

Example bug statistics

This script also supports generating statistics from the difference of two runs, based on the bug reports presented by CodeChecker cmd diff:

bug_stats.py --url http://example.org:8080/MyProduct --diff \
  --basename baseline --newname csa_patched --new

Other Tips and Tricks

CReduce diffs

CReduce is a useful tool to get minimal reproducers for compiler bugs. It is also great for getting minimal reproducers for assertion fails or crashes of static analysis tools. Moreover, we can use this tool to get minimal regressions or improvements between two versions of an analysis engine.

This section is a small example of how to do it for the Clang Static Analyzer.

  1. Create the file to reduce

    Create a preprocessed file for which the two versions of the analyzer give different results

  2. Create the reduce script

    Create a script that executes both versions of the analyzer on the preprocessed file and compares the output. Note that this script should return 0 if a transformation of creduce preserved the property we want, and non-zero otherwise. In case one of the implementations is crashed, we should return non-zero since our goal is not to get minimal crash reproducers.

    # reduce.sh
    set -e
    /path/to/basline/clang --analyze preprocessed.c 2> out1.txt
    /path/to/modified/clang --analyze preprocessed.c 2> out2.txt
    
    ! cmp --silent out1.txt out2.txt
  3. Execute creduce and wait for the results

    creduce reduce.sh preprocessed.c --n 8

Note that the final result might contain a warning that differs from the initials.