[RFC] Running SSAF over whole projects: clang-ssaf-orchestrator-make and clang-ssaf-driver

[RFC] Running SSAF over whole projects: clang-ssaf-orchestrator-make and clang-ssaf-driver

TL;DR

We’d like to add two command-line tools:

  • clang-ssaf-orchestrator-make runs an SSAF workflow over a project built
    with make.
  • clang-ssaf-driver is the user interface for SSAF analyses and
    transformations. The user picks a transformation, gives it the build
    command, and gets back the diagnostics or patch and a report respectively.

The problem

SSAF (RFC)
consists of several components. Each of them works on a single translation
unit or a single link unit:

Step Tool Runs once per
Summary extraction clang --ssaf-extract-summaries= compilation
Entity linking clang-ssaf-linker link
Whole-program analysis clang-ssaf-analyzer top-most link unit
Source-edit generation clang --ssaf-source-transformation= compilation, in a second build
Edit and report merging clang-ssaf-src-edit-merge, clang-ssaf-report-merge, clang-ssaf-conflict-merge link, in the second build

To run them over a project you have to somehow follow its build graph. Each
compilation needs its summary extracted, and each link needs the summaries of
its inputs linked, just like the build links the object files. A
transformation then needs a second pass over the whole thing, because source
edits are generated from the analysis result, and we only have that once the
whole program has been summarized.

The proposal

clang-ssaf-orchestrator-make integrates SSAF with make. It runs the build
and, for every step that produces a binary build artifact, runs the
corresponding SSAF step:

$ clang-ssaf-orchestrator-make \
    --extract-summaries=PointerFlow,TypeConstrainedPointers,UnsafeBufferUsage,VirtualMethod \
    --analysis=UnsafeBufferReachableAnalysisResult \
    -o ssaf-output -- make -j8

clang-ssaf-driver is meant for end users and hides the implementation
details. A transformation author defines a transformation as a {summary
extraction, analysis, transformation} triple and gives it a user-facing name.
cpp-bounded-buffers is the first one. Analyses will work the same way,
defined as a {summary extraction, analysis} pair with a name.

$ clang-ssaf-driver --transformation=cpp-bounded-buffers -- make -j8

The relationship between the two tools is similar to the one between
cc1 and the clang driver.

Implementation

Make orchestrator

The orchestrator is based on toolchain interposition. The build runs as usual, and the
orchestrator gets involved whenever the build invokes a compiler or a linker.

Before starting the build, the orchestrator creates a directory of shims and
prepends it to PATH. The shims are symlinks named after tools like clang++
or ld, so when the build invokes one of these tools, it actually runs the
shim. Each shim first runs the real tool with the original arguments, and
then the corresponding SSAF step:

  • Compilation: the shim asks the build’s compiler driver (with -###) which
    frontend jobs the command consists of, and runs each of them again with the
    SSAF clang and the --ssaf-* options added. This way summary extraction
    sees exactly what the build compiled.
  • Link: the shim records which binary artifacts were linked, and runs clang-ssaf-linker with the SSAF build artifacts related to the binary build artifacts. The final link, whose output isn’t
    consumed by anything else, is the top-most link unit, and clang-ssaf-analyzer runs
    on its summary.
  • Transformation: the orchestrator runs the build a second time. This time the
    shims generate source edits for each compilation, and merge the edits and
    reports at each link.

Static libraries work the same way. An ar shim links the summaries of the
archive members, and a link that uses the archive picks up that summary.

We plan to add shims for other toolchain components as necessary. For
example, a shim for lipo on macOS, which combines binaries built for
different architectures into one universal binary.

The orchestrator keeps track of which SSAF artifacts belong to which binary
build artifact: the summary of an object file, the linked summary of a
program, and in the second pass their source edits and reports. The orchestrator also
records which artifacts are input to each link. This information is used to pass the right inputs to SSAF tools.

Longer term, we want a build-system integration layer, with one integration
per build system, each running the same SSAF steps at the same points in the
build. Interposition is not the right answer for every build system.
For example, for Swift Build, we are integrating SSAF directly in the build system implementation.

Swift Build Integration RFC

Driver

The driver puts user-facing names to analysis and transformation workflow implementations: which summaries to
extract, which analysis to run, and for transformations, which transformation
to apply. It then runs the build-system integration for the given build command (currently only the make
orchestrator) with that workflow and passes the integration’s own options.
Once integrations for other build systems are available, the driver will use
them the same way.

For transformation, the driver writes the merged source edits as a patch
that can be applied with patch. It prints the transformation report to
stderr in the same format as compiler diagnostics, and also writes it as
SARIF. For analysis, only the diagnostics are emitted.

Scope

  • POSIX only. Windows is not supported.
  • A single build sub-tree. The build has to end in a single link, so a make all
    that builds several programs isn’t supported for now. In the future we plan
    to support more kinds of build steps that aggregate build artifacts, such as combining
    multi-architecture libraries.

Both tools are written in Python and only use the standard library.

PRs

Questions

  1. Is clang/tools the right place for these tools, next to
    clang-ssaf-linker and clang-ssaf-analyzer? Or should they go to
    clang-tools-extra, or somewhere else?
  2. Is it OK for them to be written in Python? There’s a precedent in
    scan-build-py.