Skip to content

Commit 818bca8

Browse files
authored
[clang-tidy] [dataflow] Cache reference accessors for bugprone-unchecked-optional-access (llvm#128437)
Fixes llvm#126283 Extending llvm#112605 to cache const getters which return references. Fixes false positives from const reference accessors to object containing optional member
1 parent 992b451 commit 818bca8

File tree

3 files changed

+212
-1
lines changed

3 files changed

+212
-1
lines changed

clang-tools-extra/docs/ReleaseNotes.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ Changes in existing checks
112112
<clang-tidy/checks/bugprone/unchecked-optional-access>` fixing false
113113
positives from smart pointer accessors repeated in checking ``has_value``
114114
and accessing ``value``. The option `IgnoreSmartPointerDereference` should
115-
no longer be needed and will be removed.
115+
no longer be needed and will be removed. Also fixing false positive from
116+
const reference accessors to objects containing optional member.
116117

117118
- Improved :doc:`bugprone-unsafe-functions
118119
<clang-tidy/checks/bugprone/unsafe-functions>` check to allow specifying

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

+16
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,22 @@ void handleConstMemberCall(const CallExpr *CE,
580580
return;
581581
}
582582

583+
// Cache if the const method returns a reference
584+
if (RecordLoc != nullptr && CE->isGLValue()) {
585+
const FunctionDecl *DirectCallee = CE->getDirectCallee();
586+
if (DirectCallee == nullptr)
587+
return;
588+
589+
StorageLocation &Loc =
590+
State.Lattice.getOrCreateConstMethodReturnStorageLocation(
591+
*RecordLoc, DirectCallee, State.Env, [&](StorageLocation &Loc) {
592+
// no-op
593+
});
594+
595+
State.Env.setStorageLocation(*CE, Loc);
596+
return;
597+
}
598+
583599
// Cache if the const method returns a boolean or pointer type.
584600
// We may decide to cache other return types in the future.
585601
if (RecordLoc != nullptr &&

clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp

+194
Original file line numberDiff line numberDiff line change
@@ -3863,6 +3863,200 @@ TEST_P(UncheckedOptionalAccessTest, ConstBoolAccessorWithModInBetween) {
38633863
)cc");
38643864
}
38653865

3866+
TEST_P(UncheckedOptionalAccessTest,
3867+
ConstRefAccessorToOptionalViaConstRefAccessorToHoldingObject) {
3868+
ExpectDiagnosticsFor(R"cc(
3869+
#include "unchecked_optional_access_test.h"
3870+
3871+
struct A {
3872+
const $ns::$optional<int>& get() const { return x; }
3873+
3874+
$ns::$optional<int> x;
3875+
};
3876+
3877+
struct B {
3878+
const A& getA() const { return a; }
3879+
3880+
A a;
3881+
};
3882+
3883+
void target(B& b) {
3884+
if (b.getA().get().has_value()) {
3885+
b.getA().get().value();
3886+
}
3887+
}
3888+
)cc");
3889+
}
3890+
3891+
TEST_P(
3892+
UncheckedOptionalAccessTest,
3893+
ConstRefAccessorToOptionalViaConstRefAccessorToHoldingObjectWithoutValueCheck) {
3894+
ExpectDiagnosticsFor(R"cc(
3895+
#include "unchecked_optional_access_test.h"
3896+
3897+
struct A {
3898+
const $ns::$optional<int>& get() const { return x; }
3899+
3900+
$ns::$optional<int> x;
3901+
};
3902+
3903+
struct B {
3904+
const A& getA() const { return a; }
3905+
3906+
A a;
3907+
};
3908+
3909+
void target(B& b) {
3910+
b.getA().get().value(); // [[unsafe]]
3911+
}
3912+
)cc");
3913+
}
3914+
3915+
TEST_P(UncheckedOptionalAccessTest,
3916+
ConstRefToOptionalSavedAsTemporaryVariable) {
3917+
ExpectDiagnosticsFor(R"cc(
3918+
#include "unchecked_optional_access_test.h"
3919+
3920+
struct A {
3921+
const $ns::$optional<int>& get() const { return x; }
3922+
3923+
$ns::$optional<int> x;
3924+
};
3925+
3926+
struct B {
3927+
const A& getA() const { return a; }
3928+
3929+
A a;
3930+
};
3931+
3932+
void target(B& b) {
3933+
const auto& opt = b.getA().get();
3934+
if (opt.has_value()) {
3935+
opt.value();
3936+
}
3937+
}
3938+
)cc");
3939+
}
3940+
3941+
TEST_P(UncheckedOptionalAccessTest,
3942+
ConstRefAccessorToOptionalViaAccessorToHoldingObjectByValue) {
3943+
ExpectDiagnosticsFor(R"cc(
3944+
#include "unchecked_optional_access_test.h"
3945+
3946+
struct A {
3947+
const $ns::$optional<int>& get() const { return x; }
3948+
3949+
$ns::$optional<int> x;
3950+
};
3951+
3952+
struct B {
3953+
const A copyA() const { return a; }
3954+
3955+
A a;
3956+
};
3957+
3958+
void target(B& b) {
3959+
if (b.copyA().get().has_value()) {
3960+
b.copyA().get().value(); // [[unsafe]]
3961+
}
3962+
}
3963+
)cc");
3964+
}
3965+
3966+
TEST_P(UncheckedOptionalAccessTest,
3967+
ConstRefAccessorToOptionalViaNonConstRefAccessorToHoldingObject) {
3968+
ExpectDiagnosticsFor(R"cc(
3969+
#include "unchecked_optional_access_test.h"
3970+
3971+
struct A {
3972+
const $ns::$optional<int>& get() const { return x; }
3973+
3974+
$ns::$optional<int> x;
3975+
};
3976+
3977+
struct B {
3978+
A& getA() { return a; }
3979+
3980+
A a;
3981+
};
3982+
3983+
void target(B& b) {
3984+
if (b.getA().get().has_value()) {
3985+
b.getA().get().value(); // [[unsafe]]
3986+
}
3987+
}
3988+
)cc");
3989+
}
3990+
3991+
TEST_P(
3992+
UncheckedOptionalAccessTest,
3993+
ConstRefAccessorToOptionalViaConstRefAccessorToHoldingObjectWithModAfterCheck) {
3994+
ExpectDiagnosticsFor(R"cc(
3995+
#include "unchecked_optional_access_test.h"
3996+
3997+
struct A {
3998+
const $ns::$optional<int>& get() const { return x; }
3999+
4000+
$ns::$optional<int> x;
4001+
};
4002+
4003+
struct B {
4004+
const A& getA() const { return a; }
4005+
4006+
A& getA() { return a; }
4007+
4008+
void clear() { a = A{}; }
4009+
4010+
A a;
4011+
};
4012+
4013+
void target(B& b) {
4014+
// changing field A via non-const getter after const getter check
4015+
if (b.getA().get().has_value()) {
4016+
b.getA() = A{};
4017+
b.getA().get().value(); // [[unsafe]]
4018+
}
4019+
4020+
// calling non-const method which might change field A
4021+
if (b.getA().get().has_value()) {
4022+
b.clear();
4023+
b.getA().get().value(); // [[unsafe]]
4024+
}
4025+
}
4026+
)cc");
4027+
}
4028+
4029+
TEST_P(
4030+
UncheckedOptionalAccessTest,
4031+
ConstRefAccessorToOptionalViaConstRefAccessorToHoldingObjectWithAnotherConstCallAfterCheck) {
4032+
ExpectDiagnosticsFor(R"cc(
4033+
#include "unchecked_optional_access_test.h"
4034+
4035+
struct A {
4036+
const $ns::$optional<int>& get() const { return x; }
4037+
4038+
$ns::$optional<int> x;
4039+
};
4040+
4041+
struct B {
4042+
const A& getA() const { return a; }
4043+
4044+
void callWithoutChanges() const {
4045+
// no-op
4046+
}
4047+
4048+
A a;
4049+
};
4050+
4051+
void target(B& b) {
4052+
if (b.getA().get().has_value()) {
4053+
b.callWithoutChanges(); // calling const method which cannot change A
4054+
b.getA().get().value();
4055+
}
4056+
}
4057+
)cc");
4058+
}
4059+
38664060
// FIXME: Add support for:
38674061
// - constructors (copy, move)
38684062
// - assignment operators (default, copy, move)

0 commit comments

Comments
 (0)