[clang] 7f07600 - [clang][dataflow] Add support for `value_or` in a comparison.
Yitzhak Mandelbaum via cfe-commits
cfe-commits at lists.llvm.org
Thu Mar 31 06:22:04 PDT 2022
Author: Yitzhak Mandelbaum
Date: 2022-03-31T13:21:39Z
New Revision: 7f076004e941fe60ab613a218da31a25b09b0925
URL: https://github.com/llvm/llvm-project/commit/7f076004e941fe60ab613a218da31a25b09b0925
DIFF: https://github.com/llvm/llvm-project/commit/7f076004e941fe60ab613a218da31a25b09b0925.diff
LOG: [clang][dataflow] Add support for `value_or` in a comparison.
This patch adds limited modeling of the `value_or` method. Specifically, when
used in a particular idiom in a comparison to implicitly check whether the
optional holds a value.
Differential Revision: https://reviews.llvm.org/D122231
Added:
Modified:
clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
Removed:
################################################################################
diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
index b775698dafb5d..aaf8289c48674 100644
--- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
@@ -121,6 +121,43 @@ auto isStdSwapCall() {
hasArgument(1, hasOptionalType()));
}
+constexpr llvm::StringLiteral ValueOrCallID = "ValueOrCall";
+
+auto isValueOrStringEmptyCall() {
+ // `opt.value_or("").empty()`
+ return cxxMemberCallExpr(
+ callee(cxxMethodDecl(hasName("empty"))),
+ onImplicitObjectArgument(ignoringImplicit(
+ cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))),
+ callee(cxxMethodDecl(hasName("value_or"),
+ ofClass(optionalClass()))),
+ hasArgument(0, stringLiteral(hasSize(0))))
+ .bind(ValueOrCallID))));
+}
+
+auto isValueOrNotEqX() {
+ auto ComparesToSame = [](ast_matchers::internal::Matcher<Stmt> Arg) {
+ return hasOperands(
+ ignoringImplicit(
+ cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))),
+ callee(cxxMethodDecl(hasName("value_or"),
+ ofClass(optionalClass()))),
+ hasArgument(0, Arg))
+ .bind(ValueOrCallID)),
+ ignoringImplicit(Arg));
+ };
+
+ // `opt.value_or(X) != X`, for X is `nullptr`, `""`, or `0`. Ideally, we'd
+ // support this pattern for any expression, but the AST does not have a
+ // generic expression comparison facility, so we specialize to common cases
+ // seen in practice. FIXME: define a matcher that compares values across
+ // nodes, which would let us generalize this to any `X`.
+ return binaryOperation(hasOperatorName("!="),
+ anyOf(ComparesToSame(cxxNullPtrLiteralExpr()),
+ ComparesToSame(stringLiteral(hasSize(0))),
+ ComparesToSame(integerLiteral(equals(0)))));
+}
+
/// 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) {
@@ -220,6 +257,69 @@ void transferOptionalHasValueCall(const CXXMemberCallExpr *CallExpr,
}
}
+/// `ModelPred` builds a logical formula relating the predicate in
+/// `ValueOrPredExpr` to the optional's `has_value` property.
+void transferValueOrImpl(const clang::Expr *ValueOrPredExpr,
+ const MatchFinder::MatchResult &Result,
+ LatticeTransferState &State,
+ BoolValue &(*ModelPred)(Environment &Env,
+ BoolValue &ExprVal,
+ BoolValue &HasValueVal)) {
+ auto &Env = State.Env;
+
+ const auto *ObjectArgumentExpr =
+ Result.Nodes.getNodeAs<clang::CXXMemberCallExpr>(ValueOrCallID)
+ ->getImplicitObjectArgument();
+
+ auto *OptionalVal = cast_or_null<StructValue>(
+ Env.getValue(*ObjectArgumentExpr, SkipPast::ReferenceThenPointer));
+ if (OptionalVal == nullptr)
+ return;
+ auto *HasValueVal = getHasValue(OptionalVal);
+ assert(HasValueVal != nullptr);
+
+ auto *ExprValue = cast_or_null<BoolValue>(
+ State.Env.getValue(*ValueOrPredExpr, SkipPast::None));
+ if (ExprValue == nullptr) {
+ auto &ExprLoc = State.Env.createStorageLocation(*ValueOrPredExpr);
+ ExprValue = &State.Env.makeAtomicBoolValue();
+ State.Env.setValue(ExprLoc, *ExprValue);
+ State.Env.setStorageLocation(*ValueOrPredExpr, ExprLoc);
+ }
+
+ Env.addToFlowCondition(ModelPred(Env, *ExprValue, *HasValueVal));
+}
+
+void transferValueOrStringEmptyCall(const clang::Expr *ComparisonExpr,
+ const MatchFinder::MatchResult &Result,
+ LatticeTransferState &State) {
+ return transferValueOrImpl(ComparisonExpr, Result, State,
+ [](Environment &Env, BoolValue &ExprVal,
+ BoolValue &HasValueVal) -> BoolValue & {
+ // If the result is *not* empty, then we know the
+ // optional must have been holding a value. If
+ // `ExprVal` is true, though, we don't learn
+ // anything definite about `has_value`, so we
+ // don't add any corresponding implications to
+ // the flow condition.
+ return Env.makeImplication(Env.makeNot(ExprVal),
+ HasValueVal);
+ });
+}
+
+void transferValueOrNotEqX(const Expr *ComparisonExpr,
+ const MatchFinder::MatchResult &Result,
+ LatticeTransferState &State) {
+ transferValueOrImpl(ComparisonExpr, Result, State,
+ [](Environment &Env, BoolValue &ExprVal,
+ BoolValue &HasValueVal) -> BoolValue & {
+ // We know that if `(opt.value_or(X) != X)` then
+ // `opt.hasValue()`, even without knowing further
+ // details about the contents of `opt`.
+ return Env.makeImplication(ExprVal, HasValueVal);
+ });
+}
+
void assignOptionalValue(const Expr &E, LatticeTransferState &State,
BoolValue &HasValueVal) {
if (auto *OptionalLoc =
@@ -439,6 +539,12 @@ auto buildTransferMatchSwitch(
// std::swap
.CaseOf<CallExpr>(isStdSwapCall(), transferStdSwapCall)
+ // opt.value_or("").empty()
+ .CaseOf<Expr>(isValueOrStringEmptyCall(), transferValueOrStringEmptyCall)
+
+ // opt.value_or(X) != X
+ .CaseOf<Expr>(isValueOrNotEqX(), transferValueOrNotEqX)
+
.Build();
}
diff --git a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
index 6ef4b2a9c3336..a501f549851f9 100644
--- a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
@@ -496,6 +496,24 @@ using enable_if_t = typename std::enable_if<B, T>::type;
#endif // ABSL_TYPE_TRAITS_H
)";
+static constexpr char StdStringHeader[] = R"(
+#ifndef STRING_H
+#define STRING_H
+
+namespace std {
+
+struct string {
+ string(const char*);
+ ~string();
+ bool empty();
+};
+bool operator!=(const string &LHS, const char *RHS);
+
+} // namespace std
+
+#endif // STRING_H
+)";
+
static constexpr char StdUtilityHeader[] = R"(
#ifndef UTILITY_H
#define UTILITY_H
@@ -1198,6 +1216,7 @@ class UncheckedOptionalAccessTest
std::vector<std::pair<std::string, std::string>> Headers;
Headers.emplace_back("cstddef.h", CSDtdDefHeader);
Headers.emplace_back("std_initializer_list.h", StdInitializerListHeader);
+ Headers.emplace_back("std_string.h", StdStringHeader);
Headers.emplace_back("std_type_traits.h", StdTypeTraitsHeader);
Headers.emplace_back("std_utility.h", StdUtilityHeader);
Headers.emplace_back("std_optional.h", StdOptionalHeader);
@@ -1209,6 +1228,7 @@ class UncheckedOptionalAccessTest
#include "base_optional.h"
#include "std_initializer_list.h"
#include "std_optional.h"
+ #include "std_string.h"
#include "std_utility.h"
template <typename T>
@@ -1712,6 +1732,102 @@ TEST_P(UncheckedOptionalAccessTest, ValueOr) {
UnorderedElementsAre(Pair("check", "safe")));
}
+TEST_P(UncheckedOptionalAccessTest, ValueOrComparison) {
+ // Pointers.
+ ExpectLatticeChecksFor(
+ R"code(
+ #include "unchecked_optional_access_test.h"
+
+ void target($ns::$optional<int*> opt) {
+ if (opt.value_or(nullptr) != nullptr) {
+ opt.value();
+ /*[[check-ptrs-1]]*/
+ } else {
+ opt.value();
+ /*[[check-ptrs-2]]*/
+ }
+ }
+ )code",
+ UnorderedElementsAre(Pair("check-ptrs-1", "safe"),
+ Pair("check-ptrs-2", "unsafe: input.cc:9:9")));
+
+ // Integers.
+ ExpectLatticeChecksFor(
+ R"code(
+ #include "unchecked_optional_access_test.h"
+
+ void target($ns::$optional<int> opt) {
+ if (opt.value_or(0) != 0) {
+ opt.value();
+ /*[[check-ints-1]]*/
+ } else {
+ opt.value();
+ /*[[check-ints-2]]*/
+ }
+ }
+ )code",
+ UnorderedElementsAre(Pair("check-ints-1", "safe"),
+ Pair("check-ints-2", "unsafe: input.cc:9:9")));
+
+ // Strings.
+ ExpectLatticeChecksFor(
+ R"code(
+ #include "unchecked_optional_access_test.h"
+
+ void target($ns::$optional<std::string> opt) {
+ if (!opt.value_or("").empty()) {
+ opt.value();
+ /*[[check-strings-1]]*/
+ } else {
+ opt.value();
+ /*[[check-strings-2]]*/
+ }
+ }
+ )code",
+ UnorderedElementsAre(Pair("check-strings-1", "safe"),
+ Pair("check-strings-2", "unsafe: input.cc:9:9")));
+
+ ExpectLatticeChecksFor(
+ R"code(
+ #include "unchecked_optional_access_test.h"
+
+ void target($ns::$optional<std::string> opt) {
+ if (opt.value_or("") != "") {
+ opt.value();
+ /*[[check-strings-neq-1]]*/
+ } else {
+ opt.value();
+ /*[[check-strings-neq-2]]*/
+ }
+ }
+ )code",
+ UnorderedElementsAre(
+ Pair("check-strings-neq-1", "safe"),
+ Pair("check-strings-neq-2", "unsafe: input.cc:9:9")));
+
+ // Pointer-to-optional.
+ //
+ // FIXME: make `opt` a parameter directly, once we ensure that all `optional`
+ // values have a `has_value` property.
+ ExpectLatticeChecksFor(
+ R"code(
+ #include "unchecked_optional_access_test.h"
+
+ void target($ns::$optional<int> p) {
+ $ns::$optional<int> *opt = &p;
+ if (opt->value_or(0) != 0) {
+ opt->value();
+ /*[[check-pto-1]]*/
+ } else {
+ opt->value();
+ /*[[check-pto-2]]*/
+ }
+ }
+ )code",
+ UnorderedElementsAre(Pair("check-pto-1", "safe"),
+ Pair("check-pto-2", "unsafe: input.cc:10:9")));
+}
+
TEST_P(UncheckedOptionalAccessTest, Emplace) {
ExpectLatticeChecksFor(R"(
#include "unchecked_optional_access_test.h"
@@ -2038,5 +2154,4 @@ TEST_P(UncheckedOptionalAccessTest, UniquePtrToStructWithOptionalField) {
// - constructors (copy, move)
// - assignment operators (default, copy, move)
// - invalidation (passing optional by non-const reference/pointer)
-// - `value_or(nullptr) != nullptr`, `value_or(0) != 0`, `value_or("").empty()`
// - nested `optional` values
More information about the cfe-commits
mailing list