Skip to content

[clang][dataflow] Disallow setting properties on RecordValues. #76042

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading