Skip to content

Commit

Permalink
[clang][dataflow] Disallow setting properties on RecordValues. (#76042
Browse files Browse the repository at this point in the history
)

Instead, synthetic fields should now be used for the same purpose. These
have a
number of advantages, as described in
#73860, and longer-term, we
want to
eliminate `RecordValue` entirely.

As `RecordValue`s cannot have properties any more, I have replaced the
`OptionalIntAnalysis` with an equivalent analysis that tracks nullness
of
pointers (instead of whether an optional has a value). This serves the
same
purpose, namely to check whether the framework applies a custom
`merge()`
operation to widen properties.
  • Loading branch information
martinboehme committed Dec 21, 2023
1 parent da4bd5b commit 469374e
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 195 deletions.
11 changes: 0 additions & 11 deletions clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
Original file line number Diff line number Diff line change
Expand Up @@ -727,20 +727,9 @@ RecordStorageLocation *getBaseObjectLocation(const MemberExpr &ME,
std::vector<FieldDecl *> getFieldsForInitListExpr(const RecordDecl *RD);

/// Associates a new `RecordValue` with `Loc` and returns the new value.
/// It is not defined whether the field values remain the same or not.
///
/// This function is primarily intended for use by checks that set custom
/// properties on `RecordValue`s to model the state of these values. Such checks
/// should avoid modifying the properties of an existing `RecordValue` because
/// these changes would be visible to other `Environment`s that share the same
/// `RecordValue`. Instead, call `refreshRecordValue()`, then set the properties
/// on the new `RecordValue` that it returns. Typical usage:
///
/// refreshRecordValue(Loc, Env).setProperty("my_prop", MyPropValue);
RecordValue &refreshRecordValue(RecordStorageLocation &Loc, Environment &Env);

/// Associates a new `RecordValue` with `Expr` and returns the new value.
/// See also documentation for the overload above.
RecordValue &refreshRecordValue(const Expr &Expr, Environment &Env);

} // namespace dataflow
Expand Down
12 changes: 2 additions & 10 deletions clang/include/clang/Analysis/FlowSensitive/RecordOps.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,13 @@ namespace dataflow {
/// Copies a record (struct, class, or union) from `Src` to `Dst`.
///
/// This performs a deep copy, i.e. it copies every field (including synthetic
/// fields) and recurses on fields of record type. It also copies properties
/// from the `RecordValue` associated with `Src` to the `RecordValue` associated
/// with `Dst` (if these `RecordValue`s exist).
/// fields) and recurses on fields of record type.
///
/// If there is a `RecordValue` associated with `Dst` in the environment, this
/// function creates a new `RecordValue` and associates it with `Dst`; clients
/// need to be aware of this and must not assume that the `RecordValue`
/// associated with `Dst` remains the same after the call.
///
/// We create a new `RecordValue` rather than modifying properties on the old
/// `RecordValue` because the old `RecordValue` may be shared with other
/// `Environment`s, and we don't want changes to properties to be visible there.
///
/// Requirements:
///
/// `Src` and `Dst` must have the same canonical unqualified type.
Expand All @@ -49,9 +43,7 @@ void copyRecord(RecordStorageLocation &Src, RecordStorageLocation &Dst,
///
/// This performs a deep comparison, i.e. it compares every field (including
/// synthetic fields) and recurses on fields of record type. Fields of reference
/// type compare equal if they refer to the same storage location. If
/// `RecordValue`s are associated with `Loc1` and Loc2`, it also compares the
/// properties on those `RecordValue`s.
/// type compare equal if they refer to the same storage location.
///
/// Note on how to interpret the result:
/// - If this returns true, the records are guaranteed to be equal at runtime.
Expand Down
40 changes: 17 additions & 23 deletions clang/include/clang/Analysis/FlowSensitive/Value.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ class Value {

/// Assigns `Val` as the value of the synthetic property with the given
/// `Name`.
///
/// Properties may not be set on `RecordValue`s; use synthetic fields instead
/// (for details, see documentation for `RecordStorageLocation`).
void setProperty(llvm::StringRef Name, Value &Val) {
assert(getKind() != Kind::Record);
Properties.insert_or_assign(Name, &Val);
}

Expand Down Expand Up @@ -184,33 +188,23 @@ class PointerValue final : public Value {
/// In C++, prvalues of class type serve only a limited purpose: They can only
/// be used to initialize a result object. It is not possible to access member
/// variables or call member functions on a prvalue of class type.
/// Correspondingly, `RecordValue` also serves only two limited purposes:
/// - It conveys a prvalue of class type from the place where the object is
/// constructed to the result object that it initializes.
/// Correspondingly, `RecordValue` also serves only a limited purpose: It
/// conveys a prvalue of class type from the place where the object is
/// constructed to the result object that it initializes.
///
/// When creating a prvalue of class type, we already need a storage location
/// for `this`, even though prvalues are otherwise not associated with storage
/// locations. `RecordValue` is therefore essentially a wrapper for a storage
/// location, which is then used to set the storage location for the result
/// object when we process the AST node for that result object.
/// When creating a prvalue of class type, we already need a storage location
/// for `this`, even though prvalues are otherwise not associated with storage
/// locations. `RecordValue` is therefore essentially a wrapper for a storage
/// location, which is then used to set the storage location for the result
/// object when we process the AST node for that result object.
///
/// For example:
/// MyStruct S = MyStruct(3);
/// For example:
/// MyStruct S = MyStruct(3);
///
/// In this example, `MyStruct(3) is a prvalue, which is modeled as a
/// `RecordValue` that wraps a `RecordStorageLocation`. This
// `RecordStorageLocation` is then used as the storage location for `S`.
/// In this example, `MyStruct(3) is a prvalue, which is modeled as a
/// `RecordValue` that wraps a `RecordStorageLocation`. This
/// `RecordStorageLocation` is then used as the storage location for `S`.
///
/// - It allows properties to be associated with an object of class type.
/// Note that when doing so, you should avoid mutating the properties of an
/// existing `RecordValue` in place, as these changes would be visible to
/// other `Environment`s that share the same `RecordValue`. Instead, associate
/// a new `RecordValue` with the `RecordStorageLocation` and set the
/// properties on this new `RecordValue`. (See also `refreshRecordValue()` in
/// DataflowEnvironment.h, which makes this easy.)
/// Note also that this implies that it is common for the same
/// `RecordStorageLocation` to be associated with different `RecordValue`s
/// in different environments.
/// Over time, we may eliminate `RecordValue` entirely. See also the discussion
/// here: https://reviews.llvm.org/D155204#inline-1503204
class RecordValue final : public Value {
Expand Down
33 changes: 1 addition & 32 deletions clang/lib/Analysis/FlowSensitive/RecordOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,8 @@ void clang::dataflow::copyRecord(RecordStorageLocation &Src,
}
}

RecordValue *SrcVal = Env.get<RecordValue>(Src);
RecordValue *DstVal = Env.get<RecordValue>(Dst);

DstVal = &Env.create<RecordValue>(Dst);
RecordValue *DstVal = &Env.create<RecordValue>(Dst);
Env.setValue(Dst, *DstVal);

if (SrcVal == nullptr)
return;

for (const auto &[Name, Value] : SrcVal->properties()) {
if (Value != nullptr)
DstVal->setProperty(Name, *Value);
}
}

bool clang::dataflow::recordsEqual(const RecordStorageLocation &Loc1,
Expand Down Expand Up @@ -125,25 +114,5 @@ bool clang::dataflow::recordsEqual(const RecordStorageLocation &Loc1,
}
}

llvm::StringMap<Value *> Props1, Props2;

if (RecordValue *Val1 = Env1.get<RecordValue>(Loc1))
for (const auto &[Name, Value] : Val1->properties())
Props1[Name] = Value;
if (RecordValue *Val2 = Env2.get<RecordValue>(Loc2))
for (const auto &[Name, Value] : Val2->properties())
Props2[Name] = Value;

if (Props1.size() != Props2.size())
return false;

for (const auto &[Name, Value] : Props1) {
auto It = Props2.find(Name);
if (It == Props2.end())
return false;
if (Value != It->second)
return false;
}

return true;
}
37 changes: 0 additions & 37 deletions clang/unittests/Analysis/FlowSensitive/RecordOpsTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,6 @@ TEST(RecordOpsTest, CopyRecord) {
auto *S2Val = cast<RecordValue>(Env.getValue(S2));
EXPECT_NE(S1Val, S2Val);

S1Val->setProperty("prop", Env.getBoolLiteralValue(true));

copyRecord(S1, S2, Env);

EXPECT_EQ(getFieldValue(&S1, *OuterIntDecl, Env),
Expand All @@ -104,8 +102,6 @@ TEST(RecordOpsTest, CopyRecord) {
S1Val = cast<RecordValue>(Env.getValue(S1));
S2Val = cast<RecordValue>(Env.getValue(S2));
EXPECT_NE(S1Val, S2Val);

EXPECT_EQ(S2Val->getProperty("prop"), &Env.getBoolLiteralValue(true));
});
}

Expand Down Expand Up @@ -150,9 +146,6 @@ TEST(RecordOpsTest, RecordsEqual) {
Env.setValue(S1.getSyntheticField("synth_int"),
Env.create<IntegerValue>());

cast<RecordValue>(Env.getValue(S1))
->setProperty("prop", Env.getBoolLiteralValue(true));

// Strategy: Create two equal records, then verify each of the various
// ways in which records can differ causes recordsEqual to return false.
// changes we can make to the record.
Expand Down Expand Up @@ -202,36 +195,6 @@ TEST(RecordOpsTest, RecordsEqual) {
EXPECT_FALSE(recordsEqual(S1, S2, Env));
copyRecord(S1, S2, Env);
EXPECT_TRUE(recordsEqual(S1, S2, Env));

// S1 and S2 have the same property with different values.
cast<RecordValue>(Env.getValue(S2))
->setProperty("prop", Env.getBoolLiteralValue(false));
EXPECT_FALSE(recordsEqual(S1, S2, Env));
copyRecord(S1, S2, Env);
EXPECT_TRUE(recordsEqual(S1, S2, Env));

// S1 has a property that S2 doesn't have.
cast<RecordValue>(Env.getValue(S1))
->setProperty("other_prop", Env.getBoolLiteralValue(false));
EXPECT_FALSE(recordsEqual(S1, S2, Env));
// We modified S1 this time, so need to copy back the other way.
copyRecord(S2, S1, Env);
EXPECT_TRUE(recordsEqual(S1, S2, Env));

// S2 has a property that S1 doesn't have.
cast<RecordValue>(Env.getValue(S2))
->setProperty("other_prop", Env.getBoolLiteralValue(false));
EXPECT_FALSE(recordsEqual(S1, S2, Env));
copyRecord(S1, S2, Env);
EXPECT_TRUE(recordsEqual(S1, S2, Env));

// S1 and S2 have the same number of properties, but with different
// names.
cast<RecordValue>(Env.getValue(S1))
->setProperty("prop1", Env.getBoolLiteralValue(false));
cast<RecordValue>(Env.getValue(S2))
->setProperty("prop2", Env.getBoolLiteralValue(false));
EXPECT_FALSE(recordsEqual(S1, S2, Env));
});
}

Expand Down

0 comments on commit 469374e

Please sign in to comment.