Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
417b4e4
Initial implementation
negativ Jul 28, 2025
af7d191
Fix build
negativ Jul 28, 2025
79acdec
Merge branch 'main' into cfg_analyzer_noreturn
negativ Jul 28, 2025
7def448
Merge branch 'main' into cfg_analyzer_noreturn
negativ Jul 29, 2025
3114b2f
Merge branch 'main' into cfg_analyzer_noreturn
negativ Jul 30, 2025
c214415
Merge branch 'main' into cfg_analyzer_noreturn
negativ Aug 1, 2025
e98e489
Merge branch 'main' into cfg_analyzer_noreturn
negativ Aug 4, 2025
e397c18
Merge branch 'main' into cfg_analyzer_noreturn
negativ Aug 5, 2025
866147e
Merge branch 'main' into cfg_analyzer_noreturn
negativ Aug 6, 2025
f11a757
Merge branch 'main' into cfg_analyzer_noreturn
negativ Aug 6, 2025
1a51c83
Merge branch 'main' into cfg_analyzer_noreturn
negativ Aug 10, 2025
c4dd550
Merge branch 'main' into cfg_analyzer_noreturn
negativ Aug 11, 2025
ecdbd16
Merge branch 'main' into cfg_analyzer_noreturn
negativ Aug 13, 2025
17c8f48
Merge branch 'main' into cfg_analyzer_noreturn
negativ Aug 16, 2025
1594dc2
Merge branch 'main' into cfg_analyzer_noreturn
negativ Aug 18, 2025
de0a4a6
Merge branch 'main' into cfg_analyzer_noreturn
negativ Aug 18, 2025
ac56b00
Merge branch 'main' into cfg_analyzer_noreturn
negativ Aug 25, 2025
3059f28
Merge branch 'main' into cfg_analyzer_noreturn
negativ Aug 25, 2025
f8ad3fb
Merge branch 'main' into cfg_analyzer_noreturn
negativ Aug 25, 2025
1207067
Merge branch 'main' into cfg_analyzer_noreturn
negativ Aug 28, 2025
67eea70
Merge branch 'main' into cfg_analyzer_noreturn
negativ Sep 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3599,6 +3599,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]
Expand Down
3 changes: 2 additions & 1 deletion clang/lib/Analysis/CFG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,80 @@ 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() {}
)"));

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)),
/*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> {
Expand Down