| # Source-based Code Coverage | |
| ## Introduction | |
| This document explains how to use clang's source-based code coverage feature. | |
| It's called "source-based" because it operates on AST and preprocessor | |
| information directly. This allows it to generate very precise coverage data. | |
| Clang ships two other code coverage implementations: | |
| - {doc}`SanitizerCoverage` - A low-overhead tool meant for use alongside the | |
| various sanitizers. It can provide up to edge-level coverage. | |
| - gcov - A GCC-compatible coverage implementation which operates on DebugInfo. | |
| This is enabled by `-ftest-coverage` or `--coverage`. | |
| From this point onwards "code coverage" will refer to the source-based kind. | |
| ## The code coverage workflow | |
| The code coverage workflow consists of three main steps: | |
| - Compiling with coverage enabled. | |
| - Running the instrumented program. | |
| - Creating coverage reports. | |
| The next few sections work through a complete, copy-'n-paste friendly example | |
| based on this program: | |
| ```cpp | |
| % cat <<EOF > foo.cc | |
| #define BAR(x) ((x) || (x)) | |
| template <typename T> void foo(T x) { | |
| for (unsigned I = 0; I < 10; ++I) { BAR(I); } | |
| } | |
| int main() { | |
| foo<int>(0); | |
| foo<float>(0); | |
| return 0; | |
| } | |
| EOF | |
| ``` | |
| ## Compiling with coverage enabled | |
| To compile code with coverage enabled, pass `-fprofile-instr-generate | |
| -fcoverage-mapping` to the compiler: | |
| ```console | |
| # Step 1: Compile with coverage enabled. | |
| % clang++ -fprofile-instr-generate -fcoverage-mapping foo.cc -o foo | |
| ``` | |
| Note that linking together code with and without coverage instrumentation is | |
| supported. Uninstrumented code simply won't be accounted for in reports. | |
| To compile code with Modified Condition/Decision Coverage (MC/DC) enabled, | |
| pass `-fcoverage-mcdc` in addition to the clang options specified above. | |
| MC/DC is an advanced form of code coverage most applicable to the embedded | |
| space. | |
| ## Running the instrumented program | |
| The next step is to run the instrumented program. When the program exits, it | |
| will write a **raw profile** to the path specified by the `LLVM_PROFILE_FILE` | |
| environment variable. If that variable does not exist, the profile is written | |
| to `default.profraw` in the current directory of the program. If | |
| `LLVM_PROFILE_FILE` specifies a path to a non-existent directory, the missing | |
| directory structure will be created. Additionally, the following special | |
| **pattern strings** are rewritten: | |
| - "%p" expands out to the process ID. | |
| - "%h" expands out to the hostname of the machine running the program. | |
| - "%t" expands out to the value of the `TMPDIR` environment variable. On | |
| Darwin, this is typically set to a temporary scratch directory. | |
| - "%Nm" expands out to the instrumented binary's signature. When this pattern | |
| is specified, the runtime creates a pool of N raw profiles which are used for | |
| on-line profile merging. The runtime takes care of selecting a raw profile | |
| from the pool, locking it, and updating it before the program exits. If N is | |
| not specified (i.e the pattern is "%m"), it's assumed that `N = 1`. The | |
| merge pool specifier can only occur once per filename pattern. | |
| - "%b" expands out to the binary ID (build ID). It can be used with "%Nm" to | |
| avoid binary signature collisions. To use it, the program should be compiled | |
| with the build ID linker option (`--build-id` for GNU ld or LLD, | |
| `/build-id` for lld-link on Windows). Linux, Windows, and AIX are supported. | |
| - "%c" expands out to nothing, but enables a mode in which profile counter | |
| updates are continuously synced to a file. This means that if the | |
| instrumented program crashes, or is killed by a signal, perfect coverage | |
| information can still be recovered. Continuous mode does not support value | |
| profiling for PGO, and is only supported on Darwin at the moment. Support for | |
| Linux may be mostly complete but requires testing, and support for Windows | |
| may require more extensive changes: please get involved if you are interested | |
| in porting this feature. | |
| ```console | |
| # Step 2: Run the program. | |
| % LLVM_PROFILE_FILE="foo.profraw" ./foo | |
| ``` | |
| Note that continuous mode is also used on Fuchsia where it's the only supported | |
| mode, but the implementation is different. The Darwin and Linux implementation | |
| relies on padding and the ability to map a file over the existing memory | |
| mapping which is generally only available on POSIX systems and isn't suitable | |
| for other platforms. | |
| On Fuchsia, we rely on the ability to relocate counters at runtime using a | |
| level of indirection. On every counter access, we add a bias to the counter | |
| address. This bias is stored in `__llvm_profile_counter_bias` symbol that's | |
| provided by the profile runtime and is initially set to zero, meaning no | |
| relocation. The runtime can map the profile into memory at arbitrary locations, | |
| and set bias to the offset between the original and the new counter location, | |
| at which point every subsequent counter access will be to the new location, | |
| which allows updating profile directly akin to the continuous mode. | |
| The advantage of this approach is that it doesn't require any special OS support. | |
| The disadvantage is the extra overhead due to additional instructions required | |
| for each counter access (overhead both in terms of binary size and performance) | |
| plus duplication of counters (i.e. one copy in the binary itself and another | |
| copy that's mapped into memory). This implementation can be also enabled for | |
| other platforms by passing the `-runtime-counter-relocation` option to the | |
| backend during compilation. | |
| For a program such as the [Lit](https://llvm.org/docs/CommandGuide/lit.html) | |
| testing tool, which invokes other programs, it may be necessary to set | |
| `LLVM_PROFILE_FILE` for each invocation. The pattern strings "%p" or "%Nm" | |
| may help to avoid corruption due to concurrency. Note that "%p" is also a Lit | |
| token and needs to be escaped as "%%p". | |
| ```console | |
| % clang++ -fprofile-instr-generate -fcoverage-mapping -mllvm -runtime-counter-relocation foo.cc -o foo | |
| ``` | |
| ## Creating coverage reports | |
| Raw profiles must be **indexed** before they can be used to generate | |
| coverage reports. This is done using the "merge" tool in `llvm-profdata` | |
| (which can combine multiple raw profiles and index them at the same time): | |
| ```console | |
| # Step 3(a): Index the raw profile. | |
| % llvm-profdata merge -sparse foo.profraw -o foo.profdata | |
| ``` | |
| For an example of merging multiple profiles created by testing, | |
| see the LLVM [coverage build script](https://github.com/llvm/llvm-zorg/blob/main/zorg/jenkins/jobs/jobs/llvm-coverage). | |
| There are multiple different ways to render coverage reports. The simplest | |
| option is to generate a line-oriented report: | |
| ```console | |
| # Step 3(b): Create a line-oriented coverage report. | |
| % llvm-cov show ./foo -instr-profile=foo.profdata | |
| ``` | |
| This report includes a summary view as well as dedicated sub-views for | |
| templated functions and their instantiations. For our example program, we get | |
| distinct views for `foo<int>(...)` and `foo<float>(...)`. If | |
| `-show-line-counts-or-regions` is enabled, `llvm-cov` displays sub-line | |
| region counts (even in macro expansions): | |
| ```none | |
| 1| 20|#define BAR(x) ((x) || (x)) | |
| ^20 ^2 | |
| 2| 2|template <typename T> void foo(T x) { | |
| 3| 22| for (unsigned I = 0; I < 10; ++I) { BAR(I); } | |
| ^22 ^20 ^20^20 | |
| 4| 2|} | |
| ------------------ | |
| | void foo<int>(int): | |
| | 2| 1|template <typename T> void foo(T x) { | |
| | 3| 11| for (unsigned I = 0; I < 10; ++I) { BAR(I); } | |
| | ^11 ^10 ^10^10 | |
| | 4| 1|} | |
| ------------------ | |
| | void foo<float>(int): | |
| | 2| 1|template <typename T> void foo(T x) { | |
| | 3| 11| for (unsigned I = 0; I < 10; ++I) { BAR(I); } | |
| | ^11 ^10 ^10^10 | |
| | 4| 1|} | |
| ------------------ | |
| ``` | |
| If `--show-branches=count` and `--show-expansions` are also enabled, the | |
| sub-views will show detailed branch coverage information in addition to the | |
| region counts: | |
| ```none | |
| ------------------ | |
| | void foo<float>(int): | |
| | 2| 1|template <typename T> void foo(T x) { | |
| | 3| 11| for (unsigned I = 0; I < 10; ++I) { BAR(I); } | |
| | ^11 ^10 ^10^10 | |
| | ------------------ | |
| | | | 1| 10|#define BAR(x) ((x) || (x)) | |
| | | | ^10 ^1 | |
| | | | ------------------ | |
| | | | | Branch (1:17): [True: 9, False: 1] | |
| | | | | Branch (1:24): [True: 0, False: 1] | |
| | | | ------------------ | |
| | ------------------ | |
| | | Branch (3:23): [True: 10, False: 1] | |
| | ------------------ | |
| | 4| 1|} | |
| ------------------ | |
| ``` | |
| If the application was instrumented for Modified Condition/Decision Coverage | |
| (MC/DC) using the clang option `-fcoverage-mcdc`, an MC/DC subview can be | |
| enabled using `--show-mcdc` that will show detailed MC/DC information for | |
| each complex condition boolean expression containing at most six conditions. | |
| To generate a file-level summary of coverage statistics instead of a | |
| line-oriented report, try: | |
| ```console | |
| # Step 3(c): Create a coverage summary. | |
| % llvm-cov report ./foo -instr-profile=foo.profdata | |
| Filename Regions Missed Regions Cover Functions Missed Functions Executed Lines Missed Lines Cover Branches Missed Branches Cover | |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| /tmp/foo.cc 13 0 100.00% 3 0 100.00% 13 0 100.00% 12 2 83.33% | |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| TOTAL 13 0 100.00% 3 0 100.00% 13 0 100.00% 12 2 83.33% | |
| ``` | |
| The `llvm-cov` tool supports specifying a custom demangler, writing out | |
| reports in a directory structure, and generating HTML reports. For the full | |
| list of options, please refer to the [command guide](https://llvm.org/docs/CommandGuide/llvm-cov.html). | |
| A few final notes: | |
| - The `-sparse` flag is optional but can produce dramatically smaller | |
| indexed profiles. This option should not be used if the indexed profile will | |
| be reused for PGO. | |
| - Raw profiles can be discarded after they are indexed. Advanced use of the | |
| profile runtime library allows an instrumented program to merge profiling | |
| information directly into an existing raw profile on disk. The details are | |
| out of scope. | |
| - The `llvm-profdata` tool can be used to merge multiple raw or | |
| indexed profiles. To combine profiling data from multiple runs of a program, | |
| try e.g: | |
| ```console | |
| % llvm-profdata merge -sparse foo1.profraw foo2.profdata -o foo3.profdata | |
| ``` | |
| ## Exporting coverage data | |
| Coverage data can be exported into JSON using the `llvm-cov export` | |
| sub-command. There is a comprehensive reference which defines the structure of | |
| the exported data at a high level in the llvm-cov source code. | |
| ## Interpreting reports | |
| There are six statistics tracked in a coverage summary: | |
| - Function coverage is the percentage of functions which have been executed at | |
| least once. A function is considered to be executed if any of its | |
| instantiations are executed. | |
| - Instantiation coverage is the percentage of function instantiations which | |
| have been executed at least once. Template functions and static inline | |
| functions from headers are two kinds of functions which may have multiple | |
| instantiations. This statistic is hidden by default in reports, but can be | |
| enabled via the `-show-instantiation-summary` option. | |
| - Line coverage is the percentage of code lines which have been executed at | |
| least once. Only executable lines within function bodies are considered to be | |
| code lines. | |
| - Region coverage is the percentage of code regions which have been executed at | |
| least once. A code region may span multiple lines (e.g in a large function | |
| body with no control flow). However, it's also possible for a single line to | |
| contain multiple code regions (e.g in "return x || y && z"). | |
| - Branch coverage is the percentage of "true" and "false" branches that have | |
| been taken at least once. Each branch is tied to individual conditions in the | |
| source code that may each evaluate to either "true" or "false". These | |
| conditions may comprise larger boolean expressions linked by boolean logical | |
| operators. For example, "x = (y == 2) || (z < 10)" is a boolean expression | |
| comprised of two individual conditions, each of which evaluates to | |
| either true or false, producing four total branch outcomes. | |
| - Modified Condition/Decision Coverage (MC/DC) is the percentage of individual | |
| branch conditions that have been shown to independently affect the decision | |
| outcome of the boolean expression they comprise. This is accomplished using | |
| the analysis of executed control flow through the expression (i.e. test | |
| vectors) to show that as a condition's outcome is varied between "true" and | |
| false", the decision's outcome also varies between "true" and false", while | |
| the outcome of all other conditions is held fixed (or they are masked out as | |
| unevaluatable, as happens in languages whose logical operators have | |
| short-circuit semantics). MC/DC builds on top of branch coverage and | |
| requires that all code blocks and all execution paths have been tested. This | |
| statistic is hidden by default in reports, but it can be enabled via the | |
| `-show-mcdc-summary` option as long as code was also compiled using the | |
| clang option `-fcoverage-mcdc`. | |
| - Boolean expressions comprised of only one condition (and therefore | |
| have no logical operators) are not included in MC/DC analysis and are | |
| trivially deducible using branch coverage. | |
| Of these six statistics, function coverage is usually the least granular while | |
| branch coverage (with MC/DC) is the most granular. 100% branch coverage for a | |
| function implies 100% region coverage for a function. The project-wide totals | |
| for each statistic are listed in the summary. | |
| ## Format compatibility guarantees | |
| - There are no backwards or forwards compatibility guarantees for the raw | |
| profile format. Raw profiles may be dependent on the specific compiler | |
| revision used to generate them. It's inadvisable to store raw profiles for | |
| long periods of time. | |
| - Tools must retain **backwards** compatibility with indexed profile formats. | |
| These formats are not forwards-compatible: i.e, a tool which uses format | |
| version X will not be able to understand format version (X+k). | |
| - Tools must also retain **backwards** compatibility with the format of the | |
| coverage mappings emitted into instrumented binaries. These formats are not | |
| forwards-compatible. | |
| - The JSON coverage export format has a (major, minor, patch) version triple. | |
| Only a major version increment indicates a backwards-incompatible change. A | |
| minor version increment is for added functionality, and patch version | |
| increments are for bugfixes. | |
| ## Impact of llvm optimizations on coverage reports | |
| llvm optimizations (such as inlining or CFG simplification) should have no | |
| impact on coverage report quality. This is due to the fact that the mapping | |
| from source regions to profile counters is immutable, and is generated before | |
| the llvm optimizer kicks in. The optimizer can't prove that profile counter | |
| instrumentation is safe to delete (because it's not: it affects the profile the | |
| program emits), and so leaves it alone. | |
| Note that this coverage feature does not rely on information that can degrade | |
| during the course of optimization, such as debug info line tables. | |
| ## Using the profiling runtime without static initializers | |
| By default the compiler runtime uses a static initializer to determine the | |
| profile output path and to register a writer function. To collect profiles | |
| without using static initializers, do this manually: | |
| - Export an `int __llvm_profile_runtime` symbol from each instrumented shared | |
| library and executable. When the linker finds a definition of this symbol, it | |
| knows to skip loading the object which contains the profiling runtime's | |
| static initializer. | |
| - Include `<profile/instr_prof_interface.h>` to declare the profiling runtime | |
| APIs. (If including compiler interface headers is challenging in your build | |
| environment, all of the functions below can instead be forward-declared | |
| directly, using `extern "C"` in C++ files.) | |
| - Call `void __llvm_profile_initialize_file(void)` once from each instrumented | |
| executable. This function parses `LLVM_PROFILE_FILE`, sets the output path, | |
| and truncates any existing files at that path. To get the same behavior | |
| without truncating existing files, pass a filename pattern string to `void | |
| __llvm_profile_set_filename(const char *)`. These calls can be placed | |
| anywhere so long as they precede all calls to `__llvm_profile_write_file`. | |
| - Note: You can call `const char *__llvm_profile_get_filename(void)` to get | |
| the currently configured filename. This returns a `malloc`-allocated string | |
| that must be passed to `free()` (or a *static* `""` on allocation failure). | |
| - Call `int __llvm_profile_write_file(void)` to write out a profile. This | |
| function returns 0 on success, and a non-zero value otherwise. Calling this | |
| function multiple times appends profile data to an existing on-disk raw | |
| profile. | |
| ### Using the profiling runtime without a filesystem | |
| The profiling runtime also supports freestanding environments that lack a | |
| filesystem. The runtime ships as a static archive that's structured to make | |
| dependencies on a hosted environment optional, depending on what features | |
| the client application uses. | |
| The first step is to export `__llvm_profile_runtime`, as above, to disable | |
| the default static initializers. Instead of calling the `*_file()` APIs | |
| described above, use the following functions from | |
| `<profile/instr_prof_interface.h>` to save the profile directly to a buffer | |
| under your control: | |
| - Call `uint64_t __llvm_profile_get_size_for_buffer(void)` to determine the | |
| size of the profile. You'll need to allocate a buffer of this size. | |
| - Call `int __llvm_profile_write_buffer(char *Buffer)` to copy the current | |
| counters to `Buffer`, which is expected to already be allocated and big | |
| enough for the profile. | |
| - Optionally, call `void __llvm_profile_reset_counters(void)` to reset the | |
| counters before entering a specific section to be profiled. This is only | |
| useful if there is some setup that should be excluded from the profile. | |
| You can also merge raw profile data from an existing buffer into the current | |
| process's in-memory counters: | |
| - Call `int __llvm_profile_check_compatibility(const char *ProfileData, | |
| uint64_t ProfileSize)` to verify that the raw profile in `ProfileData` (of | |
| size `ProfileSize` bytes) was generated by the same binary and structurally | |
| matches the in-process counters and bitmaps. This function returns 0 on | |
| success, and a non-zero value otherwise. | |
| - Call `int __llvm_profile_merge_from_buffer(const char *ProfileData, uint64_t | |
| ProfileSize)` to merge the raw profile in `ProfileData` into the in-process | |
| counters and bitmaps. **The caller is expected to have verified compatibility | |
| beforehand.** This function returns 0 on success, and a non-zero value if the | |
| profile data is invalid or corrupted. | |
| ## Collecting coverage reports for the llvm project | |
| To prepare a coverage report for llvm (and any of its sub-projects), add | |
| `-DLLVM_BUILD_INSTRUMENTED_COVERAGE=On` to the CMake configuration. Raw | |
| profiles will be written to `$BUILD_DIR/profiles/`. To prepare an html | |
| report, run `llvm/utils/prepare-code-coverage-artifact.py`. | |
| To specify an alternate directory for raw profiles, use | |
| `-DLLVM_PROFILE_DATA_DIR`. To change the size of the profile merge pool, use | |
| `-DLLVM_PROFILE_MERGE_POOL_SIZE`. | |
| ## Drawbacks and limitations | |
| - Prior to version 2.26, the GNU binutils BFD linker cannot link programs | |
| compiled with `-fcoverage-mapping` in its `--gc-sections` mode. Possible | |
| workarounds include disabling `--gc-sections`, upgrading to a newer version | |
| of BFD, or using the Gold linker. | |
| - Code coverage does not handle unpredictable changes in control flow or stack | |
| unwinding in the presence of exceptions precisely. Consider the following | |
| function: | |
| ```cpp | |
| int f() { | |
| may_throw(); | |
| return 0; | |
| } | |
| ``` | |
| If the call to `may_throw()` propagates an exception into `f`, the code | |
| coverage tool may mark the `return` statement as executed even though it is | |
| not. A call to `longjmp()` can have similar effects. | |
| ## Clang implementation details | |
| This section may be of interest to those wishing to understand or improve | |
| the clang code coverage implementation. | |
| ### Gap regions | |
| Gap regions are source regions with counts. A reporting tool cannot set a line | |
| execution count to the count from a gap region unless that region is the only | |
| one on a line. | |
| Gap regions are used to eliminate unnatural artifacts in coverage reports, such | |
| as red "unexecuted" highlights present at the end of an otherwise covered line, | |
| or blue "executed" highlights present at the start of a line that is otherwise | |
| not executed. | |
| ### Branch regions | |
| When viewing branch coverage details in source-based file-level sub-views using | |
| `--show-branches`, it is recommended that users show all macro expansions | |
| (using option `--show-expansions`) since macros may contain hidden branch | |
| conditions. The coverage summary report will always include these macro-based | |
| boolean expressions in the overall branch coverage count for a function or | |
| source file. | |
| Branch coverage is not tracked for constant folded branch conditions since | |
| branches are not generated for these cases. In the source-based file-level | |
| sub-view, these branches will simply be shown as `[Folded - Ignored]` so that | |
| users are informed about what happened. | |
| Branch coverage is tied directly to branch-generating conditions in the source | |
| code. Users should not see hidden branches that aren't actually tied to the | |
| source code. | |
| ### MC/DC Instrumentation | |
| When instrumenting for Modified Condition/Decision Coverage (MC/DC) using the | |
| clang option `-fcoverage-mcdc`, there are two hard limits. | |
| The maximum number of terms is limited to 32767, which is practical for | |
| handwritten expressions. To be more restrictive in order to enforce coding rules, | |
| use `-Xclang -fmcdc-max-conditions=n`. Expressions with exceeded condition | |
| counts `n` will generate warnings and will be excluded in the MC/DC coverage. | |
| The number of test vectors (the maximum number of possible combinations of | |
| expressions) is limited to 2,147,483,646. In this case, approximately | |
| 256MiB (==2GiB/8) is used to record test vectors. | |
| To reduce memory usage, users can limit the maximum number of test vectors per | |
| expression with `-Xclang -fmcdc-max-test-vectors=m`. | |
| If the number of test vectors resulting from the analysis of an expression | |
| exceeds `m`, a warning will be issued and the expression will be excluded | |
| from the MC/DC coverage. | |
| The number of test vectors `m`, for `n` terms in an expression, can be | |
| `m <= 2^n` in the theoretical worst case, but is usually much smaller. | |
| In simple cases, such as expressions consisting of a sequence of single | |
| operators, `m == n+1`. For example, `(a && b && c && d && e && f && g)` | |
| requires 8 test vectors. | |
| Expressions such as `((a0 && b0) || (a1 && b1) || ...)` can cause the | |
| number of test vectors to increase exponentially. | |
| ### Switch statements | |
| The region mapping for a switch body consists of a gap region that covers the | |
| entire body (starting from the '{' in 'switch (...) {', and terminating where the | |
| last case ends). This gap region has a zero count: this causes "gap" areas in | |
| between case statements, which contain no executable code, to appear uncovered. | |
| When a switch case is visited, the parent region is extended: if the parent | |
| region has no start location, its start location becomes the start of the case. | |
| This is used to support switch statements without a `CompoundStmt` body, in | |
| which the switch body and the single case share a count. | |
| For switches with `CompoundStmt` bodies, a new region is created at the start | |
| of each switch case. | |
| Branch regions are also generated for each switch case, including the default | |
| case. If there is no explicitly defined default case in the source code, a | |
| branch region is generated to correspond to the implicit default case that is | |
| generated by the compiler. The implicit branch region is tied to the line and | |
| column number of the switch statement condition since no source code for the | |
| implicit case exists. |