[clang] [llvm] [Clang] Show inlining hints for __attribute__((warning/error)) (PR #174892)
Justin Stitt via cfe-commits
cfe-commits at lists.llvm.org
Thu Mar 26 09:17:06 PDT 2026
https://github.com/JustinStitt updated https://github.com/llvm/llvm-project/pull/174892
>From 47fc2001f3026cb9ba9ca7b373e295c2974bf2b8 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Tue, 6 Jan 2026 15:36:33 -0800
Subject: [PATCH] [Clang] Add -fdiagnostics-show-inlining-chain for
warning/error attributes
When functions marked with __attribute__((warning/error)) are called
through inlined functions, Clang now optionally shows the inlining chain
that led to the call.
Three modes are supported:
- ``none`` is the default and results in no inline notes being shown
(matches the behavior before this change).
- ``heuristic`` tries to guess which functions will be inlined to
minimize the amount of source locations kept in memory and visible in
LLVM IR.
- ``debug`` leverages minimal debug directive tracking infrastructure to
get more reliable source locations over the heuristic mode while
having more compile-time overhead.
Fixes: https://github.com/ClangBuiltLinux/linux/issues/1571
Based-on: https://github.com/llvm/llvm-project/pull/73552
Signed-off-by: Justin Stitt <justinstitt at google.com>
---
clang/docs/ReleaseNotes.rst | 8 ++
clang/include/clang/Basic/AttrDocs.td | 12 ++
clang/include/clang/Basic/CodeGenOptions.def | 2 +
.../clang/Basic/DiagnosticFrontendKinds.td | 6 +
clang/include/clang/Options/Options.td | 7 ++
clang/lib/CodeGen/CGCall.cpp | 25 ++--
clang/lib/CodeGen/CodeGenAction.cpp | 41 +++++++
clang/lib/Driver/ToolChains/Clang.cpp | 9 ++
.../backend-attribute-inlining-cross-tu.c | 67 +++++++++++
...-attribute-inlining-debug-vs-heuristic.cpp | 92 ++++++++++++++
.../backend-attribute-inlining-modes.c | 42 +++++++
.../Frontend/backend-attribute-inlining.c | 112 ++++++++++++++++++
llvm/include/llvm/IR/DiagnosticInfo.h | 26 +++-
llvm/lib/IR/DiagnosticInfo.cpp | 38 +++++-
llvm/lib/Transforms/Utils/InlineFunction.cpp | 44 +++++++
15 files changed, 521 insertions(+), 10 deletions(-)
create mode 100644 clang/test/Frontend/backend-attribute-inlining-cross-tu.c
create mode 100644 clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.cpp
create mode 100644 clang/test/Frontend/backend-attribute-inlining-modes.c
create mode 100644 clang/test/Frontend/backend-attribute-inlining.c
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index a8897c707b9e6..f41d2a531bef3 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -187,6 +187,14 @@ New Compiler Flags
Control Flow Guard (CFG) is enabled by other options, it will instruct Clang
to emit the CFG metadata, but disable adding checks.
+- New option ``-fdiagnostics-show-inlining-chain`` added to show inlining chain
+ notes for ``[[gnu::warning]]`` and ``[[gnu::error]]`` diagnostics. When a
+ function with these attributes is called from an inlined context, Clang can
+ now show which functions were inlined to reach the call. When debug info is
+ available (``-gline-directives-only`` (implicitly enabled at ``-g1``) or
+ higher), accurate source locations are used; otherwise, a heuristic fallback
+ is used with a note suggesting how to enable debug info for better accuracy.
+
Deprecated Compiler Flags
-------------------------
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 555a54fd51a89..5383264be5f30 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8579,6 +8579,18 @@ pointing to precise locations of the call site in the source.
dontcall(); // No Warning
sizeof(dontcall()); // No Warning
}
+
+When the call occurs through inlined functions, the
+``-fdiagnostics-show-inlining-chain`` option can be used to show the
+inlining chain that led to the call. This helps identify which call site
+triggered the diagnostic when the attributed function is called from
+multiple locations through inline functions.
+
+When enabled, this option automatically uses debug info for accurate source
+locations if available (``-gline-directives-only`` (implicitly enabled at
+``-g1``) or higher), or falls back to a heuristic based on metadata tracking.
+When falling back, a note is emitted suggesting ``-gline-directives-only`` for
+more accurate locations.
}];
}
diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def
index d883552ea2198..6cee3e8acda3c 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -78,6 +78,8 @@ CODEGENOPT(CallGraphSection, 1, 0, Benign) ///< Emit a call graph section into t
///< object file.
CODEGENOPT(EmitCallSiteInfo, 1, 0, Benign) ///< Emit call site info only in the case of
///< '-g' + 'O>0' level.
+/// Show inlining chain notes for [[gnu::warning/error]] diagnostics.
+CODEGENOPT(ShowInliningChain, 1, 0, Benign)
CODEGENOPT(IndirectTlsSegRefs, 1, 0, Benign) ///< Set when -mno-tls-direct-seg-refs
///< is specified.
CODEGENOPT(DisableTailCalls , 1, 0, Benign) ///< Do not emit tail calls.
diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index b86caeb7714e9..62b74574102e4 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -94,6 +94,12 @@ def err_fe_backend_error_attr :
def warn_fe_backend_warning_attr :
Warning<"call to '%0' declared with 'warning' attribute: %1">, BackendInfo,
InGroup<BackendWarningAttributes>;
+def note_fe_backend_in : Note<"called by function '%0'">, BackendInfo;
+def note_fe_backend_inlined : Note<"inlined by function '%0'">, BackendInfo;
+def note_fe_backend_inlining_debug_info
+ : Note<"use '-gline-directives-only' (implied by '-g1') or higher for "
+ "more accurate inlining chain locations">,
+ BackendInfo;
def warn_toc_unsupported_type : Warning<"-mtocdata option is ignored "
"for %0 because %1">, InGroup<BackendWarningAttributes>;
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 8b0c701521728..de8f6ccc19ae2 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -2216,6 +2216,13 @@ defm diagnostics_show_note_include_stack : BoolFOption<"diagnostics-show-note-in
DiagnosticOpts<"ShowNoteIncludeStack">, DefaultFalse,
PosFlag<SetTrue, [], [ClangOption], "Display include stacks for diagnostic notes">,
NegFlag<SetFalse>, BothFlags<[], [ClangOption, CC1Option]>>;
+defm diagnostics_show_inlining_chain
+ : BoolFOption<"diagnostics-show-inlining-chain",
+ CodeGenOpts<"ShowInliningChain">, DefaultFalse,
+ PosFlag<SetTrue, [], [ClangOption, CC1Option],
+ "Show inlining chain notes for "
+ "[[gnu::warning/error]] diagnostics">,
+ NegFlag<SetFalse, [], [ClangOption, CC1Option]>>;
def fdiagnostics_format_EQ : Joined<["-"], "fdiagnostics-format=">, Group<f_clang_Group>;
def fdiagnostics_show_category_EQ : Joined<["-"], "fdiagnostics-show-category=">, Group<f_clang_Group>;
def fdiagnostics_show_template_tree : Flag<["-"], "fdiagnostics-show-template-tree">,
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 4c0ea9ec3ea9c..48e48168ba5e7 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -6159,13 +6159,24 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
if (getDebugInfo() && TargetDecl && TargetDecl->hasAttr<MSAllocatorAttr>())
getDebugInfo()->addHeapAllocSiteMetadata(CI, RetTy->getPointeeType(), Loc);
- // Add metadata if calling an __attribute__((error(""))) or warning fn.
- if (TargetDecl && TargetDecl->hasAttr<ErrorAttr>()) {
- llvm::ConstantInt *Line =
- llvm::ConstantInt::get(Int64Ty, Loc.getRawEncoding());
- llvm::ConstantAsMetadata *MD = llvm::ConstantAsMetadata::get(Line);
- llvm::MDTuple *MDT = llvm::MDNode::get(getLLVMContext(), {MD});
- CI->setMetadata("srcloc", MDT);
+ // Add srcloc metadata for [[gnu::error/warning]] diagnostics. When
+ // ShowInliningChain is enabled, also track inline/static calls for the
+ // heuristic fallback when debug info is not available. This heuristic is
+ // conservative and best-effort since static or inline-annotated functions
+ // are still not guaranteed to be inlined.
+ if (TargetDecl) {
+ bool NeedSrcLoc = TargetDecl->hasAttr<ErrorAttr>();
+ if (!NeedSrcLoc && CGM.getCodeGenOpts().ShowInliningChain) {
+ if (const auto *FD = dyn_cast<FunctionDecl>(TargetDecl))
+ NeedSrcLoc = FD->isInlined() || FD->hasAttr<AlwaysInlineAttr>() ||
+ FD->getStorageClass() == SC_Static ||
+ FD->isInAnonymousNamespace();
+ }
+ if (NeedSrcLoc) {
+ auto *Line = llvm::ConstantInt::get(Int64Ty, Loc.getRawEncoding());
+ auto *MD = llvm::ConstantAsMetadata::get(Line);
+ CI->setMetadata("srcloc", llvm::MDNode::get(getLLVMContext(), {MD}));
+ }
}
// 4. Finish the call.
diff --git a/clang/lib/CodeGen/CodeGenAction.cpp b/clang/lib/CodeGen/CodeGenAction.cpp
index 29dcabd1b0971..8a82cd057376d 100644
--- a/clang/lib/CodeGen/CodeGenAction.cpp
+++ b/clang/lib/CodeGen/CodeGenAction.cpp
@@ -732,6 +732,47 @@ void BackendConsumer::DontCallDiagHandler(const DiagnosticInfoDontCall &D) {
? diag::err_fe_backend_error_attr
: diag::warn_fe_backend_warning_attr)
<< llvm::demangle(D.getFunctionName()) << D.getNote();
+
+ if (!CodeGenOpts.ShowInliningChain)
+ return;
+
+ auto EmitNote = [&](SourceLocation Loc, StringRef FuncName, bool IsFirst) {
+ if (!Loc.isValid())
+ Loc = LocCookie;
+ unsigned DiagID =
+ IsFirst ? diag::note_fe_backend_in : diag::note_fe_backend_inlined;
+ Diags.Report(Loc, DiagID) << llvm::demangle(FuncName.str());
+ };
+
+ // Try debug info first for accurate source locations.
+ if (!D.getDebugInlineChain().empty()) {
+ SourceManager &SM = Context->getSourceManager();
+ FileManager &FM = SM.getFileManager();
+ for (const auto &[I, Info] : llvm::enumerate(D.getDebugInlineChain())) {
+ SourceLocation Loc;
+ if (Info.Line > 0)
+ if (auto FE = FM.getOptionalFileRef(Info.Filename))
+ Loc = SM.translateFileLineCol(*FE, Info.Line,
+ Info.Column ? Info.Column : 1);
+ EmitNote(Loc, Info.FuncName, I == 0);
+ }
+ return;
+ }
+
+ // Fall back to heuristic (srcloc metadata) when debug info is unavailable.
+ auto InliningDecisions = D.getInliningDecisions();
+ if (InliningDecisions.empty())
+ return;
+
+ for (const auto &[I, Entry] : llvm::enumerate(InliningDecisions)) {
+ SourceLocation Loc =
+ I == 0 ? LocCookie : SourceLocation::getFromRawEncoding(Entry.second);
+ EmitNote(Loc, Entry.first, I == 0);
+ }
+
+ // Suggest enabling debug info (at least -gline-directives-only) for more
+ // accurate locations.
+ Diags.Report(LocCookie, diag::note_fe_backend_inlining_debug_info);
}
void BackendConsumer::MisExpectDiagHandler(
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 6416baf9126ff..e80409bf1e874 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -4316,6 +4316,15 @@ static void RenderDiagnosticsOptions(const Driver &D, const ArgList &Args,
CmdArgs.push_back(Args.MakeArgString(Opt));
}
+ if (const Arg *A =
+ Args.getLastArg(options::OPT_fdiagnostics_show_inlining_chain,
+ options::OPT_fno_diagnostics_show_inlining_chain)) {
+ if (A->getOption().matches(options::OPT_fdiagnostics_show_inlining_chain))
+ CmdArgs.push_back("-fdiagnostics-show-inlining-chain");
+ else
+ CmdArgs.push_back("-fno-diagnostics-show-inlining-chain");
+ }
+
if (const Arg *A = Args.getLastArg(options::OPT_fdiagnostics_format_EQ)) {
CmdArgs.push_back("-fdiagnostics-format");
CmdArgs.push_back(A->getValue());
diff --git a/clang/test/Frontend/backend-attribute-inlining-cross-tu.c b/clang/test/Frontend/backend-attribute-inlining-cross-tu.c
new file mode 100644
index 0000000000000..3d67ae946a230
--- /dev/null
+++ b/clang/test/Frontend/backend-attribute-inlining-cross-tu.c
@@ -0,0 +1,67 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: not %clang -O2 -fdiagnostics-show-inlining-chain -S %t/main.c -I%t -o /dev/null 2>&1 | FileCheck %s
+
+// Cross-TU inlining: header functions inlined into source file.
+
+//--- overflow.h
+[[gnu::warning("write overflow")]]
+void __write_overflow(void);
+
+[[gnu::error("read overflow")]]
+void __read_overflow(void);
+
+static inline void check_write(int size) {
+ if (size > 100)
+ __write_overflow();
+}
+
+static inline void check_read(int size) {
+ if (size > 50)
+ __read_overflow();
+}
+
+static inline void check_both(int size) {
+ check_write(size);
+ check_read(size);
+}
+
+//--- main.c
+#include "overflow.h"
+
+void test_simple_cross_tu(void) {
+ check_write(200);
+}
+// CHECK: warning: call to '__write_overflow' declared with 'warning' attribute: write overflow
+// CHECK: note: called by function 'check_write'
+// CHECK: main.c:{{.*}}: note: inlined by function 'test_simple_cross_tu'
+
+// Nested cross-TU inlining (header -> header -> source).
+static inline void local_wrapper(int x) {
+ check_both(x);
+}
+
+void test_nested_cross_tu(void) {
+ local_wrapper(200);
+}
+// CHECK: warning: call to '__write_overflow' declared with 'warning' attribute: write overflow
+// CHECK: note: called by function 'check_write'
+// CHECK: overflow.h:{{.*}}: note: inlined by function 'check_both'
+// CHECK: main.c:{{.*}}: note: inlined by function 'local_wrapper'
+// CHECK: main.c:{{.*}}: note: inlined by function 'test_nested_cross_tu'
+
+// CHECK: error: call to '__read_overflow' declared with 'error' attribute: read overflow
+// CHECK: note: called by function 'check_read'
+// CHECK: overflow.h:{{.*}}: note: inlined by function 'check_both'
+// CHECK: main.c:{{.*}}: note: inlined by function 'local_wrapper'
+// CHECK: main.c:{{.*}}: note: inlined by function 'test_nested_cross_tu'
+
+void test_error_cross_tu(void) {
+ check_read(100);
+}
+// CHECK: error: call to '__read_overflow' declared with 'error' attribute: read overflow
+// CHECK: note: called by function 'check_read'
+// CHECK: main.c:{{.*}}: note: inlined by function 'test_error_cross_tu'
+
+// Fallback note should appear (no debug info).
+// CHECK: note: use '-gline-directives-only' (implied by '-g1') or higher for more accurate inlining chain locations
diff --git a/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.cpp b/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.cpp
new file mode 100644
index 0000000000000..3a58be1327844
--- /dev/null
+++ b/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.cpp
@@ -0,0 +1,92 @@
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=HEURISTIC
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain -debug-info-kind=line-directives-only %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=DEBUG
+
+// Verify auto-selection works between debug info and heuristic fallback. When
+// we have at least -gline-directives-only we can use DILocation for accurate
+// inline locations.
+
+// Without that debug info we fall back to a heuristic approach using srcloc
+// metadata.
+
+[[gnu::warning("dangerous function")]]
+void dangerous();
+
+// Non-static, non-inline functions that get inlined at -O2.
+void wrapper() {
+ dangerous();
+}
+
+void middle() {
+ wrapper();
+}
+
+void caller() {
+ middle();
+}
+
+
+// HEURISTIC: :16:{{.*}}: warning: call to '{{.*}}dangerous{{.*}}'
+// HEURISTIC: :16:{{.*}}: note: called by function '{{.*}}wrapper{{.*}}'
+// HEURISTIC: :16:{{.*}}: note: inlined by function '{{.*}}middle{{.*}}'
+// HEURISTIC: :16:{{.*}}: note: inlined by function '{{.*}}caller{{.*}}'
+// HEURISTIC: note: use '-gline-directives-only' (implied by '-g1') or higher for more accurate inlining chain locations
+
+// DEBUG: :16:{{.*}}: warning: call to '{{.*}}dangerous{{.*}}'
+// DEBUG: :16:{{.*}}: note: called by function '{{.*}}wrapper{{.*}}'
+// DEBUG: :20:{{.*}}: note: inlined by function '{{.*}}middle{{.*}}'
+// DEBUG: :24:{{.*}}: note: inlined by function '{{.*}}caller{{.*}}'
+// DEBUG-NOT: note: use '-gline-directives-only'
+
+// Test that functions in anonymous namespaces are properly tracked for
+// inlining chain diagnostics. Anonymous namespace functions have internal
+// linkage and are prime candidates for inlining.
+
+[[gnu::warning("do not call")]]
+void bad_func();
+
+namespace {
+void anon_helper() {
+ bad_func();
+}
+
+void anon_middle() {
+ anon_helper();
+}
+} // namespace
+
+void public_caller() {
+ anon_middle();
+}
+
+// HEURISTIC: :49:{{.*}}: warning: call to '{{.*}}bad_func{{.*}}'
+// HEURISTIC: :49:{{.*}}: note: called by function '{{.*}}anon_helper{{.*}}'
+// HEURISTIC: :53:{{.*}}: note: inlined by function '{{.*}}anon_middle{{.*}}'
+// HEURISTIC: :58:{{.*}}: note: inlined by function '{{.*}}public_caller{{.*}}'
+
+// DEBUG: :49:{{.*}}: warning: call to '{{.*}}bad_func{{.*}}'
+// DEBUG: :49:{{.*}}: note: called by function '{{.*}}anon_helper{{.*}}'
+// DEBUG: :53:{{.*}}: note: inlined by function '{{.*}}anon_middle{{.*}}'
+// DEBUG: :58:{{.*}}: note: inlined by function '{{.*}}public_caller{{.*}}'
+
+// always_inline forces inlining but doesn't imply
+// isInlined() in the language sense.
+
+[[gnu::warning("always inline warning")]]
+void always_inline_target();
+
+__attribute__((always_inline))
+void always_inline_wrapper() {
+ always_inline_target();
+}
+
+void always_inline_caller() {
+ always_inline_wrapper();
+}
+
+// HEURISTIC: :79:{{.*}}: warning: call to '{{.*}}always_inline_target{{.*}}'
+// HEURISTIC: :79:{{.*}}: note: called by function '{{.*}}always_inline_wrapper{{.*}}'
+// HEURISTIC: :83:{{.*}}: note: inlined by function '{{.*}}always_inline_caller{{.*}}'
+
+// DEBUG: :79:{{.*}}: warning: call to '{{.*}}always_inline_target{{.*}}'
+// DEBUG: :79:{{.*}}: note: called by function '{{.*}}always_inline_wrapper{{.*}}'
+// DEBUG: :83:{{.*}}: note: inlined by function '{{.*}}always_inline_caller{{.*}}'
diff --git a/clang/test/Frontend/backend-attribute-inlining-modes.c b/clang/test/Frontend/backend-attribute-inlining-modes.c
new file mode 100644
index 0000000000000..adc010671fffd
--- /dev/null
+++ b/clang/test/Frontend/backend-attribute-inlining-modes.c
@@ -0,0 +1,42 @@
+// RUN: %clang_cc1 -O2 -emit-obj %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=DISABLED
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=ENABLED-HEURISTIC
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain -debug-info-kind=line-directives-only %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=ENABLED-DEBUG
+
+// Test -fdiagnostics-show-inlining-chain behavior:
+// - Disabled (default): warning only, no inlining notes.
+// - Enabled without debug info: heuristic fallback + suggestion note.
+// - Enabled with debug info: accurate locations from DILocation.
+
+[[gnu::warning("do not call")]]
+void bad_func(void);
+
+static inline void level1(void) {
+ bad_func();
+}
+
+static inline void level2(void) {
+ level1();
+}
+
+void entry(void) {
+ level2();
+}
+
+// Disabled (default): warning only, no inlining notes.
+// DISABLED: warning: call to 'bad_func'
+// DISABLED-NOT: note:
+
+// Enabled without debug info: heuristic fallback.
+// All notes point to original call site (:14).
+// ENABLED-HEURISTIC: :14:{{.*}}: warning: call to 'bad_func'
+// ENABLED-HEURISTIC: :14:{{.*}}: note: called by function 'level1'
+// ENABLED-HEURISTIC: :18:{{.*}}: note: inlined by function 'level2'
+// ENABLED-HEURISTIC: :22:{{.*}}: note: inlined by function 'entry'
+// ENABLED-HEURISTIC: note: use '-gline-directives-only' (implied by '-g1') or higher for more accurate inlining chain locations
+
+// Enabled with debug info: accurate locations.
+// ENABLED-DEBUG: :14:{{.*}}: warning: call to 'bad_func'
+// ENABLED-DEBUG: :14:{{.*}}: note: called by function 'level1'
+// ENABLED-DEBUG: :18:{{.*}}: note: inlined by function 'level2'
+// ENABLED-DEBUG: :22:{{.*}}: note: inlined by function 'entry'
+// ENABLED-DEBUG-NOT: note: use '-gline-directives-only'
diff --git a/clang/test/Frontend/backend-attribute-inlining.c b/clang/test/Frontend/backend-attribute-inlining.c
new file mode 100644
index 0000000000000..734ce0fc30bba
--- /dev/null
+++ b/clang/test/Frontend/backend-attribute-inlining.c
@@ -0,0 +1,112 @@
+// RUN: not %clang -O2 -fdiagnostics-show-inlining-chain -S %s -o /dev/null 2>&1 | FileCheck %s
+
+// Single-level inlining with warning attribute.
+[[gnu::warning("do not call directly")]]
+void __warn_single(void);
+
+static inline void warn_wrapper(void) {
+ __warn_single();
+}
+
+void test_single_level(void) {
+ warn_wrapper();
+}
+// CHECK: warning: call to '__warn_single' declared with 'warning' attribute: do not call directly
+// CHECK: note: called by function 'warn_wrapper'
+// CHECK: :12:{{.*}}: note: inlined by function 'test_single_level'
+
+// Error attribute with inlining.
+[[gnu::error("never call this")]]
+void __error_func(void);
+
+static inline void error_wrapper(void) {
+ __error_func();
+}
+
+void test_error_inlined(void) {
+ error_wrapper();
+}
+// CHECK: error: call to '__error_func' declared with 'error' attribute: never call this
+// CHECK: note: called by function 'error_wrapper'
+// CHECK: :27:{{.*}}: note: inlined by function 'test_error_inlined'
+
+// Deep nesting (5 levels).
+[[gnu::warning("deep call")]]
+void __warn_deep(void);
+
+static inline void deep1(void) { __warn_deep(); }
+static inline void deep2(void) { deep1(); }
+static inline void deep3(void) { deep2(); }
+static inline void deep4(void) { deep3(); }
+static inline void deep5(void) { deep4(); }
+
+void test_deep_nesting(void) {
+ deep5();
+}
+// CHECK: warning: call to '__warn_deep' declared with 'warning' attribute: deep call
+// CHECK: note: called by function 'deep1'
+// CHECK: :38:{{.*}}: note: inlined by function 'deep2'
+// CHECK: :39:{{.*}}: note: inlined by function 'deep3'
+// CHECK: :40:{{.*}}: note: inlined by function 'deep4'
+// CHECK: :41:{{.*}}: note: inlined by function 'deep5'
+// CHECK: :44:{{.*}}: note: inlined by function 'test_deep_nesting'
+
+// Multiple call sites produce distinct diagnostics.
+[[gnu::warning("deprecated")]]
+void __warn_multi(void);
+
+static inline void multi_wrapper(void) {
+ __warn_multi();
+}
+
+void call_site_a(void) { multi_wrapper(); }
+void call_site_b(void) { multi_wrapper(); }
+void call_site_c(void) { multi_wrapper(); }
+
+// CHECK: warning: call to '__warn_multi' declared with 'warning' attribute: deprecated
+// CHECK: note: called by function 'multi_wrapper'
+// CHECK: :62:{{.*}}: note: inlined by function 'call_site_a'
+
+// CHECK: warning: call to '__warn_multi' declared with 'warning' attribute: deprecated
+// CHECK: note: called by function 'multi_wrapper'
+// CHECK: :63:{{.*}}: note: inlined by function 'call_site_b'
+
+// CHECK: warning: call to '__warn_multi' declared with 'warning' attribute: deprecated
+// CHECK: note: called by function 'multi_wrapper'
+// CHECK: :64:{{.*}}: note: inlined by function 'call_site_c'
+
+// Different nesting depths from same inner function.
+[[gnu::warning("mixed depth")]]
+void __warn_mixed(void);
+
+static inline void mixed_inner(void) { __warn_mixed(); }
+static inline void mixed_middle(void) { mixed_inner(); }
+
+void shallow(void) { mixed_inner(); }
+void deep(void) { mixed_middle(); }
+
+// CHECK: warning: call to '__warn_mixed' declared with 'warning' attribute: mixed depth
+// CHECK: note: called by function 'mixed_inner'
+// CHECK: :85:{{.*}}: note: inlined by function 'shallow'
+
+// CHECK: warning: call to '__warn_mixed' declared with 'warning' attribute: mixed depth
+// CHECK: note: called by function 'mixed_inner'
+// CHECK: :83:{{.*}}: note: inlined by function 'mixed_middle'
+// CHECK: :86:{{.*}}: note: inlined by function 'deep'
+
+// Incidental inlining (function not marked inline/static).
+// The "inlined by" note has no location since heuristic mode doesn't track it.
+[[gnu::warning("incidental")]]
+void __warn_incidental(void);
+
+void not_marked_inline(void) { __warn_incidental(); }
+
+void test_incidental(void) { not_marked_inline(); }
+
+// CHECK: warning: call to '__warn_incidental' declared with 'warning' attribute: incidental
+// CHECK: note: called by function 'not_marked_inline'
+// CHECK: note: inlined by function 'test_incidental'
+// CHECK-NOT: :{{.*}}: note: inlined by function 'test_incidental'
+
+// Fallback note should appear (no debug info).
+// CHECK: note: use '-gline-directives-only' (implied by '-g1') or higher for more accurate inlining chain locations
diff --git a/llvm/include/llvm/IR/DiagnosticInfo.h b/llvm/include/llvm/IR/DiagnosticInfo.h
index 8f6fb4da0c839..0daaa462ba715 100644
--- a/llvm/include/llvm/IR/DiagnosticInfo.h
+++ b/llvm/include/llvm/IR/DiagnosticInfo.h
@@ -1192,19 +1192,41 @@ class LLVM_ABI DiagnosticInfoSrcMgr : public DiagnosticInfo {
LLVM_ABI void diagnoseDontCall(const CallInst &CI);
+/// Inlining location extracted from debug info.
+struct DebugInlineInfo {
+ StringRef FuncName;
+ StringRef Filename;
+ unsigned Line;
+ unsigned Column;
+};
+
class LLVM_ABI DiagnosticInfoDontCall : public DiagnosticInfo {
StringRef CalleeName;
StringRef Note;
uint64_t LocCookie;
+ MDNode *InlinedFromMD = nullptr;
+ SmallVector<DebugInlineInfo, 4> DebugInlineChain;
public:
DiagnosticInfoDontCall(StringRef CalleeName, StringRef Note,
- DiagnosticSeverity DS, uint64_t LocCookie)
+ DiagnosticSeverity DS, uint64_t LocCookie,
+ MDNode *InlinedFromMD = nullptr)
: DiagnosticInfo(DK_DontCall, DS), CalleeName(CalleeName), Note(Note),
- LocCookie(LocCookie) {}
+ LocCookie(LocCookie), InlinedFromMD(InlinedFromMD) {}
+
StringRef getFunctionName() const { return CalleeName; }
StringRef getNote() const { return Note; }
uint64_t getLocCookie() const { return LocCookie; }
+ MDNode *getInlinedFromMD() const { return InlinedFromMD; }
+ SmallVector<std::pair<StringRef, uint64_t>> getInliningDecisions() const;
+
+ void setDebugInlineChain(SmallVector<DebugInlineInfo, 4> &&Chain) {
+ DebugInlineChain = std::move(Chain);
+ }
+ ArrayRef<DebugInlineInfo> getDebugInlineChain() const {
+ return DebugInlineChain;
+ }
+
void print(DiagnosticPrinter &DP) const override;
static bool classof(const DiagnosticInfo *DI) {
return DI->getKind() == DK_DontCall;
diff --git a/llvm/lib/IR/DiagnosticInfo.cpp b/llvm/lib/IR/DiagnosticInfo.cpp
index e48016fc4165f..73bf0eca0b964 100644
--- a/llvm/lib/IR/DiagnosticInfo.cpp
+++ b/llvm/lib/IR/DiagnosticInfo.cpp
@@ -484,8 +484,27 @@ void llvm::diagnoseDontCall(const CallInst &CI) {
if (MDNode *MD = CI.getMetadata("srcloc"))
LocCookie =
mdconst::extract<ConstantInt>(MD->getOperand(0))->getZExtValue();
+ MDNode *InlinedFromMD = CI.getMetadata("inlined.from");
DiagnosticInfoDontCall D(F->getName(), A.getValueAsString(), Sev,
- LocCookie);
+ LocCookie, InlinedFromMD);
+
+ if (const DebugLoc &DL = CI.getDebugLoc()) {
+ SmallVector<DebugInlineInfo, 4> DebugChain;
+ auto AddLocation = [&](const DILocation *Loc) {
+ if (auto *Scope = Loc->getScope())
+ if (auto *SP = Scope->getSubprogram())
+ DebugChain.push_back({SP->getName(), Loc->getFilename(),
+ Loc->getLine(), Loc->getColumn()});
+ };
+ if (const DILocation *Loc = DL.get()) {
+ AddLocation(Loc);
+ for (const DILocation *InlinedAt = Loc->getInlinedAt(); InlinedAt;
+ InlinedAt = InlinedAt->getInlinedAt())
+ AddLocation(InlinedAt);
+ }
+ D.setDebugInlineChain(std::move(DebugChain));
+ }
+
F->getContext().diagnose(D);
}
}
@@ -500,3 +519,20 @@ void DiagnosticInfoDontCall::print(DiagnosticPrinter &DP) const {
if (!getNote().empty())
DP << ": " << getNote();
}
+
+SmallVector<std::pair<StringRef, uint64_t>>
+DiagnosticInfoDontCall::getInliningDecisions() const {
+ SmallVector<std::pair<StringRef, uint64_t>> Chain;
+ if (!InlinedFromMD)
+ return Chain;
+
+ for (unsigned I = 0, E = InlinedFromMD->getNumOperands(); I + 1 < E; I += 2) {
+ auto *NameMD = dyn_cast<MDString>(InlinedFromMD->getOperand(I));
+ auto *LocMD =
+ mdconst::dyn_extract<ConstantInt>(InlinedFromMD->getOperand(I + 1));
+ if (NameMD && !NameMD->getString().empty())
+ Chain.emplace_back(NameMD->getString(),
+ LocMD ? LocMD->getZExtValue() : 0);
+ }
+ return Chain;
+}
diff --git a/llvm/lib/Transforms/Utils/InlineFunction.cpp b/llvm/lib/Transforms/Utils/InlineFunction.cpp
index 0c0d0f22d0367..1c4df54b60f46 100644
--- a/llvm/lib/Transforms/Utils/InlineFunction.cpp
+++ b/llvm/lib/Transforms/Utils/InlineFunction.cpp
@@ -974,6 +974,46 @@ static void PropagateCallSiteMetadata(CallBase &CB, Function::iterator FStart,
}
}
+/// Track inlining chain via inlined.from metadata for dontcall diagnostics.
+static void PropagateInlinedFromMetadata(CallBase &CB, StringRef CalledFuncName,
+ StringRef CallerFuncName,
+ Function::iterator FStart,
+ Function::iterator FEnd) {
+ LLVMContext &Ctx = CB.getContext();
+ uint64_t InlineSiteLoc = 0;
+ if (auto *MD = CB.getMetadata("srcloc"))
+ if (auto *CI = mdconst::dyn_extract<ConstantInt>(MD->getOperand(0)))
+ InlineSiteLoc = CI->getZExtValue();
+
+ auto *I64Ty = Type::getInt64Ty(Ctx);
+ auto MakeMDInt = [&](uint64_t V) {
+ return ConstantAsMetadata::get(ConstantInt::get(I64Ty, V));
+ };
+
+ for (BasicBlock &BB : make_range(FStart, FEnd)) {
+ for (Instruction &I : BB) {
+ auto *CI = dyn_cast<CallInst>(&I);
+ if (!CI || !CI->getMetadata("srcloc"))
+ continue;
+ auto *Callee = CI->getCalledFunction();
+ if (!Callee || (!Callee->hasFnAttribute("dontcall-error") &&
+ !Callee->hasFnAttribute("dontcall-warn")))
+ continue;
+
+ SmallVector<Metadata *, 8> Ops;
+ if (MDNode *Existing = CI->getMetadata("inlined.from"))
+ append_range(Ops, Existing->operands());
+ else {
+ Ops.push_back(MDString::get(Ctx, CalledFuncName));
+ Ops.push_back(MakeMDInt(0));
+ }
+ Ops.push_back(MDString::get(Ctx, CallerFuncName));
+ Ops.push_back(MakeMDInt(InlineSiteLoc));
+ CI->setMetadata("inlined.from", MDNode::get(Ctx, Ops));
+ }
+ }
+}
+
/// Bundle operands of the inlined function must be added to inlined call sites.
static void PropagateOperandBundles(Function::iterator InlinedBB,
Instruction *CallSiteEHPad) {
@@ -2851,6 +2891,10 @@ void llvm::InlineFunctionImpl(CallBase &CB, InlineFunctionInfo &IFI,
}
}
+ // Propagate inlined.from metadata for dontcall diagnostics.
+ PropagateInlinedFromMetadata(CB, CalledFunc->getName(), Caller->getName(),
+ FirstNewBlock, Caller->end());
+
// Register any cloned assumptions.
if (IFI.GetAssumptionCache)
for (BasicBlock &NewBlock :
More information about the cfe-commits
mailing list