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 <[email protected]> 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 <[email protected]> --- 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 : _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
