Skip to content

Commit 66bbbf2

Browse files
authored
[clang][dataflow] Cache accessors returning pointers in bugprone-unchecked-optional-access (#113922)
Previously, we covered returning refs, or copies of optional, and bools. Now cover returning pointers (to any type). This is useful for cases like operator-> of smart pointers. Addresses more of issue #58510
1 parent 8274be5 commit 66bbbf2

File tree

3 files changed

+87
-13
lines changed

3 files changed

+87
-13
lines changed

clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ struct UncheckedOptionalAccessModelOptions {
3737
/// can't identify when their results are used safely (across calls),
3838
/// resulting in false positives in all such cases. Note: this option does not
3939
/// cover access through `operator[]`.
40+
/// FIXME: we currently cache and equate the result of const accessors
41+
/// returning pointers, so cover the case of operator-> followed by
42+
/// operator->, which covers the common case of smart pointers. We also cover
43+
/// some limited cases of returning references (if return type is an optional
44+
/// type), so cover some cases of operator* followed by operator*. We don't
45+
/// cover mixing operator-> and operator*. Once we are confident in this const
46+
/// accessor caching, we shouldn't need the IgnoreSmartPointerDereference
47+
/// option anymore.
4048
bool IgnoreSmartPointerDereference = false;
4149
};
4250

clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,11 @@ auto isZeroParamConstMemberCall() {
338338
callee(cxxMethodDecl(parameterCountIs(0), isConst())));
339339
}
340340

341+
auto isZeroParamConstMemberOperatorCall() {
342+
return cxxOperatorCallExpr(
343+
callee(cxxMethodDecl(parameterCountIs(0), isConst())));
344+
}
345+
341346
auto isNonConstMemberCall() {
342347
return cxxMemberCallExpr(callee(cxxMethodDecl(unless(isConst()))));
343348
}
@@ -572,9 +577,10 @@ void handleConstMemberCall(const CallExpr *CE,
572577
return;
573578
}
574579

575-
// Cache if the const method returns a boolean type.
580+
// Cache if the const method returns a boolean or pointer type.
576581
// We may decide to cache other return types in the future.
577-
if (RecordLoc != nullptr && CE->getType()->isBooleanType()) {
582+
if (RecordLoc != nullptr &&
583+
(CE->getType()->isBooleanType() || CE->getType()->isPointerType())) {
578584
Value *Val = State.Lattice.getOrCreateConstMethodReturnValue(*RecordLoc, CE,
579585
State.Env);
580586
if (Val == nullptr)
@@ -597,6 +603,14 @@ void transferValue_ConstMemberCall(const CXXMemberCallExpr *MCE,
597603
MCE, dataflow::getImplicitObjectLocation(*MCE, State.Env), Result, State);
598604
}
599605

606+
void transferValue_ConstMemberOperatorCall(
607+
const CXXOperatorCallExpr *OCE, const MatchFinder::MatchResult &Result,
608+
LatticeTransferState &State) {
609+
auto *RecordLoc = cast_or_null<dataflow::RecordStorageLocation>(
610+
State.Env.getStorageLocation(*OCE->getArg(0)));
611+
handleConstMemberCall(OCE, RecordLoc, Result, State);
612+
}
613+
600614
void handleNonConstMemberCall(const CallExpr *CE,
601615
dataflow::RecordStorageLocation *RecordLoc,
602616
const MatchFinder::MatchResult &Result,
@@ -1020,6 +1034,8 @@ auto buildTransferMatchSwitch() {
10201034
// const accessor calls
10211035
.CaseOfCFGStmt<CXXMemberCallExpr>(isZeroParamConstMemberCall(),
10221036
transferValue_ConstMemberCall)
1037+
.CaseOfCFGStmt<CXXOperatorCallExpr>(isZeroParamConstMemberOperatorCall(),
1038+
transferValue_ConstMemberOperatorCall)
10231039
// non-const member calls that may modify the state of an object.
10241040
.CaseOfCFGStmt<CXXMemberCallExpr>(isNonConstMemberCall(),
10251041
transferValue_NonConstMemberCall)

clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,28 +1282,35 @@ static raw_ostream &operator<<(raw_ostream &OS,
12821282
class UncheckedOptionalAccessTest
12831283
: public ::testing::TestWithParam<OptionalTypeIdentifier> {
12841284
protected:
1285-
void ExpectDiagnosticsFor(std::string SourceCode) {
1286-
ExpectDiagnosticsFor(SourceCode, ast_matchers::hasName("target"));
1285+
void ExpectDiagnosticsFor(std::string SourceCode,
1286+
bool IgnoreSmartPointerDereference = true) {
1287+
ExpectDiagnosticsFor(SourceCode, ast_matchers::hasName("target"),
1288+
IgnoreSmartPointerDereference);
12871289
}
12881290

1289-
void ExpectDiagnosticsForLambda(std::string SourceCode) {
1291+
void ExpectDiagnosticsForLambda(std::string SourceCode,
1292+
bool IgnoreSmartPointerDereference = true) {
12901293
ExpectDiagnosticsFor(
1291-
SourceCode, ast_matchers::hasDeclContext(
1292-
ast_matchers::cxxRecordDecl(ast_matchers::isLambda())));
1294+
SourceCode,
1295+
ast_matchers::hasDeclContext(
1296+
ast_matchers::cxxRecordDecl(ast_matchers::isLambda())),
1297+
IgnoreSmartPointerDereference);
12931298
}
12941299

12951300
template <typename FuncDeclMatcher>
1296-
void ExpectDiagnosticsFor(std::string SourceCode,
1297-
FuncDeclMatcher FuncMatcher) {
1301+
void ExpectDiagnosticsFor(std::string SourceCode, FuncDeclMatcher FuncMatcher,
1302+
bool IgnoreSmartPointerDereference = true) {
12981303
// Run in C++17 and C++20 mode to cover differences in the AST between modes
12991304
// (e.g. C++20 can contain `CXXRewrittenBinaryOperator`).
13001305
for (const char *CxxMode : {"-std=c++17", "-std=c++20"})
1301-
ExpectDiagnosticsFor(SourceCode, FuncMatcher, CxxMode);
1306+
ExpectDiagnosticsFor(SourceCode, FuncMatcher, CxxMode,
1307+
IgnoreSmartPointerDereference);
13021308
}
13031309

13041310
template <typename FuncDeclMatcher>
13051311
void ExpectDiagnosticsFor(std::string SourceCode, FuncDeclMatcher FuncMatcher,
1306-
const char *CxxMode) {
1312+
const char *CxxMode,
1313+
bool IgnoreSmartPointerDereference) {
13071314
ReplaceAllOccurrences(SourceCode, "$ns", GetParam().NamespaceName);
13081315
ReplaceAllOccurrences(SourceCode, "$optional", GetParam().TypeName);
13091316

@@ -1328,8 +1335,7 @@ class UncheckedOptionalAccessTest
13281335
template <typename T>
13291336
T Make();
13301337
)");
1331-
UncheckedOptionalAccessModelOptions Options{
1332-
/*IgnoreSmartPointerDereference=*/true};
1338+
UncheckedOptionalAccessModelOptions Options{IgnoreSmartPointerDereference};
13331339
std::vector<SourceLocation> Diagnostics;
13341340
llvm::Error Error = checkDataflow<UncheckedOptionalAccessModel>(
13351341
AnalysisInputs<UncheckedOptionalAccessModel>(
@@ -3721,6 +3727,50 @@ TEST_P(UncheckedOptionalAccessTest, ConstByValueAccessorWithModInBetween) {
37213727
)cc");
37223728
}
37233729

3730+
TEST_P(UncheckedOptionalAccessTest, ConstPointerAccessor) {
3731+
ExpectDiagnosticsFor(R"cc(
3732+
#include "unchecked_optional_access_test.h"
3733+
3734+
struct A {
3735+
$ns::$optional<int> x;
3736+
};
3737+
3738+
struct MyUniquePtr {
3739+
A* operator->() const;
3740+
};
3741+
3742+
void target(MyUniquePtr p) {
3743+
if (p->x) {
3744+
*p->x;
3745+
}
3746+
}
3747+
)cc",
3748+
/*IgnoreSmartPointerDereference=*/false);
3749+
}
3750+
3751+
TEST_P(UncheckedOptionalAccessTest, ConstPointerAccessorWithModInBetween) {
3752+
ExpectDiagnosticsFor(R"cc(
3753+
#include "unchecked_optional_access_test.h"
3754+
3755+
struct A {
3756+
$ns::$optional<int> x;
3757+
};
3758+
3759+
struct MyUniquePtr {
3760+
A* operator->() const;
3761+
void reset(A*);
3762+
};
3763+
3764+
void target(MyUniquePtr p) {
3765+
if (p->x) {
3766+
p.reset(nullptr);
3767+
*p->x; // [[unsafe]]
3768+
}
3769+
}
3770+
)cc",
3771+
/*IgnoreSmartPointerDereference=*/false);
3772+
}
3773+
37243774
TEST_P(UncheckedOptionalAccessTest, ConstBoolAccessor) {
37253775
ExpectDiagnosticsFor(R"cc(
37263776
#include "unchecked_optional_access_test.h"

0 commit comments

Comments
 (0)