https://github.com/JustinStitt updated https://github.com/llvm/llvm-project/pull/178318
>From 12fff88c959586d97f74a164c512053dfa4a775b Mon Sep 17 00:00:00 2001 From: Justin Stitt <[email protected]> Date: Tue, 27 Jan 2026 13:59:28 -0800 Subject: [PATCH] [Clang] Add -Wtrivial-auto-var-init warning for unreachable variables Add an off-by-default warning -Wtrivial-auto-var-init which warns when -ftrivial-auto-init-var cannot initialize a variable because it is in unreachable code. This usually happens due to pre-case switch declarations and goto-bypassed declarations. The spelling and functionality of this warning matches GCC 12+ [1]. [1]: https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wtrivial-auto-var-init Signed-off-by: Justin Stitt <[email protected]> --- clang/docs/ReleaseNotes.rst | 3 + .../clang/Analysis/Analyses/ReachableCode.h | 1 + .../clang/Basic/DiagnosticSemaKinds.td | 4 + clang/lib/Analysis/ReachableCode.cpp | 2 + clang/lib/Sema/AnalysisBasedWarnings.cpp | 166 +++++++++++------- clang/test/Sema/trivial-auto-var-init-warn.c | 35 ++++ .../SemaCXX/trivial-auto-var-init-warn.cpp | 38 ++++ 7 files changed, 190 insertions(+), 59 deletions(-) create mode 100644 clang/test/Sema/trivial-auto-var-init-warn.c create mode 100644 clang/test/SemaCXX/trivial-auto-var-init-warn.cpp diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 489a91d439133..18e4b7e118c5a 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -187,6 +187,9 @@ Improvements to Clang's diagnostics int* p(int *in [[clang::noescape]]) { return in; } ^~ +- Added ``-Wtrivial-auto-var-init`` to detect when ``-ftrivial-auto-var-init`` + cannot initialize a variable because it is in unreachable code. + Improvements to Clang's time-trace ---------------------------------- diff --git a/clang/include/clang/Analysis/Analyses/ReachableCode.h b/clang/include/clang/Analysis/Analyses/ReachableCode.h index f1b63f74b6c80..0772dd8c7f18a 100644 --- a/clang/include/clang/Analysis/Analyses/ReachableCode.h +++ b/clang/include/clang/Analysis/Analyses/ReachableCode.h @@ -51,6 +51,7 @@ class Callback { virtual void HandleUnreachable(UnreachableKind UK, SourceLocation L, SourceRange ConditionVal, SourceRange R1, SourceRange R2, bool HasFallThroughAttr) = 0; + virtual void HandleUnreachableBlock(const CFGBlock *B) {} }; /// ScanReachableFromBlock - Mark all blocks reachable from Start. diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index c786eb4486829..48108c7fd9ebb 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -814,6 +814,10 @@ def warn_unreachable_association : Warning< "due to lvalue conversion of the controlling expression, association of type " "%0 will never be selected because it is %select{of array type|qualified}1">, InGroup<UnreachableCodeGenericAssoc>; +def warn_trivial_auto_var_init_unreachable : Warning< + "variable %0 is uninitialized and cannot be initialized with " + "'-ftrivial-auto-var-init' because it is unreachable">, + InGroup<TrivialAutoVarInit>, DefaultIgnore; /// Built-in functions. def ext_implicit_lib_function_decl : ExtWarn< diff --git a/clang/lib/Analysis/ReachableCode.cpp b/clang/lib/Analysis/ReachableCode.cpp index 4a9ab5d9f0f73..391ca07253c2c 100644 --- a/clang/lib/Analysis/ReachableCode.cpp +++ b/clang/lib/Analysis/ReachableCode.cpp @@ -761,6 +761,8 @@ void FindUnreachableCode(AnalysisDeclContext &AC, Preprocessor &PP, if (reachable[block->getBlockID()]) continue; + CB.HandleUnreachableBlock(block); + DeadCodeScan DS(reachable, PP, AC.getASTContext()); numReachable += DS.scanBackwards(block, CB); diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 913962dc0c3e0..e631f5930a418 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -69,77 +69,123 @@ using namespace clang; // Unreachable code analysis. //===----------------------------------------------------------------------===// +static bool isTrivialInitializer(const Expr *Init) { + if (!Init) + return true; + const auto *CE = dyn_cast<CXXConstructExpr>(Init); + if (!CE) + return false; + const auto *Ctor = CE->getConstructor(); + return Ctor && Ctor->isTrivial() && Ctor->isDefaultConstructor() && + !CE->requiresZeroInitialization(); +} + +static bool wouldTrivialAutoVarInitApply(const LangOptions &LangOpts, + const VarDecl *VD) { + if (LangOpts.getTrivialAutoVarInit() == + LangOptions::TrivialAutoVarInitKind::Uninitialized) + return false; + if (VD->isConstexpr() || VD->hasAttr<UninitializedAttr>()) + return false; + if (const auto *TD = VD->getType()->getAsTagDecl()) + if (TD->hasAttr<NoTrivialAutoVarInitAttr>()) + return false; + if (const auto *FD = dyn_cast<FunctionDecl>(VD->getDeclContext())) + if (FD->hasAttr<NoTrivialAutoVarInitAttr>()) + return false; + return true; +} + namespace { - class UnreachableCodeHandler : public reachable_code::Callback { - Sema &S; - SourceRange PreviousSilenceableCondVal; - - public: - UnreachableCodeHandler(Sema &s) : S(s) {} - - void HandleUnreachable(reachable_code::UnreachableKind UK, SourceLocation L, - SourceRange SilenceableCondVal, SourceRange R1, - SourceRange R2, bool HasFallThroughAttr) override { - // If the diagnosed code is `[[fallthrough]];` and - // `-Wunreachable-code-fallthrough` is enabled, suppress `code will never - // be executed` warning to avoid generating diagnostic twice - if (HasFallThroughAttr && - !S.getDiagnostics().isIgnored(diag::warn_unreachable_fallthrough_attr, - SourceLocation())) - return; +class UnreachableCodeHandler : public reachable_code::Callback { + Sema &S; + SourceRange PreviousSilenceableCondVal; + bool CheckTrivialAutoVarInit; - // Avoid reporting multiple unreachable code diagnostics that are - // triggered by the same conditional value. - if (PreviousSilenceableCondVal.isValid() && - SilenceableCondVal.isValid() && - PreviousSilenceableCondVal == SilenceableCondVal) - return; - PreviousSilenceableCondVal = SilenceableCondVal; +public: + UnreachableCodeHandler(Sema &S, bool CheckTrivialAutoVarInit) + : S(S), CheckTrivialAutoVarInit(CheckTrivialAutoVarInit) {} + + void HandleUnreachable(reachable_code::UnreachableKind UK, SourceLocation L, + SourceRange SilenceableCondVal, SourceRange R1, + SourceRange R2, bool HasFallThroughAttr) override { + // If the diagnosed code is `[[fallthrough]];` and + // `-Wunreachable-code-fallthrough` is enabled, suppress `code will never + // be executed` warning to avoid generating diagnostic twice + if (HasFallThroughAttr && + !S.getDiagnostics().isIgnored(diag::warn_unreachable_fallthrough_attr, + SourceLocation())) + return; - unsigned diag = diag::warn_unreachable; - switch (UK) { - case reachable_code::UK_Break: - diag = diag::warn_unreachable_break; - break; - case reachable_code::UK_Return: - diag = diag::warn_unreachable_return; - break; - case reachable_code::UK_Loop_Increment: - diag = diag::warn_unreachable_loop_increment; - break; - case reachable_code::UK_Other: - break; - } + // Avoid reporting multiple unreachable code diagnostics that are + // triggered by the same conditional value. + if (PreviousSilenceableCondVal.isValid() && SilenceableCondVal.isValid() && + PreviousSilenceableCondVal == SilenceableCondVal) + return; + PreviousSilenceableCondVal = SilenceableCondVal; - S.Diag(L, diag) << R1 << R2; + unsigned DiagID = diag::warn_unreachable; + switch (UK) { + case reachable_code::UK_Break: + DiagID = diag::warn_unreachable_break; + break; + case reachable_code::UK_Return: + DiagID = diag::warn_unreachable_return; + break; + case reachable_code::UK_Loop_Increment: + DiagID = diag::warn_unreachable_loop_increment; + break; + case reachable_code::UK_Other: + break; + } + + S.Diag(L, DiagID) << R1 << R2; - SourceLocation Open = SilenceableCondVal.getBegin(); - if (Open.isValid()) { - SourceLocation Close = SilenceableCondVal.getEnd(); - Close = S.getLocForEndOfToken(Close); - if (Close.isValid()) { - S.Diag(Open, diag::note_unreachable_silence) + SourceLocation Open = SilenceableCondVal.getBegin(); + if (Open.isValid()) { + SourceLocation Close = S.getLocForEndOfToken(SilenceableCondVal.getEnd()); + if (Close.isValid()) { + S.Diag(Open, diag::note_unreachable_silence) << FixItHint::CreateInsertion(Open, "/* DISABLES CODE */ (") << FixItHint::CreateInsertion(Close, ")"); - } } } - }; + } + + void HandleUnreachableBlock(const CFGBlock *B) override { + // We only currently use this method for -Wtrivial-auto-var-init + if (!CheckTrivialAutoVarInit) + return; + + for (const CFGElement &Elem : *B) { + auto CS = Elem.getAs<CFGStmt>(); + if (!CS) + continue; + const auto *DS = dyn_cast<DeclStmt>(CS->getStmt()); + if (!DS) + continue; + for (const Decl *DI : DS->decls()) { + const auto *VD = dyn_cast<VarDecl>(DI); + if (!VD || !VD->getDeclName() || !VD->hasLocalStorage()) + continue; + if (!isTrivialInitializer(VD->getInit())) + continue; + if (!wouldTrivialAutoVarInitApply(S.getLangOpts(), VD)) + continue; + S.Diag(VD->getLocation(), diag::warn_trivial_auto_var_init_unreachable) + << VD; + } + } + } +}; } // anonymous namespace -/// CheckUnreachable - Check for unreachable code. -static void CheckUnreachable(Sema &S, AnalysisDeclContext &AC) { - // As a heuristic prune all diagnostics not in the main file. Currently - // the majority of warnings in headers are false positives. These - // are largely caused by configuration state, e.g. preprocessor - // defined code, etc. - // - // Note that this is also a performance optimization. Analyzing - // headers many times can be expensive. +static void CheckUnreachable(Sema &S, AnalysisDeclContext &AC, + bool CheckTrivialAutoVarInit) { if (!S.getSourceManager().isInMainFile(AC.getDecl()->getBeginLoc())) return; - UnreachableCodeHandler UC(S); + UnreachableCodeHandler UC(S, CheckTrivialAutoVarInit); reachable_code::FindUnreachableCode(AC, S.getPreprocessor(), UC); } @@ -3156,7 +3202,9 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( } // Warning: check for unreachable code - if (P.enableCheckUnreachable) { + bool CheckTrivialAutoVarInit = !Diags.isIgnored( + diag::warn_trivial_auto_var_init_unreachable, D->getBeginLoc()); + if (P.enableCheckUnreachable || CheckTrivialAutoVarInit) { // Only check for unreachable code on non-template instantiations. // Different template instantiations can effectively change the control-flow // and it is very difficult to prove that a snippet of code in a template @@ -3165,7 +3213,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( if (const FunctionDecl *Function = dyn_cast<FunctionDecl>(D)) isTemplateInstantiation = Function->isTemplateInstantiation(); if (!isTemplateInstantiation) - CheckUnreachable(S, AC); + CheckUnreachable(S, AC, CheckTrivialAutoVarInit); } // Check for thread safety violations diff --git a/clang/test/Sema/trivial-auto-var-init-warn.c b/clang/test/Sema/trivial-auto-var-init-warn.c new file mode 100644 index 0000000000000..732fa1179f48c --- /dev/null +++ b/clang/test/Sema/trivial-auto-var-init-warn.c @@ -0,0 +1,35 @@ +// RUN: %clang_cc1 -ftrivial-auto-var-init=zero -Wtrivial-auto-var-init -fsyntax-only -verify=zero %s +// RUN: %clang_cc1 -ftrivial-auto-var-init=pattern -Wtrivial-auto-var-init -fsyntax-only -verify=pattern %s +// RUN: %clang_cc1 -fsyntax-only -verify=noflag %s +// RUN: %clang_cc1 -ftrivial-auto-var-init=zero -Wno-trivial-auto-var-init -fsyntax-only -verify=suppressed %s +// RUN: %clang_cc1 -ftrivial-auto-var-init=zero -fsyntax-only -verify=default %s + +// noflag-no-diagnostics +// suppressed-no-diagnostics +// default-no-diagnostics + +void use(int *); + +void switch_precase(int c) { + switch (c) { + int x; // zero-warning{{variable 'x' is uninitialized and cannot be initialized with '-ftrivial-auto-var-init' because it is unreachable}} + // pattern-warning@-1{{variable 'x' is uninitialized and cannot be initialized with '-ftrivial-auto-var-init' because it is unreachable}} + case 0: + x = 1; + use(&x); + break; + } +} + +void goto_bypass(void) { + goto skip; + int y; // zero-warning{{variable 'y' is uninitialized and cannot be initialized with '-ftrivial-auto-var-init' because it is unreachable}} + // pattern-warning@-1{{variable 'y' is uninitialized and cannot be initialized with '-ftrivial-auto-var-init' because it is unreachable}} +skip: + use(&y); +} + +void normal_var(void) { + int x; + use(&x); +} diff --git a/clang/test/SemaCXX/trivial-auto-var-init-warn.cpp b/clang/test/SemaCXX/trivial-auto-var-init-warn.cpp new file mode 100644 index 0000000000000..9c21e77c901ca --- /dev/null +++ b/clang/test/SemaCXX/trivial-auto-var-init-warn.cpp @@ -0,0 +1,38 @@ +// RUN: %clang_cc1 -std=c++17 -ftrivial-auto-var-init=zero -Wtrivial-auto-var-init -fsyntax-only -verify %s + +void use(int *); +void use(void *); + +struct Trivial { + int a, b; +}; + +void uninitialized_attr(int c) { + switch (c) { + [[clang::uninitialized]] int x; + case 0: + x = 1; + use(&x); + break; + } +} + +void struct_precase(int c) { + switch (c) { + Trivial t; // expected-warning{{variable 't' is uninitialized and cannot be initialized with '-ftrivial-auto-var-init' because it is unreachable}} + case 0: + t.a = 1; + use(&t); + break; + } +} + +void int_precase(int c) { + switch (c) { + int x; // expected-warning{{variable 'x' is uninitialized and cannot be initialized with '-ftrivial-auto-var-init' because it is unreachable}} + case 0: + x = 1; + use(&x); + break; + } +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
