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

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


>From 33c7675e8ebb3c1488d38661eb55021ad004745e Mon Sep 17 00:00:00 2001
From: Maxim Zhukov <[email protected]>
Date: Thu, 1 Oct 2026 17:48:16 +0300
Subject: [PATCH] [Coverage] Preserve user macro expansion locations when
 remapping system macros

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.

Assisted-by: OpenAI Codex
---
 clang/lib/CodeGen/CoverageMappingGen.cpp      | 26 +++++++++++++++--
 .../Inputs/nested-system-macro/system.h       |  3 ++
 .../Inputs/nested-system-macro/user.h         | 10 +++++++
 .../CoverageMapping/nested-system-macro.c     | 29 +++++++++++++++++++
 4 files changed, 65 insertions(+), 3 deletions(-)
 create mode 100644 
clang/test/CoverageMapping/Inputs/nested-system-macro/system.h
 create mode 100644 clang/test/CoverageMapping/Inputs/nested-system-macro/user.h
 create mode 100644 clang/test/CoverageMapping/nested-system-macro.c

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

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to