[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-makeruns an SSAF workflow over a project built
with make.clang-ssaf-driveris 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-linkerwith 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, andclang-ssaf-analyzerruns
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.
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
- Is
clang/toolsthe right place for these tools, next to
clang-ssaf-linkerandclang-ssaf-analyzer? Or should they go to
clang-tools-extra, or somewhere else? - Is it OK for them to be written in Python? There’s a precedent in
scan-build-py.