[clang] [llvm] [Clang] Show inlining hints for __attribute__((warning/error)) (PR #174892)
Justin Stitt via llvm-commits
llvm-commits at lists.llvm.org
Thu Jan 8 11:33:19 PST 2026
https://github.com/JustinStitt updated https://github.com/llvm/llvm-project/pull/174892
>From ea02427b27288e70acaf87b0c2ad53d897f13e80 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 1/2] [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 | 6 +
clang/include/clang/Basic/AttrDocs.td | 7 ++
clang/include/clang/Basic/CodeGenOptions.def | 2 +
clang/include/clang/Basic/CodeGenOptions.h | 8 ++
.../clang/Basic/DiagnosticFrontendKinds.td | 6 +
clang/include/clang/Options/Options.td | 15 +++
clang/lib/CodeGen/CGCall.cpp | 23 ++--
clang/lib/CodeGen/CodeGenAction.cpp | 31 +++++
clang/lib/Driver/ToolChains/Clang.cpp | 7 ++
clang/lib/Frontend/CompilerInvocation.cpp | 8 ++
.../backend-attribute-inlining-cross-tu.c | 64 ++++++++++
...nd-attribute-inlining-debug-vs-heuristic.c | 28 +++++
.../backend-attribute-inlining-modes.c | 47 ++++++++
.../Frontend/backend-attribute-inlining.c | 109 ++++++++++++++++++
llvm/include/llvm/IR/DiagnosticInfo.h | 26 ++++-
llvm/lib/IR/DiagnosticInfo.cpp | 38 +++++-
llvm/lib/Transforms/Utils/InlineFunction.cpp | 44 +++++++
17 files changed, 459 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.c
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 8ef6564bd80e6..cf17f8a052cbb 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -348,6 +348,12 @@ New Compiler Flags
- New options for enabling allocation token instrumentation: ``-fsanitize=alloc-token``, ``-falloc-token-max=``, ``-fsanitize-alloc-token-fast-abi``, ``-fsanitize-alloc-token-extended``.
- The ``-resource-dir`` option is now displayed in the list of options shown by ``--help``.
- New option ``-fmatrix-memory-layout`` added to control the memory layout of Clang matrix types. (e.g. ``-fmatrix-memory-layout=column-major`` or ``-fmatrix-memory-layout=row-major``).
+- New option ``-fdiagnostics-show-inlining-chain=`` added to show inlining chain
+ notes for ``__attribute__((warning))`` and ``__attribute__((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.
+ Modes: ``none`` (default), ``heuristic`` (uses metadata tracking), ``debug``
+ (uses debug info for accurate source locations).
Lanai Support
^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 812b48058d189..97331b1175cc9 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8397,6 +8397,13 @@ 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. See ``clang --help`` for
+available modes.
}];
}
diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def
index 3784844ef1028..223e803a22c6c 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -76,6 +76,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.
+/// Tracking inlining chain in __attribute__((warning)) and __attribute__((error)) diagnostics
+ENUM_CODEGENOPT(InliningChain, InliningChainKind, 2, Inlining_None, 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/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h
index c60ca507ff917..f34de98126768 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -120,6 +120,14 @@ class CodeGenOptions : public CodeGenOptionsBase {
Embed_Marker // Embed a marker as a placeholder for bitcode.
};
+ /// Inlining chain tracking __attribute__((warning)) or __attribute__((error))
+ /// diagnostics
+ enum InliningChainKind {
+ Inlining_Heuristic, /// Track via srcloc metadata on inline/static calls.
+ Inlining_Debug, /// Track via debug info (DILocation inlinedAt chain).
+ Inlining_None /// No tracking, no inlining notes shown (default).
+ };
+
enum class ExtendVariableLivenessKind {
None,
This,
diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index e2b257ceae80d..3a7b7ccc73ab8 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 warn_fe_inlining_debug_requires_g
+ : Warning<"'-fdiagnostics-show-inlining-chain=debug' requires at least "
+ "'-gline-directives-only'; falling back to 'heuristic' mode">,
+ InGroup<BackendWarningAttributes>;
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 dd039fd474263..92bbb34e6a1eb 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -2158,6 +2158,21 @@ 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]>>;
+def fdiagnostics_show_inlining_chain_EQ
+ : Joined<["-"], "fdiagnostics-show-inlining-chain=">,
+ Group<f_Group>,
+ Visibility<[ClangOption, CC1Option]>,
+ HelpText<"Show inlining chain notes for __attribute__((warning/error)) "
+ "diagnostics: "
+ "'none' (default) shows no chain, 'heuristic' uses best-effort "
+ "metadata tracking, "
+ "'debug' uses debug info for accurate locations with increased "
+ "compile-time overhead">,
+ Values<"heuristic,debug,none">,
+ NormalizedValuesScope<"CodeGenOptions">,
+ NormalizedValues<["Inlining_Heuristic", "Inlining_Debug",
+ "Inlining_None"]>,
+ MarshallingInfoEnum<CodeGenOpts<"InliningChain">, "Inlining_None">;
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 d7bdeb3981cf8..2bc23d9737f5d 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -6048,13 +6048,22 @@ 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 __attribute__((error/warning)) diagnostics.
+ // In heuristic mode, also track inline/static calls for inlining chain.
+ if (TargetDecl) {
+ bool NeedSrcLoc = TargetDecl->hasAttr<ErrorAttr>();
+ if (!NeedSrcLoc && CGM.getCodeGenOpts().getInliningChain() ==
+ CodeGenOptions::Inlining_Heuristic) {
+ if (const auto *FD = dyn_cast<FunctionDecl>(TargetDecl))
+ NeedSrcLoc = FD->isInlined() || FD->isInlineSpecified() ||
+ FD->hasAttr<AlwaysInlineAttr>() ||
+ FD->getStorageClass() == SC_Static;
+ }
+ 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 60d6b7fa009e7..57adcd626839f 100644
--- a/clang/lib/CodeGen/CodeGenAction.cpp
+++ b/clang/lib/CodeGen/CodeGenAction.cpp
@@ -734,6 +734,37 @@ 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.getInliningChain() == CodeGenOptions::Inlining_None)
+ 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());
+ };
+
+ if (CodeGenOpts.getInliningChain() == CodeGenOptions::Inlining_Debug) {
+ 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;
+ }
+
+ for (const auto &[I, Entry] : llvm::enumerate(D.getInliningDecisions())) {
+ SourceLocation Loc =
+ I == 0 ? LocCookie : SourceLocation::getFromRawEncoding(Entry.second);
+ EmitNote(Loc, Entry.first, I == 0);
+ }
}
void BackendConsumer::MisExpectDiagHandler(
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 9ead69bd2fe48..2f232d296ce3d 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -4241,6 +4241,13 @@ 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_EQ)) {
+ std::string Opt =
+ std::string("-fdiagnostics-show-inlining-chain=") + A->getValue();
+ CmdArgs.push_back(Args.MakeArgString(Opt));
+ }
+
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/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index 477406f2526c0..91f5aa160dd47 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -2253,6 +2253,14 @@ bool CompilerInvocation::ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args,
Opts.getDebugInfo() == llvm::codegenoptions::NoDebugInfo)
Opts.setDebugInfo(llvm::codegenoptions::LocTrackingOnly);
+ // Debug mode for inlining chain diagnostics requires at least
+ // -gline-directives-only to track inlining locations via DILocation.
+ if (Opts.getInliningChain() == CodeGenOptions::Inlining_Debug &&
+ Opts.getDebugInfo() < llvm::codegenoptions::DebugDirectivesOnly) {
+ Diags.Report(diag::warn_fe_inlining_debug_requires_g);
+ Opts.setInliningChain(CodeGenOptions::Inlining_Heuristic);
+ }
+
// Parse -fsanitize-recover= arguments.
// FIXME: Report unrecoverable sanitizers incorrectly specified here.
parseSanitizerKinds("-fsanitize-recover=",
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..6c1cee3e9e212
--- /dev/null
+++ b/clang/test/Frontend/backend-attribute-inlining-cross-tu.c
@@ -0,0 +1,64 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: not %clang -O2 -fdiagnostics-show-inlining-chain=heuristic -S %t/main.c -I%t -o /dev/null 2>&1 | FileCheck %s
+
+// Cross-TU inlining: header functions inlined into source file.
+
+//--- overflow.h
+__attribute__((warning("write overflow")))
+void __write_overflow(void);
+
+__attribute__((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'
diff --git a/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c b/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c
new file mode 100644
index 0000000000000..284b23d5cace2
--- /dev/null
+++ b/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c
@@ -0,0 +1,28 @@
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=heuristic %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=HEURISTIC
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=debug -debug-info-kind=line-directives-only %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=DEBUG
+
+__attribute__((warning("dangerous function")))
+void dangerous(void);
+
+// Non-static, non-inline functions that get inlined at -O2.
+void wrapper(void) {
+ dangerous();
+}
+
+void middle(void) {
+ wrapper();
+}
+
+void caller(void) {
+ middle();
+}
+
+// HEURISTIC: :9:{{.*}}: warning: call to 'dangerous'
+// HEURISTIC: :9:{{.*}}: note: called by function 'wrapper'
+// HEURISTIC: :9:{{.*}}: note: inlined by function 'middle'
+// HEURISTIC: :9:{{.*}}: note: inlined by function 'caller'
+
+// DEBUG: :9:{{.*}}: warning: call to 'dangerous'
+// DEBUG: :9:{{.*}}: note: called by function 'wrapper'
+// DEBUG: :13:{{.*}}: note: inlined by function 'middle'
+// DEBUG: :17:{{.*}}: note: inlined by function '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..ebee1208a17dd
--- /dev/null
+++ b/clang/test/Frontend/backend-attribute-inlining-modes.c
@@ -0,0 +1,47 @@
+// RUN: %clang_cc1 -O2 -emit-obj %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=NONE-DEFAULT
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=none %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=NONE-EXPLICIT
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=heuristic %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=HEURISTIC
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=debug -debug-info-kind=line-directives-only %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=DEBUG
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=debug %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=FALLBACK
+
+// Tests all three modes plus fallback behavior when debug info
+// (-gline-directives-only) is missing.
+
+__attribute__((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();
+}
+
+// none (default): warning only, no inlining notes.
+// NONE-DEFAULT: warning: call to 'bad_func'
+// NONE-DEFAULT-NOT: note:
+
+// none (explicit): same as default.
+// NONE-EXPLICIT: warning: call to 'bad_func'
+// NONE-EXPLICIT-NOT: note:
+
+// HEURISTIC: :14:{{.*}}: warning: call to 'bad_func'
+// HEURISTIC: :14:{{.*}}: note: called by function 'level1'
+// HEURISTIC: :18:{{.*}}: note: inlined by function 'level2'
+// HEURISTIC: :22:{{.*}}: note: inlined by function 'entry'
+
+// DEBUG: :14:{{.*}}: warning: call to 'bad_func'
+// DEBUG: :14:{{.*}}: note: called by function 'level1'
+// DEBUG: :18:{{.*}}: note: inlined by function 'level2'
+// DEBUG: :22:{{.*}}: note: inlined by function 'entry'
+
+// FALLBACK: warning: '-fdiagnostics-show-inlining-chain=debug' requires at least '-gline-directives-only'; falling back to 'heuristic' mode
+// FALLBACK: :14:{{.*}}: warning: call to 'bad_func'
+// FALLBACK: :14:{{.*}}: note: called by function 'level1'
+// FALLBACK: :18:{{.*}}: note: inlined by function 'level2'
+// FALLBACK: :22:{{.*}}: note: inlined by function 'entry'
diff --git a/clang/test/Frontend/backend-attribute-inlining.c b/clang/test/Frontend/backend-attribute-inlining.c
new file mode 100644
index 0000000000000..5f91a38ebdc57
--- /dev/null
+++ b/clang/test/Frontend/backend-attribute-inlining.c
@@ -0,0 +1,109 @@
+// RUN: not %clang -O2 -fdiagnostics-show-inlining-chain=heuristic -S %s -o /dev/null 2>&1 | FileCheck %s
+
+// Single-level inlining with warning attribute.
+__attribute__((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.
+__attribute__((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).
+__attribute__((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.
+__attribute__((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.
+__attribute__((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.
+__attribute__((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'
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 8e6d654f6afb3..10ee9f27058f8 100644
--- a/llvm/lib/IR/DiagnosticInfo.cpp
+++ b/llvm/lib/IR/DiagnosticInfo.cpp
@@ -487,8 +487,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);
}
}
@@ -503,3 +522,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 f49fbf8807bac..7d23c9ba80f86 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) {
@@ -2830,6 +2870,10 @@ void llvm::InlineFunctionImpl(CallBase &CB, InlineFunctionInfo &IFI,
// Propagate metadata on the callsite if necessary.
PropagateCallSiteMetadata(CB, FirstNewBlock, Caller->end());
+ // 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 :
>From e866945f1c61774e745ec3a793629104ef473fee Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Thu, 8 Jan 2026 10:27:15 -0800
Subject: [PATCH 2/2] use scoped enum, binary flag, better docs
based on review:
1) use scoped enum
2) switch over to a binary flag instead of a ternary one.
3) improve docs and help text
4) add note regarding -gline-directives-only
5) use modern attribute spellings
Signed-off-by: Justin Stitt <justinstitt at google.com>
---
clang/docs/ReleaseNotes.rst | 13 +++--
clang/include/clang/Basic/AttrDocs.td | 11 +++-
clang/include/clang/Basic/CodeGenOptions.def | 4 +-
clang/include/clang/Basic/CodeGenOptions.h | 8 ---
.../clang/Basic/DiagnosticFrontendKinds.td | 8 +--
clang/include/clang/Options/Options.td | 22 +++----
clang/lib/CodeGen/CGCall.cpp | 11 ++--
clang/lib/CodeGen/CodeGenAction.cpp | 16 +++++-
clang/lib/Driver/ToolChains/Clang.cpp | 10 ++--
clang/lib/Frontend/CompilerInvocation.cpp | 8 ---
.../backend-attribute-inlining-cross-tu.c | 9 ++-
...nd-attribute-inlining-debug-vs-heuristic.c | 31 ++++++----
.../backend-attribute-inlining-modes.c | 57 +++++++++----------
.../Frontend/backend-attribute-inlining.c | 17 +++---
14 files changed, 115 insertions(+), 110 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index cf17f8a052cbb..7bdf72e22948b 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -348,12 +348,13 @@ New Compiler Flags
- New options for enabling allocation token instrumentation: ``-fsanitize=alloc-token``, ``-falloc-token-max=``, ``-fsanitize-alloc-token-fast-abi``, ``-fsanitize-alloc-token-extended``.
- The ``-resource-dir`` option is now displayed in the list of options shown by ``--help``.
- New option ``-fmatrix-memory-layout`` added to control the memory layout of Clang matrix types. (e.g. ``-fmatrix-memory-layout=column-major`` or ``-fmatrix-memory-layout=row-major``).
-- New option ``-fdiagnostics-show-inlining-chain=`` added to show inlining chain
- notes for ``__attribute__((warning))`` and ``__attribute__((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.
- Modes: ``none`` (default), ``heuristic`` (uses metadata tracking), ``debug``
- (uses debug info for accurate source locations).
+- 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`` 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.
Lanai Support
^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 97331b1175cc9..f9c6d6ccd606f 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8399,11 +8399,16 @@ pointing to precise locations of the call site in the source.
}
When the call occurs through inlined functions, the
-``-fdiagnostics-show-inlining-chain=`` option can be used to show 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. See ``clang --help`` for
-available modes.
+multiple locations through inline functions.
+
+When enabled, this option automatically uses debug info for accurate source
+locations if available (requires at least ``-gline-directives-only``), 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 223e803a22c6c..30e6612d84e55 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -76,8 +76,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.
-/// Tracking inlining chain in __attribute__((warning)) and __attribute__((error)) diagnostics
-ENUM_CODEGENOPT(InliningChain, InliningChainKind, 2, Inlining_None, Benign)
+/// 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/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h
index f34de98126768..c60ca507ff917 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -120,14 +120,6 @@ class CodeGenOptions : public CodeGenOptionsBase {
Embed_Marker // Embed a marker as a placeholder for bitcode.
};
- /// Inlining chain tracking __attribute__((warning)) or __attribute__((error))
- /// diagnostics
- enum InliningChainKind {
- Inlining_Heuristic, /// Track via srcloc metadata on inline/static calls.
- Inlining_Debug, /// Track via debug info (DILocation inlinedAt chain).
- Inlining_None /// No tracking, no inlining notes shown (default).
- };
-
enum class ExtendVariableLivenessKind {
None,
This,
diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index 3a7b7ccc73ab8..ff924b3ace3dd 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -96,10 +96,10 @@ def warn_fe_backend_warning_attr :
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 warn_fe_inlining_debug_requires_g
- : Warning<"'-fdiagnostics-show-inlining-chain=debug' requires at least "
- "'-gline-directives-only'; falling back to 'heuristic' mode">,
- InGroup<BackendWarningAttributes>;
+def note_fe_backend_inlining_debug_info
+ : Note<"use '-gline-directives-only' 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 92bbb34e6a1eb..89a72abaa2a50 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -2158,21 +2158,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]>>;
-def fdiagnostics_show_inlining_chain_EQ
- : Joined<["-"], "fdiagnostics-show-inlining-chain=">,
- Group<f_Group>,
- Visibility<[ClangOption, CC1Option]>,
- HelpText<"Show inlining chain notes for __attribute__((warning/error)) "
- "diagnostics: "
- "'none' (default) shows no chain, 'heuristic' uses best-effort "
- "metadata tracking, "
- "'debug' uses debug info for accurate locations with increased "
- "compile-time overhead">,
- Values<"heuristic,debug,none">,
- NormalizedValuesScope<"CodeGenOptions">,
- NormalizedValues<["Inlining_Heuristic", "Inlining_Debug",
- "Inlining_None"]>,
- MarshallingInfoEnum<CodeGenOpts<"InliningChain">, "Inlining_None">;
+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 2bc23d9737f5d..76e504a5aa47b 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -6048,16 +6048,17 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
if (getDebugInfo() && TargetDecl && TargetDecl->hasAttr<MSAllocatorAttr>())
getDebugInfo()->addHeapAllocSiteMetadata(CI, RetTy->getPointeeType(), Loc);
- // Add srcloc metadata for __attribute__((error/warning)) diagnostics.
- // In heuristic mode, also track inline/static calls for inlining chain.
+ // 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.
if (TargetDecl) {
bool NeedSrcLoc = TargetDecl->hasAttr<ErrorAttr>();
- if (!NeedSrcLoc && CGM.getCodeGenOpts().getInliningChain() ==
- CodeGenOptions::Inlining_Heuristic) {
+ if (!NeedSrcLoc && CGM.getCodeGenOpts().ShowInliningChain) {
if (const auto *FD = dyn_cast<FunctionDecl>(TargetDecl))
NeedSrcLoc = FD->isInlined() || FD->isInlineSpecified() ||
FD->hasAttr<AlwaysInlineAttr>() ||
- FD->getStorageClass() == SC_Static;
+ FD->getStorageClass() == SC_Static ||
+ FD->isInAnonymousNamespace();
}
if (NeedSrcLoc) {
auto *Line = llvm::ConstantInt::get(Int64Ty, Loc.getRawEncoding());
diff --git a/clang/lib/CodeGen/CodeGenAction.cpp b/clang/lib/CodeGen/CodeGenAction.cpp
index 57adcd626839f..513767a6e6af0 100644
--- a/clang/lib/CodeGen/CodeGenAction.cpp
+++ b/clang/lib/CodeGen/CodeGenAction.cpp
@@ -735,7 +735,7 @@ void BackendConsumer::DontCallDiagHandler(const DiagnosticInfoDontCall &D) {
: diag::warn_fe_backend_warning_attr)
<< llvm::demangle(D.getFunctionName()) << D.getNote();
- if (CodeGenOpts.getInliningChain() == CodeGenOptions::Inlining_None)
+ if (!CodeGenOpts.ShowInliningChain)
return;
auto EmitNote = [&](SourceLocation Loc, StringRef FuncName, bool IsFirst) {
@@ -746,7 +746,8 @@ void BackendConsumer::DontCallDiagHandler(const DiagnosticInfoDontCall &D) {
Diags.Report(Loc, DiagID) << llvm::demangle(FuncName.str());
};
- if (CodeGenOpts.getInliningChain() == CodeGenOptions::Inlining_Debug) {
+ // 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())) {
@@ -760,11 +761,20 @@ void BackendConsumer::DontCallDiagHandler(const DiagnosticInfoDontCall &D) {
return;
}
- for (const auto &[I, Entry] : llvm::enumerate(D.getInliningDecisions())) {
+ // 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 2f232d296ce3d..8eb85414664ae 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -4242,10 +4242,12 @@ static void RenderDiagnosticsOptions(const Driver &D, const ArgList &Args,
}
if (const Arg *A =
- Args.getLastArg(options::OPT_fdiagnostics_show_inlining_chain_EQ)) {
- std::string Opt =
- std::string("-fdiagnostics-show-inlining-chain=") + A->getValue();
- CmdArgs.push_back(Args.MakeArgString(Opt));
+ 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)) {
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index 91f5aa160dd47..477406f2526c0 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -2253,14 +2253,6 @@ bool CompilerInvocation::ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args,
Opts.getDebugInfo() == llvm::codegenoptions::NoDebugInfo)
Opts.setDebugInfo(llvm::codegenoptions::LocTrackingOnly);
- // Debug mode for inlining chain diagnostics requires at least
- // -gline-directives-only to track inlining locations via DILocation.
- if (Opts.getInliningChain() == CodeGenOptions::Inlining_Debug &&
- Opts.getDebugInfo() < llvm::codegenoptions::DebugDirectivesOnly) {
- Diags.Report(diag::warn_fe_inlining_debug_requires_g);
- Opts.setInliningChain(CodeGenOptions::Inlining_Heuristic);
- }
-
// Parse -fsanitize-recover= arguments.
// FIXME: Report unrecoverable sanitizers incorrectly specified here.
parseSanitizerKinds("-fsanitize-recover=",
diff --git a/clang/test/Frontend/backend-attribute-inlining-cross-tu.c b/clang/test/Frontend/backend-attribute-inlining-cross-tu.c
index 6c1cee3e9e212..cf259cc77f232 100644
--- a/clang/test/Frontend/backend-attribute-inlining-cross-tu.c
+++ b/clang/test/Frontend/backend-attribute-inlining-cross-tu.c
@@ -1,14 +1,14 @@
// RUN: rm -rf %t
// RUN: split-file %s %t
-// RUN: not %clang -O2 -fdiagnostics-show-inlining-chain=heuristic -S %t/main.c -I%t -o /dev/null 2>&1 | FileCheck %s
+// 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
-__attribute__((warning("write overflow")))
+[[gnu::warning("write overflow")]]
void __write_overflow(void);
-__attribute__((error("read overflow")))
+[[gnu::error("read overflow")]]
void __read_overflow(void);
static inline void check_write(int size) {
@@ -62,3 +62,6 @@ void test_error_cross_tu(void) {
// 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' or higher for more accurate inlining chain locations
diff --git a/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c b/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c
index 284b23d5cace2..4760b95f49c39 100644
--- a/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c
+++ b/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c
@@ -1,7 +1,14 @@
-// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=heuristic %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=HEURISTIC
-// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=debug -debug-info-kind=line-directives-only %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=DEBUG
+// 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
-__attribute__((warning("dangerous function")))
+// 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(void);
// Non-static, non-inline functions that get inlined at -O2.
@@ -17,12 +24,14 @@ void caller(void) {
middle();
}
-// HEURISTIC: :9:{{.*}}: warning: call to 'dangerous'
-// HEURISTIC: :9:{{.*}}: note: called by function 'wrapper'
-// HEURISTIC: :9:{{.*}}: note: inlined by function 'middle'
-// HEURISTIC: :9:{{.*}}: note: inlined by function 'caller'
+// 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' or higher for more accurate inlining chain locations
-// DEBUG: :9:{{.*}}: warning: call to 'dangerous'
-// DEBUG: :9:{{.*}}: note: called by function 'wrapper'
-// DEBUG: :13:{{.*}}: note: inlined by function 'middle'
-// DEBUG: :17:{{.*}}: note: inlined by function 'caller'
+// 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'
diff --git a/clang/test/Frontend/backend-attribute-inlining-modes.c b/clang/test/Frontend/backend-attribute-inlining-modes.c
index ebee1208a17dd..9768845458754 100644
--- a/clang/test/Frontend/backend-attribute-inlining-modes.c
+++ b/clang/test/Frontend/backend-attribute-inlining-modes.c
@@ -1,13 +1,13 @@
-// RUN: %clang_cc1 -O2 -emit-obj %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=NONE-DEFAULT
-// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=none %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=NONE-EXPLICIT
-// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=heuristic %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=HEURISTIC
-// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=debug -debug-info-kind=line-directives-only %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=DEBUG
-// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=debug %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=FALLBACK
+// 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
-// Tests all three modes plus fallback behavior when debug info
-// (-gline-directives-only) is missing.
+// 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.
-__attribute__((warning("do not call")))
+[[gnu::warning("do not call")]]
void bad_func(void);
static inline void level1(void) {
@@ -22,26 +22,21 @@ void entry(void) {
level2();
}
-// none (default): warning only, no inlining notes.
-// NONE-DEFAULT: warning: call to 'bad_func'
-// NONE-DEFAULT-NOT: note:
-
-// none (explicit): same as default.
-// NONE-EXPLICIT: warning: call to 'bad_func'
-// NONE-EXPLICIT-NOT: note:
-
-// HEURISTIC: :14:{{.*}}: warning: call to 'bad_func'
-// HEURISTIC: :14:{{.*}}: note: called by function 'level1'
-// HEURISTIC: :18:{{.*}}: note: inlined by function 'level2'
-// HEURISTIC: :22:{{.*}}: note: inlined by function 'entry'
-
-// DEBUG: :14:{{.*}}: warning: call to 'bad_func'
-// DEBUG: :14:{{.*}}: note: called by function 'level1'
-// DEBUG: :18:{{.*}}: note: inlined by function 'level2'
-// DEBUG: :22:{{.*}}: note: inlined by function 'entry'
-
-// FALLBACK: warning: '-fdiagnostics-show-inlining-chain=debug' requires at least '-gline-directives-only'; falling back to 'heuristic' mode
-// FALLBACK: :14:{{.*}}: warning: call to 'bad_func'
-// FALLBACK: :14:{{.*}}: note: called by function 'level1'
-// FALLBACK: :18:{{.*}}: note: inlined by function 'level2'
-// FALLBACK: :22:{{.*}}: note: inlined by function 'entry'
+// 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' 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
index 5f91a38ebdc57..45a8e601e805f 100644
--- a/clang/test/Frontend/backend-attribute-inlining.c
+++ b/clang/test/Frontend/backend-attribute-inlining.c
@@ -1,7 +1,7 @@
-// RUN: not %clang -O2 -fdiagnostics-show-inlining-chain=heuristic -S %s -o /dev/null 2>&1 | FileCheck %s
+// RUN: not %clang -O2 -fdiagnostics-show-inlining-chain -S %s -o /dev/null 2>&1 | FileCheck %s
// Single-level inlining with warning attribute.
-__attribute__((warning("do not call directly")))
+[[gnu::warning("do not call directly")]]
void __warn_single(void);
static inline void warn_wrapper(void) {
@@ -16,7 +16,7 @@ void test_single_level(void) {
// CHECK: :12:{{.*}}: note: inlined by function 'test_single_level'
// Error attribute with inlining.
-__attribute__((error("never call this")))
+[[gnu::error("never call this")]]
void __error_func(void);
static inline void error_wrapper(void) {
@@ -31,7 +31,7 @@ void test_error_inlined(void) {
// CHECK: :27:{{.*}}: note: inlined by function 'test_error_inlined'
// Deep nesting (5 levels).
-__attribute__((warning("deep call")))
+[[gnu::warning("deep call")]]
void __warn_deep(void);
static inline void deep1(void) { __warn_deep(); }
@@ -52,7 +52,7 @@ void test_deep_nesting(void) {
// CHECK: :44:{{.*}}: note: inlined by function 'test_deep_nesting'
// Multiple call sites produce distinct diagnostics.
-__attribute__((warning("deprecated")))
+[[gnu::warning("deprecated")]]
void __warn_multi(void);
static inline void multi_wrapper(void) {
@@ -76,7 +76,7 @@ void call_site_c(void) { multi_wrapper(); }
// CHECK: :64:{{.*}}: note: inlined by function 'call_site_c'
// Different nesting depths from same inner function.
-__attribute__((warning("mixed depth")))
+[[gnu::warning("mixed depth")]]
void __warn_mixed(void);
static inline void mixed_inner(void) { __warn_mixed(); }
@@ -96,7 +96,7 @@ void deep(void) { mixed_middle(); }
// Incidental inlining (function not marked inline/static).
// The "inlined by" note has no location since heuristic mode doesn't track it.
-__attribute__((warning("incidental")))
+[[gnu::warning("incidental")]]
void __warn_incidental(void);
void not_marked_inline(void) { __warn_incidental(); }
@@ -107,3 +107,6 @@ void test_incidental(void) { not_marked_inline(); }
// 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' or higher for more accurate inlining chain locations
More information about the llvm-commits
mailing list