Skip to content

Commit dd38caf

Browse files
committed
[clang][dataflow] Track optional contents in optional model.
This patch adds partial support for tracking (i.e. modeling) the contents of an optional value. Specifically, it supports tracking the value after it is accessed. We leave tracking constructed/assigned contents to a future patch. Differential Revision: https://reviews.llvm.org/D124932
1 parent bc2c759 commit dd38caf

File tree

2 files changed

+161
-23
lines changed

2 files changed

+161
-23
lines changed

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

Lines changed: 73 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,15 @@ StructValue &createOptionalValue(Environment &Env, BoolValue &HasValueVal) {
179179

180180
/// Returns the symbolic value that represents the "has_value" property of the
181181
/// optional value `OptionalVal`. Returns null if `OptionalVal` is null.
182-
BoolValue *getHasValue(Value *OptionalVal) {
183-
if (OptionalVal) {
184-
return cast<BoolValue>(OptionalVal->getProperty("has_value"));
182+
BoolValue *getHasValue(Environment &Env, Value *OptionalVal) {
183+
if (OptionalVal != nullptr) {
184+
auto *HasValueVal =
185+
cast_or_null<BoolValue>(OptionalVal->getProperty("has_value"));
186+
if (HasValueVal == nullptr) {
187+
HasValueVal = &Env.makeAtomicBoolValue();
188+
OptionalVal->setProperty("has_value", *HasValueVal);
189+
}
190+
return HasValueVal;
185191
}
186192
return nullptr;
187193
}
@@ -218,6 +224,50 @@ int countOptionalWrappers(const ASTContext &ASTCtx, QualType Type) {
218224
.getDesugaredType(ASTCtx));
219225
}
220226

227+
/// Tries to initialize the `optional`'s value (that is, contents), and return
228+
/// its location. Returns nullptr if the value can't be represented.
229+
StorageLocation *maybeInitializeOptionalValueMember(QualType Q,
230+
Value &OptionalVal,
231+
Environment &Env) {
232+
// The "value" property represents a synthetic field. As such, it needs
233+
// `StorageLocation`, like normal fields (and other variables). So, we model
234+
// it with a `ReferenceValue`, since that includes a storage location. Once
235+
// the property is set, it will be shared by all environments that access the
236+
// `Value` representing the optional (here, `OptionalVal`).
237+
if (auto *ValueProp = OptionalVal.getProperty("value")) {
238+
auto *ValueRef = clang::cast<ReferenceValue>(ValueProp);
239+
auto &ValueLoc = ValueRef->getPointeeLoc();
240+
if (Env.getValue(ValueLoc) == nullptr) {
241+
// The property was previously set, but the value has been lost. This can
242+
// happen, for example, because of an environment merge (where the two
243+
// environments mapped the property to different values, which resulted in
244+
// them both being discarded), or when two blocks in the CFG, with neither
245+
// a dominator of the other, visit the same optional value, or even when a
246+
// block is revisited during testing to collect per-statement state.
247+
// FIXME: This situation means that the optional contents are not shared
248+
// between branches and the like. Practically, this lack of sharing
249+
// reduces the precision of the model when the contents are relevant to
250+
// the check, like another optional or a boolean that influences control
251+
// flow.
252+
auto *ValueVal = Env.createValue(ValueLoc.getType());
253+
if (ValueVal == nullptr)
254+
return nullptr;
255+
Env.setValue(ValueLoc, *ValueVal);
256+
}
257+
return &ValueLoc;
258+
}
259+
260+
auto Ty = stripReference(Q);
261+
auto *ValueVal = Env.createValue(Ty);
262+
if (ValueVal == nullptr)
263+
return nullptr;
264+
auto &ValueLoc = Env.createStorageLocation(Ty);
265+
Env.setValue(ValueLoc, *ValueVal);
266+
auto ValueRef = std::make_unique<ReferenceValue>(ValueLoc);
267+
OptionalVal.setProperty("value", Env.takeOwnership(std::move(ValueRef)));
268+
return &ValueLoc;
269+
}
270+
221271
void initializeOptionalReference(const Expr *OptionalExpr,
222272
const MatchFinder::MatchResult &,
223273
LatticeTransferState &State) {
@@ -233,11 +283,16 @@ void transferUnwrapCall(const Expr *UnwrapExpr, const Expr *ObjectExpr,
233283
LatticeTransferState &State) {
234284
if (auto *OptionalVal =
235285
State.Env.getValue(*ObjectExpr, SkipPast::ReferenceThenPointer)) {
236-
auto *HasValueVal = getHasValue(OptionalVal);
237-
assert(HasValueVal != nullptr);
238-
239-
if (State.Env.flowConditionImplies(*HasValueVal))
240-
return;
286+
if (State.Env.getStorageLocation(*UnwrapExpr, SkipPast::None) == nullptr)
287+
if (auto *Loc = maybeInitializeOptionalValueMember(
288+
UnwrapExpr->getType(), *OptionalVal, State.Env))
289+
State.Env.setStorageLocation(*UnwrapExpr, *Loc);
290+
291+
auto *Prop = OptionalVal->getProperty("has_value");
292+
if (auto *HasValueVal = cast_or_null<BoolValue>(Prop)) {
293+
if (State.Env.flowConditionImplies(*HasValueVal))
294+
return;
295+
}
241296
}
242297

243298
// Record that this unwrap is *not* provably safe.
@@ -258,12 +313,9 @@ void transferMakeOptionalCall(const CallExpr *E,
258313
void transferOptionalHasValueCall(const CXXMemberCallExpr *CallExpr,
259314
const MatchFinder::MatchResult &,
260315
LatticeTransferState &State) {
261-
if (auto *OptionalVal = cast_or_null<StructValue>(
262-
State.Env.getValue(*CallExpr->getImplicitObjectArgument(),
263-
SkipPast::ReferenceThenPointer))) {
264-
auto *HasValueVal = getHasValue(OptionalVal);
265-
assert(HasValueVal != nullptr);
266-
316+
if (auto *HasValueVal = getHasValue(
317+
State.Env, State.Env.getValue(*CallExpr->getImplicitObjectArgument(),
318+
SkipPast::ReferenceThenPointer))) {
267319
auto &CallExprLoc = State.Env.createStorageLocation(*CallExpr);
268320
State.Env.setValue(CallExprLoc, *HasValueVal);
269321
State.Env.setStorageLocation(*CallExpr, CallExprLoc);
@@ -284,12 +336,11 @@ void transferValueOrImpl(const clang::Expr *ValueOrPredExpr,
284336
Result.Nodes.getNodeAs<clang::CXXMemberCallExpr>(ValueOrCallID)
285337
->getImplicitObjectArgument();
286338

287-
auto *OptionalVal = cast_or_null<StructValue>(
288-
Env.getValue(*ObjectArgumentExpr, SkipPast::ReferenceThenPointer));
289-
if (OptionalVal == nullptr)
339+
auto *HasValueVal = getHasValue(
340+
State.Env,
341+
State.Env.getValue(*ObjectArgumentExpr, SkipPast::ReferenceThenPointer));
342+
if (HasValueVal == nullptr)
290343
return;
291-
auto *HasValueVal = getHasValue(OptionalVal);
292-
assert(HasValueVal != nullptr);
293344

294345
auto *ExprValue = cast_or_null<BoolValue>(
295346
State.Env.getValue(*ValueOrPredExpr, SkipPast::None));
@@ -376,8 +427,9 @@ getValueOrConversionHasValue(const FunctionDecl &F, const Expr &E,
376427

377428
// This is a constructor/assignment call for `optional<T>` with argument of
378429
// type `optional<U>` such that `T` is constructible from `U`.
379-
if (BoolValue *Val = getHasValue(State.Env.getValue(E, SkipPast::Reference)))
380-
return *Val;
430+
if (auto *HasValueVal =
431+
getHasValue(State.Env, State.Env.getValue(E, SkipPast::Reference)))
432+
return *HasValueVal;
381433
return State.Env.makeAtomicBoolValue();
382434
}
383435

clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2165,7 +2165,6 @@ TEST_P(UncheckedOptionalAccessTest, CallReturningOptional) {
21652165
}
21662166
)",
21672167
UnorderedElementsAre(Pair("check-1", "unsafe: input.cc:9:7")));
2168-
21692168
ExpectLatticeChecksFor(
21702169
R"(
21712170
#include "unchecked_optional_access_test.h"
@@ -2231,8 +2230,95 @@ TEST_P(UncheckedOptionalAccessTest, WithAlias) {
22312230
UnorderedElementsAre(Pair("check", "unsafe: input.cc:8:7")));
22322231
}
22332232

2233+
TEST_P(UncheckedOptionalAccessTest, OptionalValueOptional) {
2234+
// Basic test that nested values are populated. We nest an optional because
2235+
// its easy to use in a test, but the type of the nested value shouldn't
2236+
// matter.
2237+
ExpectLatticeChecksFor(
2238+
R"(
2239+
#include "unchecked_optional_access_test.h"
2240+
2241+
using Foo = $ns::$optional<std::string>;
2242+
2243+
void target($ns::$optional<Foo> foo) {
2244+
if (foo && *foo) {
2245+
foo->value();
2246+
/*[[access]]*/
2247+
}
2248+
}
2249+
)",
2250+
UnorderedElementsAre(Pair("access", "safe")));
2251+
2252+
// Mutation is supported for nested values.
2253+
ExpectLatticeChecksFor(
2254+
R"(
2255+
#include "unchecked_optional_access_test.h"
2256+
2257+
using Foo = $ns::$optional<std::string>;
2258+
2259+
void target($ns::$optional<Foo> foo) {
2260+
if (foo && *foo) {
2261+
foo->reset();
2262+
foo->value();
2263+
/*[[reset]]*/
2264+
}
2265+
}
2266+
)",
2267+
UnorderedElementsAre(Pair("reset", "unsafe: input.cc:9:9")));
2268+
}
2269+
2270+
// Tests that structs can be nested. We use an optional field because its easy
2271+
// to use in a test, but the type of the field shouldn't matter.
2272+
TEST_P(UncheckedOptionalAccessTest, OptionalValueStruct) {
2273+
ExpectLatticeChecksFor(
2274+
R"(
2275+
#include "unchecked_optional_access_test.h"
2276+
2277+
struct Foo {
2278+
$ns::$optional<std::string> opt;
2279+
};
2280+
2281+
void target($ns::$optional<Foo> foo) {
2282+
if (foo && foo->opt) {
2283+
foo->opt.value();
2284+
/*[[access]]*/
2285+
}
2286+
}
2287+
)",
2288+
UnorderedElementsAre(Pair("access", "safe")));
2289+
}
2290+
2291+
TEST_P(UncheckedOptionalAccessTest, OptionalValueInitialization) {
2292+
// FIXME: Fix when to initialize `value`. All unwrapping should be safe in
2293+
// this example, but `value` initialization is done multiple times during the
2294+
// fixpoint iterations and joining the environment won't correctly merge them.
2295+
ExpectLatticeChecksFor(
2296+
R"(
2297+
#include "unchecked_optional_access_test.h"
2298+
2299+
using Foo = $ns::$optional<std::string>;
2300+
2301+
void target($ns::$optional<Foo> foo, bool b) {
2302+
if (!foo.has_value()) return;
2303+
if (b) {
2304+
if (!foo->has_value()) return;
2305+
// We have created `foo.value()`.
2306+
foo->value();
2307+
} else {
2308+
if (!foo->has_value()) return;
2309+
// We have created `foo.value()` again, in a different environment.
2310+
foo->value();
2311+
}
2312+
// Now we merge the two values. UncheckedOptionalAccessModel::merge() will
2313+
// throw away the "value" property.
2314+
foo->value();
2315+
/*[[merge]]*/
2316+
}
2317+
)",
2318+
UnorderedElementsAre(Pair("merge", "unsafe: input.cc:19:7")));
2319+
}
2320+
22342321
// FIXME: Add support for:
22352322
// - constructors (copy, move)
22362323
// - assignment operators (default, copy, move)
22372324
// - invalidation (passing optional by non-const reference/pointer)
2238-
// - nested `optional` values

0 commit comments

Comments
 (0)