Skip to content

Commit d4fb829

Browse files
committed
[clang][dataflow] Relax validity assumptions in UncheckedOptionalAccessModel.
Currently, the interpretation of `swap` calls in the optional model assumes the optional arguments are modeled (and therefore have valid storage locations and values). This assumption is incorrect, for example, in the case of unmodeled optional fields (which can be missing either value or location). This patch relaxes these assumptions, to return rather than assert when either argument is not modeled. Differential Revision: https://reviews.llvm.org/D142710
1 parent b0c1a45 commit d4fb829

File tree

2 files changed

+168
-30
lines changed

2 files changed

+168
-30
lines changed

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

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -521,48 +521,54 @@ void transferNulloptAssignment(const CXXOperatorCallExpr *E,
521521
transferAssignment(E, State.Env.getBoolLiteralValue(false), State);
522522
}
523523

524-
void transferSwap(const StorageLocation &OptionalLoc1,
525-
const StorageLocation &OptionalLoc2,
526-
LatticeTransferState &State) {
527-
auto *OptionalVal1 = State.Env.getValue(OptionalLoc1);
528-
assert(OptionalVal1 != nullptr);
524+
void transferSwap(const Expr &E1, SkipPast E1Skip, const Expr &E2,
525+
Environment &Env) {
526+
// We account for cases where one or both of the optionals are not modeled,
527+
// either lacking associated storage locations, or lacking values associated
528+
// to such storage locations.
529+
auto *Loc1 = Env.getStorageLocation(E1, E1Skip);
530+
auto *Loc2 = Env.getStorageLocation(E2, SkipPast::Reference);
531+
532+
if (Loc1 == nullptr) {
533+
if (Loc2 != nullptr)
534+
Env.setValue(*Loc2, createOptionalValue(Env, Env.makeAtomicBoolValue()));
535+
return;
536+
}
537+
if (Loc2 == nullptr) {
538+
Env.setValue(*Loc1, createOptionalValue(Env, Env.makeAtomicBoolValue()));
539+
return;
540+
}
529541

530-
auto *OptionalVal2 = State.Env.getValue(OptionalLoc2);
531-
assert(OptionalVal2 != nullptr);
542+
// Both expressions have locations, though they may not have corresponding
543+
// values. In that case, we create a fresh value at this point. Note that if
544+
// two branches both do this, they will not share the value, but it at least
545+
// allows for local reasoning about the value. To avoid the above, we would
546+
// need *lazy* value allocation.
547+
// FIXME: allocate values lazily, instead of just creating a fresh value.
548+
auto *Val1 = Env.getValue(*Loc1);
549+
if (Val1 == nullptr)
550+
Val1 = &createOptionalValue(Env, Env.makeAtomicBoolValue());
532551

533-
State.Env.setValue(OptionalLoc1, *OptionalVal2);
534-
State.Env.setValue(OptionalLoc2, *OptionalVal1);
552+
auto *Val2 = Env.getValue(*Loc2);
553+
if (Val2 == nullptr)
554+
Val2 = &createOptionalValue(Env, Env.makeAtomicBoolValue());
555+
556+
Env.setValue(*Loc1, *Val2);
557+
Env.setValue(*Loc2, *Val1);
535558
}
536559

537560
void transferSwapCall(const CXXMemberCallExpr *E,
538561
const MatchFinder::MatchResult &,
539562
LatticeTransferState &State) {
540563
assert(E->getNumArgs() == 1);
541-
542-
auto *OptionalLoc1 = State.Env.getStorageLocation(
543-
*E->getImplicitObjectArgument(), SkipPast::ReferenceThenPointer);
544-
assert(OptionalLoc1 != nullptr);
545-
546-
auto *OptionalLoc2 =
547-
State.Env.getStorageLocation(*E->getArg(0), SkipPast::Reference);
548-
assert(OptionalLoc2 != nullptr);
549-
550-
transferSwap(*OptionalLoc1, *OptionalLoc2, State);
564+
transferSwap(*E->getImplicitObjectArgument(), SkipPast::ReferenceThenPointer,
565+
*E->getArg(0), State.Env);
551566
}
552567

553568
void transferStdSwapCall(const CallExpr *E, const MatchFinder::MatchResult &,
554569
LatticeTransferState &State) {
555570
assert(E->getNumArgs() == 2);
556-
557-
auto *OptionalLoc1 =
558-
State.Env.getStorageLocation(*E->getArg(0), SkipPast::Reference);
559-
assert(OptionalLoc1 != nullptr);
560-
561-
auto *OptionalLoc2 =
562-
State.Env.getStorageLocation(*E->getArg(1), SkipPast::Reference);
563-
assert(OptionalLoc2 != nullptr);
564-
565-
transferSwap(*OptionalLoc1, *OptionalLoc2, State);
571+
transferSwap(*E->getArg(0), SkipPast::Reference, *E->getArg(1), State.Env);
566572
}
567573

568574
BoolValue &evaluateEquality(Environment &Env, BoolValue &EqVal, BoolValue &LHS,

clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
#include "TestingSupport.h"
1212
#include "clang/AST/ASTContext.h"
1313
#include "clang/ASTMatchers/ASTMatchers.h"
14-
#include "clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h"
1514
#include "clang/Basic/SourceLocation.h"
1615
#include "clang/Tooling/Tooling.h"
1716
#include "llvm/ADT/DenseSet.h"
@@ -2124,6 +2123,139 @@ TEST_P(UncheckedOptionalAccessTest, StdSwap) {
21242123
)");
21252124
}
21262125

2126+
TEST_P(UncheckedOptionalAccessTest, SwapUnmodeledLocLeft) {
2127+
ExpectDiagnosticsFor(
2128+
R"(
2129+
#include "unchecked_optional_access_test.h"
2130+
2131+
struct L { $ns::$optional<int> hd; L* tl; };
2132+
2133+
void target() {
2134+
$ns::$optional<int> foo = 3;
2135+
L bar;
2136+
2137+
// Any `tl` beyond the first is not modeled.
2138+
bar.tl->tl->hd.swap(foo);
2139+
2140+
bar.tl->tl->hd.value(); // [[unsafe]]
2141+
foo.value(); // [[unsafe]]
2142+
}
2143+
)");
2144+
}
2145+
2146+
TEST_P(UncheckedOptionalAccessTest, SwapUnmodeledLocRight) {
2147+
ExpectDiagnosticsFor(
2148+
R"(
2149+
#include "unchecked_optional_access_test.h"
2150+
2151+
struct L { $ns::$optional<int> hd; L* tl; };
2152+
2153+
void target() {
2154+
$ns::$optional<int> foo = 3;
2155+
L bar;
2156+
2157+
// Any `tl` beyond the first is not modeled.
2158+
foo.swap(bar.tl->tl->hd);
2159+
2160+
bar.tl->tl->hd.value(); // [[unsafe]]
2161+
foo.value(); // [[unsafe]]
2162+
}
2163+
)");
2164+
}
2165+
2166+
TEST_P(UncheckedOptionalAccessTest, SwapUnmodeledValueLeftSet) {
2167+
ExpectDiagnosticsFor(
2168+
R"(
2169+
#include "unchecked_optional_access_test.h"
2170+
2171+
struct S { int x; };
2172+
struct A { $ns::$optional<S> late; };
2173+
struct B { A f3; };
2174+
struct C { B f2; };
2175+
struct D { C f1; };
2176+
2177+
void target() {
2178+
$ns::$optional<S> foo = S{3};
2179+
D bar;
2180+
2181+
bar.f1.f2.f3.late.swap(foo);
2182+
2183+
bar.f1.f2.f3.late.value();
2184+
foo.value(); // [[unsafe]]
2185+
}
2186+
)");
2187+
}
2188+
2189+
TEST_P(UncheckedOptionalAccessTest, SwapUnmodeledValueLeftUnset) {
2190+
ExpectDiagnosticsFor(
2191+
R"(
2192+
#include "unchecked_optional_access_test.h"
2193+
2194+
struct S { int x; };
2195+
struct A { $ns::$optional<S> late; };
2196+
struct B { A f3; };
2197+
struct C { B f2; };
2198+
struct D { C f1; };
2199+
2200+
void target() {
2201+
$ns::$optional<S> foo;
2202+
D bar;
2203+
2204+
bar.f1.f2.f3.late.swap(foo);
2205+
2206+
bar.f1.f2.f3.late.value(); // [[unsafe]]
2207+
foo.value(); // [[unsafe]]
2208+
}
2209+
)");
2210+
}
2211+
2212+
// fixme: use recursion instead of depth.
2213+
TEST_P(UncheckedOptionalAccessTest, SwapUnmodeledValueRightSet) {
2214+
ExpectDiagnosticsFor(
2215+
R"(
2216+
#include "unchecked_optional_access_test.h"
2217+
2218+
struct S { int x; };
2219+
struct A { $ns::$optional<S> late; };
2220+
struct B { A f3; };
2221+
struct C { B f2; };
2222+
struct D { C f1; };
2223+
2224+
void target() {
2225+
$ns::$optional<S> foo = S{3};
2226+
D bar;
2227+
2228+
foo.swap(bar.f1.f2.f3.late);
2229+
2230+
bar.f1.f2.f3.late.value();
2231+
foo.value(); // [[unsafe]]
2232+
}
2233+
)");
2234+
}
2235+
2236+
TEST_P(UncheckedOptionalAccessTest, SwapUnmodeledValueRightUnset) {
2237+
ExpectDiagnosticsFor(
2238+
R"(
2239+
#include "unchecked_optional_access_test.h"
2240+
2241+
struct S { int x; };
2242+
struct A { $ns::$optional<S> late; };
2243+
struct B { A f3; };
2244+
struct C { B f2; };
2245+
struct D { C f1; };
2246+
2247+
void target() {
2248+
$ns::$optional<S> foo;
2249+
D bar;
2250+
2251+
foo.swap(bar.f1.f2.f3.late);
2252+
2253+
bar.f1.f2.f3.late.value(); // [[unsafe]]
2254+
foo.value(); // [[unsafe]]
2255+
}
2256+
)");
2257+
}
2258+
21272259
TEST_P(UncheckedOptionalAccessTest, UniquePtrToOptional) {
21282260
// We suppress diagnostics for optionals in smart pointers (other than
21292261
// `optional` itself).

0 commit comments

Comments
 (0)