https://github.com/negativ updated https://github.com/llvm/llvm-project/pull/150952
>From 417b4e465744f9a1d1704c87da4587626c9f5411 Mon Sep 17 00:00:00 2001 From: Andrey Karlov <dein.nega...@gmail.com> Date: Mon, 28 Jul 2025 16:23:21 +0300 Subject: [PATCH 1/2] Initial implementation --- .../bugprone/unchecked-optional-access.cpp | 11 +++ clang/include/clang/AST/Decl.h | 4 + clang/lib/AST/Decl.cpp | 4 + clang/lib/Analysis/CFG.cpp | 3 +- .../TypeErasedDataflowAnalysisTest.cpp | 78 +++++++++++++++++++ 5 files changed, 99 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp index 3167b85f0e024..4911157828765 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp @@ -141,6 +141,17 @@ void nullable_value_after_swap(BloombergLP::bdlb::NullableValue<int> &opt1, Bloo } } +void assertion_handler() __attribute__((analyzer_noreturn)); + +void function_calling_analyzer_noreturn(const bsl::optional<int>& opt) +{ + if (!opt) { + assertion_handler(); + } + + *opt; // no-warning: The previous condition guards this dereference. +} + template <typename T> void function_template_without_user(const absl::optional<T> &opt) { opt.value(); // no-warning diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index 08fe1f881503b..d58920270083a 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -2668,6 +2668,10 @@ class FunctionDecl : public DeclaratorDecl, /// an attribute on its declaration or its type. bool isNoReturn() const; + /// Determines whether this function is known to be 'noreturn' for analyzer, + /// through an `analyzer_noreturn` attribute on its declaration. + bool isAnalyzerNoReturn() const; + /// True if the function was a definition but its body was skipped. bool hasSkippedBody() const { return FunctionDeclBits.HasSkippedBody; } void setHasSkippedBody(bool Skipped = true) { diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 83fcd87aec2f8..3c0b55f3e3b68 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -3596,6 +3596,10 @@ bool FunctionDecl::isNoReturn() const { return false; } +bool FunctionDecl::isAnalyzerNoReturn() const { + return hasAttr<AnalyzerNoReturnAttr>(); +} + bool FunctionDecl::isMemberLikeConstrainedFriend() const { // C++20 [temp.friend]p9: // A non-template friend declaration with a requires-clause [or] diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp index d960d5130332b..60a2d113c08e2 100644 --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -2833,7 +2833,8 @@ CFGBlock *CFGBuilder::VisitCallExpr(CallExpr *C, AddStmtChoice asc) { if (!FD->isVariadic()) findConstructionContextsForArguments(C); - if (FD->isNoReturn() || C->isBuiltinAssumeFalse(*Context)) + if (FD->isNoReturn() || FD->isAnalyzerNoReturn() || + C->isBuiltinAssumeFalse(*Context)) NoReturn = true; if (FD->hasAttr<NoThrowAttr>()) AddEHEdge = false; diff --git a/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp b/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp index 9fb7bebdbe41e..7e38410ae61d7 100644 --- a/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp @@ -693,6 +693,84 @@ TEST_F(NoreturnDestructorTest, ConditionalOperatorNestedBranchReturns) { // FIXME: Called functions at point `p` should contain only "foo". } +class AnalyzerNoreturnTest : public Test { +protected: + template <typename Matcher> + void runDataflow(llvm::StringRef Code, Matcher Expectations) { + tooling::FileContentMappings FilesContents; + FilesContents.push_back( + std::make_pair<std::string, std::string>("noreturn_test_defs.h", R"( + void assertionHandler() __attribute__((analyzer_noreturn)); + + void trap() {} + )")); + + CFG::BuildOptions Opts; + Opts.ExtendedNoReturnAnalysis = true; + + ASSERT_THAT_ERROR( + test::checkDataflow<FunctionCallAnalysis>( + AnalysisInputs<FunctionCallAnalysis>( + Code, ast_matchers::hasName("target"), + [](ASTContext &C, Environment &) { + return FunctionCallAnalysis(C); + }) + .withASTBuildArgs({"-fsyntax-only", "-std=c++17"}) + .withASTBuildVirtualMappedFiles(std::move(FilesContents)) + .withCfgBuildOptions(std::move(Opts)), + /*VerifyResults=*/ + [&Expectations]( + const llvm::StringMap< + DataflowAnalysisState<FunctionCallLattice>> &Results, + const AnalysisOutputs &) { + EXPECT_THAT(Results, Expectations); + }), + llvm::Succeeded()); + } +}; + +TEST_F(AnalyzerNoreturnTest, Breathing) { + std::string Code = R"( + #include "noreturn_test_defs.h" + + void target() { + trap(); + // [[p]] + } + )"; + runDataflow(Code, UnorderedElementsAre(IsStringMapEntry( + "p", HoldsFunctionCallLattice(HasCalledFunctions( + UnorderedElementsAre("trap")))))); +} + +TEST_F(AnalyzerNoreturnTest, DirectNoReturnCall) { + std::string Code = R"( + #include "noreturn_test_defs.h" + + void target() { + assertionHandler(); + trap(); + // [[p]] + } + )"; + runDataflow(Code, IsEmpty()); +} + +TEST_F(AnalyzerNoreturnTest, CanonicalDeclCallCheck) { + std::string Code = R"( + #include "noreturn_test_defs.h" + + extern void assertionHandler(); + + void target() { + assertionHandler(); + trap(); + // [[p]] + } + )"; + runDataflow(Code, IsEmpty()); +} + // Models an analysis that uses flow conditions. class SpecialBoolAnalysis final : public DataflowAnalysis<SpecialBoolAnalysis, NoopLattice> { >From af7d1912412726e1831c1a11f52ca743912a34e2 Mon Sep 17 00:00:00 2001 From: Andrey Karlov <dein.nega...@gmail.com> Date: Mon, 28 Jul 2025 17:00:40 +0300 Subject: [PATCH 2/2] Fix build --- .../FlowSensitive/TypeErasedDataflowAnalysisTest.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp b/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp index 7e38410ae61d7..d1dd4ff3ea33e 100644 --- a/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp @@ -705,9 +705,6 @@ class AnalyzerNoreturnTest : public Test { void trap() {} )")); - CFG::BuildOptions Opts; - Opts.ExtendedNoReturnAnalysis = true; - ASSERT_THAT_ERROR( test::checkDataflow<FunctionCallAnalysis>( AnalysisInputs<FunctionCallAnalysis>( @@ -716,8 +713,7 @@ class AnalyzerNoreturnTest : public Test { return FunctionCallAnalysis(C); }) .withASTBuildArgs({"-fsyntax-only", "-std=c++17"}) - .withASTBuildVirtualMappedFiles(std::move(FilesContents)) - .withCfgBuildOptions(std::move(Opts)), + .withASTBuildVirtualMappedFiles(std::move(FilesContents)), /*VerifyResults=*/ [&Expectations]( const llvm::StringMap< _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits