Skip to content

Commit 417b4e4

Browse files
authored
Initial implementation
1 parent d803c61 commit 417b4e4

File tree

5 files changed

+99
-1
lines changed

5 files changed

+99
-1
lines changed

clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,17 @@ void nullable_value_after_swap(BloombergLP::bdlb::NullableValue<int> &opt1, Bloo
141141
}
142142
}
143143

144+
void assertion_handler() __attribute__((analyzer_noreturn));
145+
146+
void function_calling_analyzer_noreturn(const bsl::optional<int>& opt)
147+
{
148+
if (!opt) {
149+
assertion_handler();
150+
}
151+
152+
*opt; // no-warning: The previous condition guards this dereference.
153+
}
154+
144155
template <typename T>
145156
void function_template_without_user(const absl::optional<T> &opt) {
146157
opt.value(); // no-warning

clang/include/clang/AST/Decl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2668,6 +2668,10 @@ class FunctionDecl : public DeclaratorDecl,
26682668
/// an attribute on its declaration or its type.
26692669
bool isNoReturn() const;
26702670

2671+
/// Determines whether this function is known to be 'noreturn' for analyzer,
2672+
/// through an `analyzer_noreturn` attribute on its declaration.
2673+
bool isAnalyzerNoReturn() const;
2674+
26712675
/// True if the function was a definition but its body was skipped.
26722676
bool hasSkippedBody() const { return FunctionDeclBits.HasSkippedBody; }
26732677
void setHasSkippedBody(bool Skipped = true) {

clang/lib/AST/Decl.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3596,6 +3596,10 @@ bool FunctionDecl::isNoReturn() const {
35963596
return false;
35973597
}
35983598

3599+
bool FunctionDecl::isAnalyzerNoReturn() const {
3600+
return hasAttr<AnalyzerNoReturnAttr>();
3601+
}
3602+
35993603
bool FunctionDecl::isMemberLikeConstrainedFriend() const {
36003604
// C++20 [temp.friend]p9:
36013605
// A non-template friend declaration with a requires-clause [or]

clang/lib/Analysis/CFG.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2833,7 +2833,8 @@ CFGBlock *CFGBuilder::VisitCallExpr(CallExpr *C, AddStmtChoice asc) {
28332833
if (!FD->isVariadic())
28342834
findConstructionContextsForArguments(C);
28352835

2836-
if (FD->isNoReturn() || C->isBuiltinAssumeFalse(*Context))
2836+
if (FD->isNoReturn() || FD->isAnalyzerNoReturn() ||
2837+
C->isBuiltinAssumeFalse(*Context))
28372838
NoReturn = true;
28382839
if (FD->hasAttr<NoThrowAttr>())
28392840
AddEHEdge = false;

clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,84 @@ TEST_F(NoreturnDestructorTest, ConditionalOperatorNestedBranchReturns) {
693693
// FIXME: Called functions at point `p` should contain only "foo".
694694
}
695695

696+
class AnalyzerNoreturnTest : public Test {
697+
protected:
698+
template <typename Matcher>
699+
void runDataflow(llvm::StringRef Code, Matcher Expectations) {
700+
tooling::FileContentMappings FilesContents;
701+
FilesContents.push_back(
702+
std::make_pair<std::string, std::string>("noreturn_test_defs.h", R"(
703+
void assertionHandler() __attribute__((analyzer_noreturn));
704+
705+
void trap() {}
706+
)"));
707+
708+
CFG::BuildOptions Opts;
709+
Opts.ExtendedNoReturnAnalysis = true;
710+
711+
ASSERT_THAT_ERROR(
712+
test::checkDataflow<FunctionCallAnalysis>(
713+
AnalysisInputs<FunctionCallAnalysis>(
714+
Code, ast_matchers::hasName("target"),
715+
[](ASTContext &C, Environment &) {
716+
return FunctionCallAnalysis(C);
717+
})
718+
.withASTBuildArgs({"-fsyntax-only", "-std=c++17"})
719+
.withASTBuildVirtualMappedFiles(std::move(FilesContents))
720+
.withCfgBuildOptions(std::move(Opts)),
721+
/*VerifyResults=*/
722+
[&Expectations](
723+
const llvm::StringMap<
724+
DataflowAnalysisState<FunctionCallLattice>> &Results,
725+
const AnalysisOutputs &) {
726+
EXPECT_THAT(Results, Expectations);
727+
}),
728+
llvm::Succeeded());
729+
}
730+
};
731+
732+
TEST_F(AnalyzerNoreturnTest, Breathing) {
733+
std::string Code = R"(
734+
#include "noreturn_test_defs.h"
735+
736+
void target() {
737+
trap();
738+
// [[p]]
739+
}
740+
)";
741+
runDataflow(Code, UnorderedElementsAre(IsStringMapEntry(
742+
"p", HoldsFunctionCallLattice(HasCalledFunctions(
743+
UnorderedElementsAre("trap"))))));
744+
}
745+
746+
TEST_F(AnalyzerNoreturnTest, DirectNoReturnCall) {
747+
std::string Code = R"(
748+
#include "noreturn_test_defs.h"
749+
750+
void target() {
751+
assertionHandler();
752+
trap();
753+
// [[p]]
754+
}
755+
)";
756+
runDataflow(Code, IsEmpty());
757+
}
758+
759+
TEST_F(AnalyzerNoreturnTest, CanonicalDeclCallCheck) {
760+
std::string Code = R"(
761+
#include "noreturn_test_defs.h"
762+
763+
extern void assertionHandler();
764+
765+
void target() {
766+
assertionHandler();
767+
trap();
768+
// [[p]]
769+
}
770+
)";
771+
runDataflow(Code, IsEmpty());
772+
}
773+
696774
// Models an analysis that uses flow conditions.
697775
class SpecialBoolAnalysis final
698776
: public DataflowAnalysis<SpecialBoolAnalysis, NoopLattice> {

0 commit comments

Comments
 (0)