Skip to content

Commit 2ddd57a

Browse files
committed
[clang][dataflow] Model the behavior of optional and std swap
Differential Revision: https://reviews.llvm.org/D122129 Reviewed-by: ymandel, xazax.hun
1 parent abb5a98 commit 2ddd57a

File tree

2 files changed

+150
-1
lines changed

2 files changed

+150
-1
lines changed

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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ auto isOptionalNulloptAssignment() {
107107
hasArgument(1, hasNulloptType()));
108108
}
109109

110+
auto isStdSwapCall() {
111+
return callExpr(callee(functionDecl(hasName("std::swap"))),
112+
argumentCountIs(2), hasArgument(0, hasOptionalType()),
113+
hasArgument(1, hasOptionalType()));
114+
}
115+
110116
/// Creates a symbolic value for an `optional` value using `HasValueVal` as the
111117
/// symbolic value of its "has_value" property.
112118
StructValue &createOptionalValue(Environment &Env, BoolValue &HasValueVal) {
@@ -283,6 +289,50 @@ void transferNulloptAssignment(const CXXOperatorCallExpr *E,
283289
transferAssignment(E, State.Env.getBoolLiteralValue(false), State);
284290
}
285291

292+
void transferSwap(const StorageLocation &OptionalLoc1,
293+
const StorageLocation &OptionalLoc2,
294+
LatticeTransferState &State) {
295+
auto *OptionalVal1 = State.Env.getValue(OptionalLoc1);
296+
assert(OptionalVal1 != nullptr);
297+
298+
auto *OptionalVal2 = State.Env.getValue(OptionalLoc2);
299+
assert(OptionalVal2 != nullptr);
300+
301+
State.Env.setValue(OptionalLoc1, *OptionalVal2);
302+
State.Env.setValue(OptionalLoc2, *OptionalVal1);
303+
}
304+
305+
void transferSwapCall(const CXXMemberCallExpr *E,
306+
const MatchFinder::MatchResult &,
307+
LatticeTransferState &State) {
308+
assert(E->getNumArgs() == 1);
309+
310+
auto *OptionalLoc1 = State.Env.getStorageLocation(
311+
*E->getImplicitObjectArgument(), SkipPast::ReferenceThenPointer);
312+
assert(OptionalLoc1 != nullptr);
313+
314+
auto *OptionalLoc2 =
315+
State.Env.getStorageLocation(*E->getArg(0), SkipPast::Reference);
316+
assert(OptionalLoc2 != nullptr);
317+
318+
transferSwap(*OptionalLoc1, *OptionalLoc2, State);
319+
}
320+
321+
void transferStdSwapCall(const CallExpr *E, const MatchFinder::MatchResult &,
322+
LatticeTransferState &State) {
323+
assert(E->getNumArgs() == 2);
324+
325+
auto *OptionalLoc1 =
326+
State.Env.getStorageLocation(*E->getArg(0), SkipPast::Reference);
327+
assert(OptionalLoc1 != nullptr);
328+
329+
auto *OptionalLoc2 =
330+
State.Env.getStorageLocation(*E->getArg(1), SkipPast::Reference);
331+
assert(OptionalLoc2 != nullptr);
332+
333+
transferSwap(*OptionalLoc1, *OptionalLoc2, State);
334+
}
335+
286336
auto buildTransferMatchSwitch() {
287337
// FIXME: Evaluate the efficiency of matchers. If using matchers results in a
288338
// lot of duplicated work (e.g. string comparisons), consider providing APIs
@@ -361,6 +411,13 @@ auto buildTransferMatchSwitch() {
361411
State.Env.getBoolLiteralValue(false));
362412
})
363413

414+
// optional::swap
415+
.CaseOf<CXXMemberCallExpr>(isOptionalMemberCallWithName("swap"),
416+
transferSwapCall)
417+
418+
// std::swap
419+
.CaseOf<CallExpr>(isStdSwapCall(), transferStdSwapCall)
420+
364421
.Build();
365422
}
366423

clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,9 @@ namespace std {
507507
template <typename T>
508508
constexpr remove_reference_t<T>&& move(T&& x);
509509
510+
template <typename T>
511+
void swap(T& a, T& b) noexcept;
512+
510513
} // namespace std
511514
512515
#endif // UTILITY_H
@@ -718,6 +721,8 @@ class optional : private __optional_storage_base<_Tp> {
718721
719722
constexpr explicit operator bool() const noexcept;
720723
using __base::has_value;
724+
725+
constexpr void swap(optional& __opt) noexcept;
721726
};
722727
723728
template <typename T>
@@ -938,6 +943,8 @@ class optional {
938943
939944
constexpr explicit operator bool() const noexcept;
940945
constexpr bool has_value() const noexcept;
946+
947+
void swap(optional& rhs) noexcept;
941948
};
942949
943950
template <typename T>
@@ -1129,6 +1136,8 @@ class Optional {
11291136
11301137
constexpr explicit operator bool() const noexcept;
11311138
constexpr bool has_value() const noexcept;
1139+
1140+
void swap(Optional& other);
11321141
};
11331142
11341143
template <typename T>
@@ -1911,10 +1920,93 @@ TEST_P(UncheckedOptionalAccessTest, NulloptAssignment) {
19111920
UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:7")));
19121921
}
19131922

1923+
TEST_P(UncheckedOptionalAccessTest, OptionalSwap) {
1924+
ExpectLatticeChecksFor(
1925+
R"(
1926+
#include "unchecked_optional_access_test.h"
1927+
1928+
void target() {
1929+
$ns::$optional<int> opt1 = $ns::nullopt;
1930+
$ns::$optional<int> opt2 = 3;
1931+
1932+
opt1.swap(opt2);
1933+
1934+
opt1.value();
1935+
/*[[check-1]]*/
1936+
1937+
opt2.value();
1938+
/*[[check-2]]*/
1939+
}
1940+
)",
1941+
UnorderedElementsAre(Pair("check-1", "safe"),
1942+
Pair("check-2", "unsafe: input.cc:13:7")));
1943+
1944+
ExpectLatticeChecksFor(
1945+
R"(
1946+
#include "unchecked_optional_access_test.h"
1947+
1948+
void target() {
1949+
$ns::$optional<int> opt1 = $ns::nullopt;
1950+
$ns::$optional<int> opt2 = 3;
1951+
1952+
opt2.swap(opt1);
1953+
1954+
opt1.value();
1955+
/*[[check-3]]*/
1956+
1957+
opt2.value();
1958+
/*[[check-4]]*/
1959+
}
1960+
)",
1961+
UnorderedElementsAre(Pair("check-3", "safe"),
1962+
Pair("check-4", "unsafe: input.cc:13:7")));
1963+
}
1964+
1965+
TEST_P(UncheckedOptionalAccessTest, StdSwap) {
1966+
ExpectLatticeChecksFor(
1967+
R"(
1968+
#include "unchecked_optional_access_test.h"
1969+
1970+
void target() {
1971+
$ns::$optional<int> opt1 = $ns::nullopt;
1972+
$ns::$optional<int> opt2 = 3;
1973+
1974+
std::swap(opt1, opt2);
1975+
1976+
opt1.value();
1977+
/*[[check-1]]*/
1978+
1979+
opt2.value();
1980+
/*[[check-2]]*/
1981+
}
1982+
)",
1983+
UnorderedElementsAre(Pair("check-1", "safe"),
1984+
Pair("check-2", "unsafe: input.cc:13:7")));
1985+
1986+
ExpectLatticeChecksFor(
1987+
R"(
1988+
#include "unchecked_optional_access_test.h"
1989+
1990+
void target() {
1991+
$ns::$optional<int> opt1 = $ns::nullopt;
1992+
$ns::$optional<int> opt2 = 3;
1993+
1994+
std::swap(opt2, opt1);
1995+
1996+
opt1.value();
1997+
/*[[check-3]]*/
1998+
1999+
opt2.value();
2000+
/*[[check-4]]*/
2001+
}
2002+
)",
2003+
UnorderedElementsAre(Pair("check-3", "safe"),
2004+
Pair("check-4", "unsafe: input.cc:13:7")));
2005+
}
2006+
19142007
// FIXME: Add support for:
19152008
// - constructors (copy, move)
19162009
// - assignment operators (default, copy, move)
1917-
// - swap
19182010
// - invalidation (passing optional by non-const reference/pointer)
19192011
// - `value_or(nullptr) != nullptr`, `value_or(0) != 0`, `value_or("").empty()`
19202012
// - nested `optional` values

0 commit comments

Comments
 (0)