[clang][dataflow] Add support for correlated branches to optional model

Add support for correlated branches to the std::optional dataflow model.

Differential Revision: https://reviews.llvm.org/D125931

Reviewed-by: ymandel, xazax.hun
This commit is contained in:
Stanislav Gatev 2022-05-18 21:57:40 +00:00
parent 8c7b64b5ae
commit 8fcdd62585
3 changed files with 378 additions and 4 deletions

View File

@ -59,6 +59,14 @@ public:
void transfer(const Stmt *Stmt, SourceLocationsLattice &State,
Environment &Env);
bool compareEquivalent(QualType Type, const Value &Val1,
const Environment &Env1, const Value &Val2,
const Environment &Env2) override;
bool merge(QualType Type, const Value &Val1, const Environment &Env1,
const Value &Val2, const Environment &Env2, Value &MergedVal,
Environment &MergedEnv) override;
private:
MatchSwitch<TransferState<SourceLocationsLattice>> TransferMatchSwitch;
};

View File

@ -169,11 +169,17 @@ auto isCallReturningOptional() {
optionalOrAliasType(), referenceType(pointee(optionalOrAliasType()))))));
}
/// Sets `HasValueVal` as the symbolic value that represents the "has_value"
/// property of the optional value `OptionalVal`.
void setHasValue(Value &OptionalVal, BoolValue &HasValueVal) {
OptionalVal.setProperty("has_value", HasValueVal);
}
/// Creates a symbolic value for an `optional` value using `HasValueVal` as the
/// symbolic value of its "has_value" property.
StructValue &createOptionalValue(Environment &Env, BoolValue &HasValueVal) {
auto OptionalVal = std::make_unique<StructValue>();
OptionalVal->setProperty("has_value", HasValueVal);
setHasValue(*OptionalVal, HasValueVal);
return Env.takeOwnership(std::move(OptionalVal));
}
@ -274,11 +280,28 @@ void initializeOptionalReference(const Expr *OptionalExpr,
if (auto *OptionalVal =
State.Env.getValue(*OptionalExpr, SkipPast::Reference)) {
if (OptionalVal->getProperty("has_value") == nullptr) {
OptionalVal->setProperty("has_value", State.Env.makeAtomicBoolValue());
setHasValue(*OptionalVal, State.Env.makeAtomicBoolValue());
}
}
}
/// Returns true if and only if `OptionalVal` is initialized and known to be
/// empty in `Env.
bool isEmptyOptional(const Value &OptionalVal, const Environment &Env) {
auto *HasValueVal =
cast_or_null<BoolValue>(OptionalVal.getProperty("has_value"));
return HasValueVal != nullptr &&
Env.flowConditionImplies(Env.makeNot(*HasValueVal));
}
/// Returns true if and only if `OptionalVal` is initialized and known to be
/// non-empty in `Env.
bool isNonEmptyOptional(const Value &OptionalVal, const Environment &Env) {
auto *HasValueVal =
cast_or_null<BoolValue>(OptionalVal.getProperty("has_value"));
return HasValueVal != nullptr && Env.flowConditionImplies(*HasValueVal);
}
void transferUnwrapCall(const Expr *UnwrapExpr, const Expr *ObjectExpr,
LatticeTransferState &State) {
if (auto *OptionalVal =
@ -651,5 +674,31 @@ void UncheckedOptionalAccessModel::transfer(const Stmt *S,
TransferMatchSwitch(*S, getASTContext(), State);
}
bool UncheckedOptionalAccessModel::compareEquivalent(QualType Type,
const Value &Val1,
const Environment &Env1,
const Value &Val2,
const Environment &Env2) {
return isNonEmptyOptional(Val1, Env1) == isNonEmptyOptional(Val2, Env2);
}
bool UncheckedOptionalAccessModel::merge(QualType Type, const Value &Val1,
const Environment &Env1,
const Value &Val2,
const Environment &Env2,
Value &MergedVal,
Environment &MergedEnv) {
if (!IsOptionalType(Type))
return true;
auto &HasValueVal = MergedEnv.makeAtomicBoolValue();
if (isNonEmptyOptional(Val1, Env1) && isNonEmptyOptional(Val2, Env2))
MergedEnv.addToFlowCondition(HasValueVal);
else if (isEmptyOptional(Val1, Env1) && isEmptyOptional(Val2, Env2))
MergedEnv.addToFlowCondition(MergedEnv.makeNot(HasValueVal));
setHasValue(MergedVal, HasValueVal);
return true;
}
} // namespace dataflow
} // namespace clang

View File

@ -1861,7 +1861,26 @@ TEST_P(UncheckedOptionalAccessTest, Emplace) {
)",
UnorderedElementsAre(Pair("check", "safe")));
// FIXME: Add tests that call `emplace` in conditional branches.
// FIXME: Add tests that call `emplace` in conditional branches:
// ExpectLatticeChecksFor(
// R"(
// #include "unchecked_optional_access_test.h"
//
// void target($ns::$optional<int> opt, bool b) {
// if (b) {
// opt.emplace(0);
// }
// if (b) {
// opt.value();
// /*[[check-1]]*/
// } else {
// opt.value();
// /*[[check-2]]*/
// }
// }
// )",
// UnorderedElementsAre(Pair("check-1", "safe"),
// Pair("check-2", "unsafe: input.cc:12:9")));
}
TEST_P(UncheckedOptionalAccessTest, Reset) {
@ -1892,7 +1911,27 @@ TEST_P(UncheckedOptionalAccessTest, Reset) {
)",
UnorderedElementsAre(Pair("check", "unsafe: input.cc:7:9")));
// FIXME: Add tests that call `reset` in conditional branches.
// FIXME: Add tests that call `reset` in conditional branches:
// ExpectLatticeChecksFor(
// R"(
// #include "unchecked_optional_access_test.h"
//
// void target(bool b) {
// $ns::$optional<int> opt = $ns::make_optional(0);
// if (b) {
// opt.reset();
// }
// if (b) {
// opt.value();
// /*[[check-1]]*/
// } else {
// opt.value();
// /*[[check-2]]*/
// }
// }
// )",
// UnorderedElementsAre(Pair("check-1", "unsafe: input.cc:10:9"),
// Pair("check-2", "safe")));
}
TEST_P(UncheckedOptionalAccessTest, ValueAssignment) {
@ -2347,6 +2386,284 @@ TEST_P(UncheckedOptionalAccessTest, AssignThroughLvalueReferencePtr) {
UnorderedElementsAre(Pair("check", "unsafe: input.cc:12:7")));
}
TEST_P(UncheckedOptionalAccessTest, CorrelatedBranches) {
ExpectLatticeChecksFor(R"code(
#include "unchecked_optional_access_test.h"
void target(bool b, $ns::$optional<int> opt) {
if (b || opt.has_value()) {
if (!b) {
opt.value();
/*[[check-1]]*/
}
}
}
)code",
UnorderedElementsAre(Pair("check-1", "safe")));
ExpectLatticeChecksFor(R"code(
#include "unchecked_optional_access_test.h"
void target(bool b, $ns::$optional<int> opt) {
if (b && !opt.has_value()) return;
if (b) {
opt.value();
/*[[check-2]]*/
}
}
)code",
UnorderedElementsAre(Pair("check-2", "safe")));
ExpectLatticeChecksFor(
R"code(
#include "unchecked_optional_access_test.h"
void target(bool b, $ns::$optional<int> opt) {
if (opt.has_value()) b = true;
if (b) {
opt.value();
/*[[check-3]]*/
}
}
)code",
UnorderedElementsAre(Pair("check-3", "unsafe: input.cc:7:9")));
ExpectLatticeChecksFor(R"code(
#include "unchecked_optional_access_test.h"
void target(bool b, $ns::$optional<int> opt) {
if (b) return;
if (opt.has_value()) b = true;
if (b) {
opt.value();
/*[[check-4]]*/
}
}
)code",
UnorderedElementsAre(Pair("check-4", "safe")));
ExpectLatticeChecksFor(R"(
#include "unchecked_optional_access_test.h"
void target(bool b, $ns::$optional<int> opt) {
if (opt.has_value() == b) {
if (b) {
opt.value();
/*[[check-5]]*/
}
}
}
)",
UnorderedElementsAre(Pair("check-5", "safe")));
ExpectLatticeChecksFor(R"(
#include "unchecked_optional_access_test.h"
void target(bool b, $ns::$optional<int> opt) {
if (opt.has_value() != b) {
if (!b) {
opt.value();
/*[[check-6]]*/
}
}
}
)",
UnorderedElementsAre(Pair("check-6", "safe")));
ExpectLatticeChecksFor(R"(
#include "unchecked_optional_access_test.h"
void target(bool b) {
$ns::$optional<int> opt1 = $ns::nullopt;
$ns::$optional<int> opt2;
if (b) {
opt2 = $ns::nullopt;
} else {
opt2 = $ns::nullopt;
}
if (opt2.has_value()) {
opt1.value();
/*[[check]]*/
}
}
)",
UnorderedElementsAre(Pair("check", "safe")));
// FIXME: Add support for operator==.
// ExpectLatticeChecksFor(R"(
// #include "unchecked_optional_access_test.h"
//
// void target($ns::$optional<int> opt1, $ns::$optional<int> opt2) {
// if (opt1 == opt2) {
// if (opt1.has_value()) {
// opt2.value();
// /*[[check-7]]*/
// }
// }
// }
// )",
// UnorderedElementsAre(Pair("check-7", "safe")));
}
TEST_P(UncheckedOptionalAccessTest, JoinDistinctValues) {
ExpectLatticeChecksFor(
R"code(
#include "unchecked_optional_access_test.h"
void target(bool b) {
$ns::$optional<int> opt;
if (b) {
opt = Make<$ns::$optional<int>>();
} else {
opt = Make<$ns::$optional<int>>();
}
if (opt.has_value()) {
opt.value();
/*[[check-1]]*/
} else {
opt.value();
/*[[check-2]]*/
}
}
)code",
UnorderedElementsAre(Pair("check-1", "safe"),
Pair("check-2", "unsafe: input.cc:15:9")));
ExpectLatticeChecksFor(R"code(
#include "unchecked_optional_access_test.h"
void target(bool b) {
$ns::$optional<int> opt;
if (b) {
opt = Make<$ns::$optional<int>>();
if (!opt.has_value()) return;
} else {
opt = Make<$ns::$optional<int>>();
if (!opt.has_value()) return;
}
opt.value();
/*[[check-3]]*/
}
)code",
UnorderedElementsAre(Pair("check-3", "safe")));
ExpectLatticeChecksFor(
R"code(
#include "unchecked_optional_access_test.h"
void target(bool b) {
$ns::$optional<int> opt;
if (b) {
opt = Make<$ns::$optional<int>>();
if (!opt.has_value()) return;
} else {
opt = Make<$ns::$optional<int>>();
}
opt.value();
/*[[check-4]]*/
}
)code",
UnorderedElementsAre(Pair("check-4", "unsafe: input.cc:12:7")));
ExpectLatticeChecksFor(
R"code(
#include "unchecked_optional_access_test.h"
void target(bool b) {
$ns::$optional<int> opt;
if (b) {
opt = 1;
} else {
opt = 2;
}
opt.value();
/*[[check-5]]*/
}
)code",
UnorderedElementsAre(Pair("check-5", "safe")));
ExpectLatticeChecksFor(
R"code(
#include "unchecked_optional_access_test.h"
void target(bool b) {
$ns::$optional<int> opt;
if (b) {
opt = 1;
} else {
opt = Make<$ns::$optional<int>>();
}
opt.value();
/*[[check-6]]*/
}
)code",
UnorderedElementsAre(Pair("check-6", "unsafe: input.cc:11:7")));
}
TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoop) {
ExpectLatticeChecksFor(R"(
#include "unchecked_optional_access_test.h"
void target() {
$ns::$optional<int> opt = 3;
while (Make<bool>()) {
opt.value();
/*[[check-1]]*/
}
}
)",
UnorderedElementsAre(Pair("check-1", "safe")));
ExpectLatticeChecksFor(R"(
#include "unchecked_optional_access_test.h"
void target() {
$ns::$optional<int> opt = 3;
while (Make<bool>()) {
opt.value();
/*[[check-2]]*/
opt = Make<$ns::$optional<int>>();
if (!opt.has_value()) return;
}
}
)",
UnorderedElementsAre(Pair("check-2", "safe")));
ExpectLatticeChecksFor(
R"(
#include "unchecked_optional_access_test.h"
void target() {
$ns::$optional<int> opt = 3;
while (Make<bool>()) {
opt.value();
/*[[check-3]]*/
opt = Make<$ns::$optional<int>>();
}
}
)",
UnorderedElementsAre(Pair("check-3", "unsafe: input.cc:7:9")));
ExpectLatticeChecksFor(
R"(
#include "unchecked_optional_access_test.h"
void target() {
$ns::$optional<int> opt = 3;
while (Make<bool>()) {
opt.value();
/*[[check-4]]*/
opt = Make<$ns::$optional<int>>();
if (!opt.has_value()) continue;
}
}
)",
UnorderedElementsAre(Pair("check-4", "unsafe: input.cc:7:9")));
}
// FIXME: Add support for:
// - constructors (copy, move)
// - assignment operators (default, copy, move)