[clang] [Coverage] Preserve user macro expansion locations when remapping system macros (PR #228098)

via cfe-commits cfe-commits at lists.llvm.org
Thu Oct 1 07:49:47 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang-codegen

Author: Maxim Zhukov (Mephistophiles)

<details>
<summary>Changes</summary>

A system macro nested inside a user macro can cause coverage regions to be
attributed to the outermost physical invocation instead of the enclosing user
macro's virtual file.

For example, a macro-generated inline function can contain an assertion that
expands to `do {} while (false)`, where `false` is defined in a system header.
When system-header coverage is disabled, `gatherFileIDs` uses `getFileLoc` to
remap regions from the system macro. This can introduce a disconnected,
zero-length code region and branch at the declaration macro's invocation.
The resulting LCOV export can contain function and branch records for that
line without a corresponding line record. Consumers can then reject the
trace as inconsistent, including when deriving function end lines.

Walk through system macro expansions and preserve the first enclosing user
macro's virtual file. Use the immediate expansion range, converting a token
range to an exclusive end location. Keep the existing behavior for expansions
that reach a physical file directly and for explicit system-header coverage.

The regression test covers a system macro nested in a user statement macro
and a system-macro alias nested in a macro-generated function.

Validation on macOS arm64, Release build with assertions enabled:

- Built Clang from this branch.
- All 76 tests in `clang/test/CoverageMapping` passed (the initial run lacked `split-file`; the affected test passed after building it).
- The new regression test fails on unmodified Clang 22.1.8 with the expected zero-length regions.
- The `<stdbool.h>` / `NDEBUG` reproducer fails the LCOV structural checks before the fix and passes after it, both with and without calling the generated function.
- Additional local stress checks passed for 2, 3, 8, and 32 user macro levels combined with 1 and 8 system macro levels: all 18 LCOV tracefiles, including the original reproducer, satisfy the branch/line and function/last-line invariants.
- Macro-argument, function-like macro, token-pasting, and system/user/system nesting smoke checks compiled with system-header coverage both enabled and disabled.
- `git diff --check` and clang-format checks passed.

End-to-end checks linked the matching main-branch profile runtime. Profiles were converted through text using main-branch llvm-profdata and llvm-profdata 22.1.8, then exported with unmodified llvm-cov 22.1.8. The structural checker checks these two invariants only; this is not full LCOV consumer validation.

Assisted-by: OpenAI Codex


---
Full diff: https://github.com/llvm/llvm-project/pull/228098.diff


4 Files Affected:

- (modified) clang/lib/CodeGen/CoverageMappingGen.cpp (+23-3) 
- (added) clang/test/CoverageMapping/Inputs/nested-system-macro/system.h (+3) 
- (added) clang/test/CoverageMapping/Inputs/nested-system-macro/user.h (+10) 
- (added) clang/test/CoverageMapping/nested-system-macro.c (+29) 


``````````diff
diff --git a/clang/lib/CodeGen/CoverageMappingGen.cpp b/clang/lib/CodeGen/CoverageMappingGen.cpp
index 970812f7b0f75..02bc3d14fc230 100644
--- a/clang/lib/CodeGen/CoverageMappingGen.cpp
+++ b/clang/lib/CodeGen/CoverageMappingGen.cpp
@@ -395,9 +395,29 @@ class CoverageMappingBuilder {
           auto BeginLoc = SM.getSpellingLoc(Loc);
           auto EndLoc = SM.getSpellingLoc(Region.getEndLoc());
           if (SM.isWrittenInSameFile(BeginLoc, EndLoc)) {
-            Loc = SM.getFileLoc(Loc);
-            Region.setStartLoc(Loc);
-            Region.setEndLoc(SM.getFileLoc(Region.getEndLoc()));
+            auto ExpansionRange = SM.getImmediateExpansionRange(Loc);
+            while (ExpansionRange.getBegin().isMacroID() &&
+                   SM.isInSystemHeader(
+                       SM.getSpellingLoc(ExpansionRange.getBegin())))
+              ExpansionRange =
+                  SM.getImmediateExpansionRange(ExpansionRange.getBegin());
+
+            if (ExpansionRange.getBegin().isMacroID()) {
+              // Keep a system macro nested in a user macro in that macro's
+              // virtual file. Mapping it all the way to a physical file can
+              // introduce a disconnected, zero-length region at the outermost
+              // invocation (e.g. false in a macro-defined function).
+              Loc = ExpansionRange.getBegin();
+              Region.setStartLoc(Loc);
+              Region.setEndLoc(
+                  ExpansionRange.isTokenRange()
+                      ? getPreciseTokenLocEnd(ExpansionRange.getEnd())
+                      : ExpansionRange.getEnd());
+            } else {
+              Loc = SM.getFileLoc(Loc);
+              Region.setStartLoc(Loc);
+              Region.setEndLoc(SM.getFileLoc(Region.getEndLoc()));
+            }
           }
         }
         if (SM.isInSystemHeader(SM.getSpellingLoc(Loc)))
diff --git a/clang/test/CoverageMapping/Inputs/nested-system-macro/system.h b/clang/test/CoverageMapping/Inputs/nested-system-macro/system.h
new file mode 100644
index 0000000000000..85623bda697d6
--- /dev/null
+++ b/clang/test/CoverageMapping/Inputs/nested-system-macro/system.h
@@ -0,0 +1,3 @@
+#pragma clang system_header
+#define SYSTEM_FALSE 0
+#define SYSTEM_FALSE_ALIAS SYSTEM_FALSE
diff --git a/clang/test/CoverageMapping/Inputs/nested-system-macro/user.h b/clang/test/CoverageMapping/Inputs/nested-system-macro/user.h
new file mode 100644
index 0000000000000..d0056830e3ddf
--- /dev/null
+++ b/clang/test/CoverageMapping/Inputs/nested-system-macro/user.h
@@ -0,0 +1,10 @@
+#include "system.h"
+#define USER_NOP() do {} while (SYSTEM_FALSE)
+#define USER_NOP_ALIAS() do {} while (SYSTEM_FALSE_ALIAS)
+#define USER_ASSERT(expr) USER_NOP_ALIAS()
+#define DECLARE_COMMON(c) \
+  static inline c *c##_cast(void *p) { \
+    USER_ASSERT(p); \
+    return (c *)p; \
+  }
+#define DECLARE_CLASS(c) DECLARE_COMMON(c)
diff --git a/clang/test/CoverageMapping/nested-system-macro.c b/clang/test/CoverageMapping/nested-system-macro.c
new file mode 100644
index 0000000000000..410b3fc8d834f
--- /dev/null
+++ b/clang/test/CoverageMapping/nested-system-macro.c
@@ -0,0 +1,29 @@
+// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c11 -mllvm -emptyline-comment-coverage=false -fprofile-instrument=clang -fcoverage-mapping -dump-coverage-mapping -emit-llvm-only -main-file-name nested-system-macro.c %s | FileCheck %s
+
+// A system macro nested in a user macro must stay in the enclosing macro's
+// virtual file. In particular, do not introduce a disconnected zero-length
+// code region at the invocation of a macro which defines a function.
+#include "Inputs/nested-system-macro/user.h"
+typedef struct object { int value; } object;
+DECLARE_CLASS(object)
+
+void direct(void) {
+  USER_NOP();
+}
+
+int main(void) {
+  object o = {0};
+  return object_cast(&o)->value;
+}
+
+// CHECK-LABEL: direct:
+// CHECK-NEXT: File 0, 10:19 -> 12:2 = #0
+// CHECK-NEXT: Expansion,File 0, 11:3 -> 11:11 = #0 (Expanded file = 1)
+// CHECK: Branch,File 1, 2:33 -> 2:45 = 0, #0
+
+// CHECK-LABEL: nested-system-macro.c:object_cast:
+// CHECK-NEXT: File 0, 6:38 -> 9:4 = #0
+// CHECK-NEXT: Expansion,File 0, 7:5 -> 7:16 = #0 (Expanded file = 1)
+// CHECK-NEXT: File 1, 4:27 -> 4:43 = #0
+// CHECK-NEXT: Expansion,File 1, 4:27 -> 4:41 = #0 (Expanded file = 2)
+// CHECK: Branch,File 2, 3:39 -> 3:57 = 0, #0

``````````

</details>


https://github.com/llvm/llvm-project/pull/228098


More information about the cfe-commits mailing list