[clang] f2123af - [clang][dataflow] Perform deep copies in copy and move operations.
Martin Braenne via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 26 06:53:06 PDT 2023
Author: Martin Braenne
Date: 2023-06-26T13:52:56Z
New Revision: f2123af1e7d7555af92b1bcff91bd5d0679a9b55
URL: https://github.com/llvm/llvm-project/commit/f2123af1e7d7555af92b1bcff91bd5d0679a9b55
DIFF: https://github.com/llvm/llvm-project/commit/f2123af1e7d7555af92b1bcff91bd5d0679a9b55.diff
LOG: [clang][dataflow] Perform deep copies in copy and move operations.
This serves two purposes:
- Because, today, we only copy the `StructValue`, modifying the destination of
the copy also modifies the source. This is demonstrated by the new checks
added to `CopyConstructor` and `MoveConstructor`, which fail without the
deep copy.
- It lays the groundwork for eliminating the redundancy between
`AggregateStorageLocation` and `StructValue`, which will happen as part of the
ongoing migration to strict handling of value categories (seeo
https://discourse.llvm.org/t/70086 for details). This will involve turning
`StructValue` into essentially just a wrapper for `AggregateStorageLocation`;
under this scheme, the current "shallow" copy (copying a `StructValue` from
one `AggregateStorageLocation` to another) will no longer be possible.
Because we now perform deep copies, tests need to perform a deep equality
comparison instead of just comparing for equal identity of the `StructValue`s.
The new function `recordsEqual()` provides such a deep equality comparison.
Reviewed By: xazax.hun
Differential Revision: https://reviews.llvm.org/D153006
Added:
clang/include/clang/Analysis/FlowSensitive/RecordOps.h
clang/lib/Analysis/FlowSensitive/RecordOps.cpp
clang/unittests/Analysis/FlowSensitive/RecordOpsTest.cpp
Modified:
clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
clang/include/clang/Analysis/FlowSensitive/StorageLocation.h
clang/include/clang/Analysis/FlowSensitive/Value.h
clang/lib/Analysis/FlowSensitive/CMakeLists.txt
clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
clang/lib/Analysis/FlowSensitive/Transfer.cpp
clang/unittests/Analysis/FlowSensitive/CMakeLists.txt
clang/unittests/Analysis/FlowSensitive/TestingSupport.h
clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
index 1184fa60bd234..15e63c3d91a32 100644
--- a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
+++ b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
@@ -397,6 +397,9 @@ class Environment {
/// Assigns `Val` as the value of `Loc` in the environment.
void setValue(const StorageLocation &Loc, Value &Val);
+ /// Clears any association between `Loc` and a value in the environment.
+ void clearValue(const StorageLocation &Loc);
+
/// Assigns `Val` as the value of the prvalue `E` in the environment.
///
/// If `E` is not yet associated with a storage location, associates it with
diff --git a/clang/include/clang/Analysis/FlowSensitive/RecordOps.h b/clang/include/clang/Analysis/FlowSensitive/RecordOps.h
new file mode 100644
index 0000000000000..f5a0a5a501c11
--- /dev/null
+++ b/clang/include/clang/Analysis/FlowSensitive/RecordOps.h
@@ -0,0 +1,71 @@
+//===-- RecordOps.h ---------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Operations on records (structs, classes, and unions).
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_RECORDOPS_H
+#define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_RECORDOPS_H
+
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+#include "clang/Analysis/FlowSensitive/StorageLocation.h"
+
+namespace clang {
+namespace dataflow {
+
+/// Copies a record (struct, class, or union) from `Src` to `Dst`.
+///
+/// This performs a deep copy, i.e. it copies every field and recurses on
+/// fields of record type. It also copies properties from the `StructValue`
+/// associated with `Dst` to the `StructValue` associated with `Src` (if these
+/// `StructValue`s exist).
+///
+/// If there is a `StructValue` associated with `Dst` in the environment, this
+/// function creates a new `StructValue` and associates it with `Dst`; clients
+/// need to be aware of this and must not assume that the `StructValue`
+/// associated with `Dst` remains the same after the call.
+///
+/// We create a new `StructValue` rather than modifying properties on the old
+/// `StructValue` because the old `StructValue` 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.
+void copyRecord(AggregateStorageLocation &Src, AggregateStorageLocation &Dst,
+ Environment &Env);
+
+/// Returns whether the records `Loc1` and `Loc2` are equal.
+///
+/// Values for `Loc1` are retrieved from `Env1`, and values for `Loc2` are
+/// retrieved from `Env2`. A convenience overload retrieves values for `Loc1`
+/// and `Loc2` from the same environment.
+///
+/// This performs a deep comparison, i.e. it compares every field and recurses
+/// on fields of record type. Fields of reference type compare equal if they
+/// refer to the same storage location. If `StructValue`s are associated with
+/// `Loc1` and `Loc2`, it also compares the properties on those `StructValue`s.
+///
+/// Requirements:
+///
+/// `Src` and `Dst` must have the same canonical unqualified type.
+bool recordsEqual(const AggregateStorageLocation &Loc1, const Environment &Env1,
+ const AggregateStorageLocation &Loc2,
+ const Environment &Env2);
+
+inline bool recordsEqual(const AggregateStorageLocation &Loc1,
+ const AggregateStorageLocation &Loc2,
+ const Environment &Env) {
+ return recordsEqual(Loc1, Env, Loc2, Env);
+}
+
+} // namespace dataflow
+} // namespace clang
+
+#endif // LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_RECORDOPS_H
diff --git a/clang/include/clang/Analysis/FlowSensitive/StorageLocation.h b/clang/include/clang/Analysis/FlowSensitive/StorageLocation.h
index f7ea7eb174c54..58ebb9bf52586 100644
--- a/clang/include/clang/Analysis/FlowSensitive/StorageLocation.h
+++ b/clang/include/clang/Analysis/FlowSensitive/StorageLocation.h
@@ -70,13 +70,12 @@ class ScalarStorageLocation final : public StorageLocation {
/// that all of the members of a given union have the same storage location.
class AggregateStorageLocation final : public StorageLocation {
public:
+ using FieldToLoc = llvm::DenseMap<const ValueDecl *, StorageLocation *>;
+
explicit AggregateStorageLocation(QualType Type)
- : AggregateStorageLocation(
- Type, llvm::DenseMap<const ValueDecl *, StorageLocation *>()) {}
+ : AggregateStorageLocation(Type, FieldToLoc()) {}
- AggregateStorageLocation(
- QualType Type,
- llvm::DenseMap<const ValueDecl *, StorageLocation *> Children)
+ AggregateStorageLocation(QualType Type, FieldToLoc Children)
: StorageLocation(Kind::Aggregate, Type), Children(std::move(Children)) {}
static bool classof(const StorageLocation *Loc) {
@@ -90,8 +89,12 @@ class AggregateStorageLocation final : public StorageLocation {
return *It->second;
}
+ llvm::iterator_range<FieldToLoc::const_iterator> children() const {
+ return {Children.begin(), Children.end()};
+ }
+
private:
- llvm::DenseMap<const ValueDecl *, StorageLocation *> Children;
+ FieldToLoc Children;
};
} // namespace dataflow
diff --git a/clang/include/clang/Analysis/FlowSensitive/Value.h b/clang/include/clang/Analysis/FlowSensitive/Value.h
index fd8d6ee2f1e0d..59d5fa6923aab 100644
--- a/clang/include/clang/Analysis/FlowSensitive/Value.h
+++ b/clang/include/clang/Analysis/FlowSensitive/Value.h
@@ -306,6 +306,9 @@ class StructValue final : public Value {
/// Assigns `Val` as the child value for `D`.
void setChild(const ValueDecl &D, Value &Val) { Children[&D] = &Val; }
+ /// Clears any value assigned as the child value for `D`.
+ void clearChild(const ValueDecl &D) { Children.erase(&D); }
+
llvm::iterator_range<
llvm::DenseMap<const ValueDecl *, Value *>::const_iterator>
children() const {
diff --git a/clang/lib/Analysis/FlowSensitive/CMakeLists.txt b/clang/lib/Analysis/FlowSensitive/CMakeLists.txt
index 5176c0924902e..39a7e34f7de03 100644
--- a/clang/lib/Analysis/FlowSensitive/CMakeLists.txt
+++ b/clang/lib/Analysis/FlowSensitive/CMakeLists.txt
@@ -5,6 +5,7 @@ add_clang_library(clangAnalysisFlowSensitive
DataflowEnvironment.cpp
HTMLLogger.cpp
Logger.cpp
+ RecordOps.cpp
Transfer.cpp
TypeErasedDataflowAnalysis.cpp
Value.cpp
diff --git a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
index c7ecdf4538d23..b2e67651626d5 100644
--- a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
+++ b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
@@ -739,6 +739,23 @@ void Environment::setValue(const StorageLocation &Loc, Value &Val) {
}
}
+void Environment::clearValue(const StorageLocation &Loc) {
+ LocToVal.erase(&Loc);
+
+ if (auto It = MemberLocToStruct.find(&Loc); It != MemberLocToStruct.end()) {
+ // `Loc` is the location of a struct member so we need to also clear the
+ // member in the corresponding `StructValue`.
+
+ assert(It->second.first != nullptr);
+ StructValue &StructVal = *It->second.first;
+
+ assert(It->second.second != nullptr);
+ const ValueDecl &Member = *It->second.second;
+
+ StructVal.clearChild(Member);
+ }
+}
+
void Environment::setValueStrict(const Expr &E, Value &Val) {
assert(E.isPRValue());
assert(!isa<ReferenceValue>(Val));
diff --git a/clang/lib/Analysis/FlowSensitive/RecordOps.cpp b/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
new file mode 100644
index 0000000000000..92d9cef8ea858
--- /dev/null
+++ b/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
@@ -0,0 +1,133 @@
+//===-- RecordOps.cpp -------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Operations on records (structs, classes, and unions).
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/FlowSensitive/RecordOps.h"
+
+#define DEBUG_TYPE "dataflow"
+
+namespace clang {
+namespace dataflow {
+
+void copyRecord(AggregateStorageLocation &Src, AggregateStorageLocation &Dst,
+ Environment &Env) {
+ QualType Type = Src.getType();
+
+ LLVM_DEBUG({
+ if (Dst.getType().getCanonicalType().getUnqualifiedType() !=
+ Type.getCanonicalType().getUnqualifiedType()) {
+ llvm::dbgs() << "Source type " << Type << "\n";
+ llvm::dbgs() << "Destination type " << Dst.getType() << "\n";
+ }
+ });
+ assert(Dst.getType().getCanonicalType().getUnqualifiedType() ==
+ Type.getCanonicalType().getUnqualifiedType());
+
+ for (auto [Field, SrcFieldLoc] : Src.children()) {
+ assert(SrcFieldLoc != nullptr);
+
+ StorageLocation &DstFieldLoc = Dst.getChild(*Field);
+
+ if (Field->getType()->isRecordType()) {
+ copyRecord(cast<AggregateStorageLocation>(*SrcFieldLoc),
+ cast<AggregateStorageLocation>(DstFieldLoc), Env);
+ } else {
+ if (Value *Val = Env.getValue(*SrcFieldLoc))
+ Env.setValue(DstFieldLoc, *Val);
+ else
+ Env.clearValue(DstFieldLoc);
+ }
+ }
+
+ StructValue *SrcVal = cast_or_null<StructValue>(Env.getValue(Src));
+ StructValue *DstVal = cast_or_null<StructValue>(Env.getValue(Dst));
+
+ if (SrcVal == nullptr || DstVal == nullptr)
+ return;
+
+ auto DstChildren = DstVal->children();
+ DstVal = &Env.create<StructValue>(llvm::DenseMap<const ValueDecl *, Value *>(
+ DstChildren.begin(), DstChildren.end()));
+ Env.setValue(Dst, *DstVal);
+
+ for (const auto &[Name, Value] : SrcVal->properties()) {
+ if (Value != nullptr)
+ DstVal->setProperty(Name, *Value);
+ }
+}
+
+bool recordsEqual(const AggregateStorageLocation &Loc1, const Environment &Env1,
+ const AggregateStorageLocation &Loc2,
+ const Environment &Env2) {
+ QualType Type = Loc1.getType();
+
+ LLVM_DEBUG({
+ if (Loc2.getType().getCanonicalType().getUnqualifiedType() !=
+ Type.getCanonicalType().getUnqualifiedType()) {
+ llvm::dbgs() << "Loc1 type " << Type << "\n";
+ llvm::dbgs() << "Loc2 type " << Loc2.getType() << "\n";
+ }
+ });
+ assert(Loc2.getType().getCanonicalType().getUnqualifiedType() ==
+ Type.getCanonicalType().getUnqualifiedType());
+
+ for (auto [Field, FieldLoc1] : Loc1.children()) {
+ assert(FieldLoc1 != nullptr);
+
+ StorageLocation &FieldLoc2 = Loc2.getChild(*Field);
+
+ if (Field->getType()->isRecordType()) {
+ if (!recordsEqual(cast<AggregateStorageLocation>(*FieldLoc1), Env1,
+ cast<AggregateStorageLocation>(FieldLoc2), Env2))
+ return false;
+ } else if (Field->getType()->isReferenceType()) {
+ auto *RefVal1 = cast_or_null<ReferenceValue>(Env1.getValue(*FieldLoc1));
+ auto *RefVal2 = cast_or_null<ReferenceValue>(Env1.getValue(FieldLoc2));
+ if (RefVal1 && RefVal2) {
+ if (&RefVal1->getReferentLoc() != &RefVal2->getReferentLoc())
+ return false;
+ } else {
+ // If either of `RefVal1` and `RefVal2` is null, we only consider them
+ // equal if they're both null.
+ if (RefVal1 || RefVal2)
+ return false;
+ }
+ } else {
+ if (Env1.getValue(*FieldLoc1) != Env2.getValue(FieldLoc2))
+ return false;
+ }
+ }
+
+ llvm::StringMap<Value *> Props1, Props2;
+
+ if (StructValue *Val1 = cast_or_null<StructValue>(Env1.getValue(Loc1)))
+ for (const auto &[Name, Value] : Val1->properties())
+ Props1[Name] = Value;
+ if (StructValue *Val2 = cast_or_null<StructValue>(Env2.getValue(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;
+}
+
+} // namespace dataflow
+} // namespace clang
diff --git a/clang/lib/Analysis/FlowSensitive/Transfer.cpp b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
index a7c84b1ac218e..c09b6b9a99ac3 100644
--- a/clang/lib/Analysis/FlowSensitive/Transfer.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
@@ -23,6 +23,7 @@
#include "clang/Analysis/FlowSensitive/ControlFlowContext.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/NoopAnalysis.h"
+#include "clang/Analysis/FlowSensitive/RecordOps.h"
#include "clang/Analysis/FlowSensitive/Value.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/OperatorKinds.h"
@@ -597,16 +598,21 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
const Expr *Arg = S->getArg(0);
assert(Arg != nullptr);
- if (S->isElidable()) {
- auto *ArgLoc = Env.getStorageLocation(*Arg, SkipPast::Reference);
- if (ArgLoc == nullptr)
- return;
+ auto *ArgLoc = cast<AggregateStorageLocation>(
+ Env.getStorageLocation(*Arg, SkipPast::Reference));
+ if (ArgLoc == nullptr)
+ return;
+ if (S->isElidable()) {
Env.setStorageLocation(*S, *ArgLoc);
- } else if (auto *ArgVal = Env.getValue(*Arg, SkipPast::Reference)) {
- auto &Loc = Env.createStorageLocation(*S);
+ } else {
+ auto &Loc =
+ cast<AggregateStorageLocation>(Env.createStorageLocation(*S));
Env.setStorageLocation(*S, Loc);
- Env.setValue(Loc, *ArgVal);
+ if (Value *Val = Env.createValue(S->getType())) {
+ Env.setValue(Loc, *Val);
+ copyRecord(*ArgLoc, Loc, Env);
+ }
}
return;
}
@@ -638,16 +644,17 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
!Method->isMoveAssignmentOperator())
return;
- auto *ObjectLoc = Env.getStorageLocation(*Arg0, SkipPast::Reference);
+ auto *ObjectLoc = cast_or_null<AggregateStorageLocation>(
+ Env.getStorageLocation(*Arg0, SkipPast::Reference));
if (ObjectLoc == nullptr)
return;
- auto *Val = Env.getValue(*Arg1, SkipPast::Reference);
- if (Val == nullptr)
+ auto *ArgLoc = cast_or_null<AggregateStorageLocation>(
+ Env.getStorageLocation(*Arg1, SkipPast::Reference));
+ if (ArgLoc == nullptr)
return;
- // Assign a value to the storage location of the object.
- Env.setValue(*ObjectLoc, *Val);
+ copyRecord(*ArgLoc, *ObjectLoc, Env);
// FIXME: Add a test for the value of the whole expression.
// Assign a storage location for the whole expression.
diff --git a/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt b/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt
index e1e9581bd25b4..81f804d10bf7c 100644
--- a/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt
+++ b/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt
@@ -14,6 +14,7 @@ add_clang_unittest(ClangAnalysisFlowSensitiveTests
MapLatticeTest.cpp
MatchSwitchTest.cpp
MultiVarConstantPropagationTest.cpp
+ RecordOpsTest.cpp
SignAnalysisTest.cpp
SingleVarConstantPropagationTest.cpp
SolverTest.cpp
diff --git a/clang/unittests/Analysis/FlowSensitive/RecordOpsTest.cpp b/clang/unittests/Analysis/FlowSensitive/RecordOpsTest.cpp
new file mode 100644
index 0000000000000..f0f7a5e225800
--- /dev/null
+++ b/clang/unittests/Analysis/FlowSensitive/RecordOpsTest.cpp
@@ -0,0 +1,205 @@
+//===- unittests/Analysis/FlowSensitive/RecordOpsTest.cpp -----------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/FlowSensitive/RecordOps.h"
+#include "TestingSupport.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace dataflow {
+namespace test {
+namespace {
+
+template <typename VerifyResultsT>
+void runDataflow(llvm::StringRef Code, VerifyResultsT VerifyResults,
+ LangStandard::Kind Std = LangStandard::lang_cxx17,
+ llvm::StringRef TargetFun = "target") {
+ ASSERT_THAT_ERROR(
+ runDataflowReturnError(Code, VerifyResults,
+ DataflowAnalysisOptions{BuiltinOptions{}}, Std,
+ TargetFun),
+ llvm::Succeeded());
+}
+
+TEST(RecordOpsTest, CopyRecord) {
+ std::string Code = R"(
+ struct S {
+ int outer_int;
+ int &ref;
+ struct {
+ int inner_int;
+ } inner;
+ };
+ void target(S s1, S s2) {
+ (void)s1.outer_int;
+ (void)s1.ref;
+ (void)s1.inner.inner_int;
+ // [[p]]
+ }
+ )";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ ASTContext &ASTCtx) {
+ Environment Env = getEnvironmentAtAnnotation(Results, "p");
+
+ const ValueDecl *OuterIntDecl = findValueDecl(ASTCtx, "outer_int");
+ const ValueDecl *RefDecl = findValueDecl(ASTCtx, "ref");
+ const ValueDecl *InnerDecl = findValueDecl(ASTCtx, "inner");
+ const ValueDecl *InnerIntDecl = findValueDecl(ASTCtx, "inner_int");
+
+ auto &S1 = getLocForDecl<AggregateStorageLocation>(ASTCtx, Env, "s1");
+ auto &S2 = getLocForDecl<AggregateStorageLocation>(ASTCtx, Env, "s2");
+ auto &Inner1 = cast<AggregateStorageLocation>(S1.getChild(*InnerDecl));
+ auto &Inner2 = cast<AggregateStorageLocation>(S2.getChild(*InnerDecl));
+
+ EXPECT_NE(Env.getValue(S1.getChild(*OuterIntDecl)),
+ Env.getValue(S2.getChild(*OuterIntDecl)));
+ EXPECT_NE(Env.getValue(S1.getChild(*RefDecl)),
+ Env.getValue(S2.getChild(*RefDecl)));
+ EXPECT_NE(Env.getValue(Inner1.getChild(*InnerIntDecl)),
+ Env.getValue(Inner2.getChild(*InnerIntDecl)));
+
+ auto *S1Val = cast<StructValue>(Env.getValue(S1));
+ auto *S2Val = cast<StructValue>(Env.getValue(S2));
+ EXPECT_NE(S1Val, S2Val);
+
+ S1Val->setProperty("prop", Env.getBoolLiteralValue(true));
+
+ copyRecord(S1, S2, Env);
+
+ EXPECT_EQ(Env.getValue(S1.getChild(*OuterIntDecl)),
+ Env.getValue(S2.getChild(*OuterIntDecl)));
+ EXPECT_EQ(Env.getValue(S1.getChild(*RefDecl)),
+ Env.getValue(S2.getChild(*RefDecl)));
+ EXPECT_EQ(Env.getValue(Inner1.getChild(*InnerIntDecl)),
+ Env.getValue(Inner2.getChild(*InnerIntDecl)));
+
+ S1Val = cast<StructValue>(Env.getValue(S1));
+ S2Val = cast<StructValue>(Env.getValue(S2));
+ EXPECT_NE(S1Val, S2Val);
+
+ EXPECT_EQ(S2Val->getProperty("prop"), &Env.getBoolLiteralValue(true));
+ });
+}
+
+TEST(RecordOpsTest, RecordsEqual) {
+ std::string Code = R"(
+ struct S {
+ int outer_int;
+ int &ref;
+ struct {
+ int inner_int;
+ } inner;
+ };
+ void target(S s1, S s2) {
+ (void)s1.outer_int;
+ (void)s1.ref;
+ (void)s1.inner.inner_int;
+ // [[p]]
+ }
+ )";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ ASTContext &ASTCtx) {
+ Environment Env = getEnvironmentAtAnnotation(Results, "p");
+
+ const ValueDecl *OuterIntDecl = findValueDecl(ASTCtx, "outer_int");
+ const ValueDecl *RefDecl = findValueDecl(ASTCtx, "ref");
+ const ValueDecl *InnerDecl = findValueDecl(ASTCtx, "inner");
+ const ValueDecl *InnerIntDecl = findValueDecl(ASTCtx, "inner_int");
+
+ auto &S1 = getLocForDecl<AggregateStorageLocation>(ASTCtx, Env, "s1");
+ auto &S2 = getLocForDecl<AggregateStorageLocation>(ASTCtx, Env, "s2");
+ auto &Inner2 = cast<AggregateStorageLocation>(S2.getChild(*InnerDecl));
+
+ cast<StructValue>(Env.getValue(S1))
+ ->setProperty("prop", Env.getBoolLiteralValue(true));
+
+ // Strategy: Create two equal records, then verify each of the various
+ // ways in which records can
diff er causes recordsEqual to return false.
+ // changes we can make to the record.
+
+ // This test reuses the same objects for multiple checks, which isn't
+ // great, but seems better than duplicating the setup code for every
+ // check.
+
+ copyRecord(S1, S2, Env);
+ EXPECT_TRUE(recordsEqual(S1, S2, Env));
+
+ // S2 has a
diff erent outer_int.
+ Env.setValue(S2.getChild(*OuterIntDecl), Env.create<IntegerValue>());
+ EXPECT_FALSE(recordsEqual(S1, S2, Env));
+ copyRecord(S1, S2, Env);
+ EXPECT_TRUE(recordsEqual(S1, S2, Env));
+
+ // S2 doesn't have outer_int at all.
+ Env.clearValue(S2.getChild(*OuterIntDecl));
+ EXPECT_FALSE(recordsEqual(S1, S2, Env));
+ copyRecord(S1, S2, Env);
+ EXPECT_TRUE(recordsEqual(S1, S2, Env));
+
+ // S2 has a
diff erent ref.
+ Env.setValue(S2.getChild(*RefDecl),
+ Env.create<ReferenceValue>(Env.createStorageLocation(
+ RefDecl->getType().getNonReferenceType())));
+ EXPECT_FALSE(recordsEqual(S1, S2, Env));
+ copyRecord(S1, S2, Env);
+ EXPECT_TRUE(recordsEqual(S1, S2, Env));
+
+ // S2 doesn't have ref at all.
+ Env.clearValue(S2.getChild(*RefDecl));
+ EXPECT_FALSE(recordsEqual(S1, S2, Env));
+ copyRecord(S1, S2, Env);
+ EXPECT_TRUE(recordsEqual(S1, S2, Env));
+
+ // S2 as a
diff erent inner_int.
+ Env.setValue(Inner2.getChild(*InnerIntDecl),
+ Env.create<IntegerValue>());
+ EXPECT_FALSE(recordsEqual(S1, S2, Env));
+ copyRecord(S1, S2, Env);
+ EXPECT_TRUE(recordsEqual(S1, S2, Env));
+
+ // S1 and S2 have the same property with
diff erent values.
+ cast<StructValue>(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<StructValue>(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<StructValue>(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
diff erent
+ // names.
+ cast<StructValue>(Env.getValue(S1))
+ ->setProperty("prop1", Env.getBoolLiteralValue(false));
+ cast<StructValue>(Env.getValue(S2))
+ ->setProperty("prop2", Env.getBoolLiteralValue(false));
+ EXPECT_FALSE(recordsEqual(S1, S2, Env));
+ });
+}
+
+} // namespace
+} // namespace test
+} // namespace dataflow
+} // namespace clang
diff --git a/clang/unittests/Analysis/FlowSensitive/TestingSupport.h b/clang/unittests/Analysis/FlowSensitive/TestingSupport.h
index c8274227d7467..e22600d7b2e44 100644
--- a/clang/unittests/Analysis/FlowSensitive/TestingSupport.h
+++ b/clang/unittests/Analysis/FlowSensitive/TestingSupport.h
@@ -33,6 +33,7 @@
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/MatchSwitch.h"
+#include "clang/Analysis/FlowSensitive/NoopAnalysis.h"
#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
#include "clang/Basic/LLVM.h"
#include "clang/Serialization/PCHContainerOperations.h"
@@ -381,6 +382,45 @@ checkDataflow(AnalysisInputs<AnalysisT> AI,
});
}
+using BuiltinOptions = DataflowAnalysisContext::Options;
+
+/// Runs dataflow on `Code` with a `NoopAnalysis` and calls `VerifyResults` to
+/// verify the results.
+template <typename VerifyResultsT>
+llvm::Error
+runDataflowReturnError(llvm::StringRef Code, VerifyResultsT VerifyResults,
+ DataflowAnalysisOptions Options,
+ LangStandard::Kind Std = LangStandard::lang_cxx17,
+ llvm::StringRef TargetFun = "target") {
+ using ast_matchers::hasName;
+ llvm::SmallVector<std::string, 3> ASTBuildArgs = {
+ // -fnodelayed-template-parsing is the default everywhere but on Windows.
+ // Set it explicitly so that tests behave the same on Windows as on other
+ // platforms.
+ "-fsyntax-only", "-fno-delayed-template-parsing",
+ "-std=" +
+ std::string(LangStandard::getLangStandardForKind(Std).getName())};
+ AnalysisInputs<NoopAnalysis> AI(
+ Code, hasName(TargetFun),
+ [UseBuiltinModel = Options.BuiltinOpts.has_value()](ASTContext &C,
+ Environment &Env) {
+ return NoopAnalysis(
+ C,
+ DataflowAnalysisOptions{
+ UseBuiltinModel ? Env.getDataflowAnalysisContext().getOptions()
+ : std::optional<BuiltinOptions>()});
+ });
+ AI.ASTBuildArgs = ASTBuildArgs;
+ if (Options.BuiltinOpts)
+ AI.BuiltinOptions = *Options.BuiltinOpts;
+ return checkDataflow<NoopAnalysis>(
+ std::move(AI),
+ /*VerifyResults=*/
+ [&VerifyResults](
+ const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ const AnalysisOutputs &AO) { VerifyResults(Results, AO.ASTCtx); });
+}
+
/// Returns the `ValueDecl` for the given identifier.
///
/// Requirements:
@@ -388,6 +428,21 @@ checkDataflow(AnalysisInputs<AnalysisT> AI,
/// `Name` must be unique in `ASTCtx`.
const ValueDecl *findValueDecl(ASTContext &ASTCtx, llvm::StringRef Name);
+/// Returns the storage location (of type `LocT`) for the given identifier.
+/// `LocT` must be a subclass of `StorageLocation` and must be of the
+/// appropriate type.
+///
+/// Requirements:
+///
+/// `Name` must be unique in `ASTCtx`.
+template <class LocT>
+LocT &getLocForDecl(ASTContext &ASTCtx, const Environment &Env,
+ llvm::StringRef Name) {
+ const ValueDecl *VD = findValueDecl(ASTCtx, Name);
+ assert(VD != nullptr);
+ return *cast<LocT>(Env.getStorageLocation(*VD));
+}
+
/// Returns the value (of type `ValueT`) for the given identifier.
/// `ValueT` must be a subclass of `Value` and must be of the appropriate type.
///
diff --git a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
index 5f52d74afe388..5e7abcc6ce750 100644
--- a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
@@ -12,7 +12,7 @@
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
-#include "clang/Analysis/FlowSensitive/NoopAnalysis.h"
+#include "clang/Analysis/FlowSensitive/RecordOps.h"
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
#include "clang/Analysis/FlowSensitive/Value.h"
#include "clang/Basic/LangStandard.h"
@@ -38,59 +38,22 @@ using ::testing::Ne;
using ::testing::NotNull;
using ::testing::UnorderedElementsAre;
-using BuiltinOptions = DataflowAnalysisContext::Options;
-
-template <typename Matcher>
-llvm::Error
-runDataflowReturnError(llvm::StringRef Code, Matcher Match,
- DataflowAnalysisOptions Options,
- LangStandard::Kind Std = LangStandard::lang_cxx17,
- llvm::StringRef TargetFun = "target") {
- using ast_matchers::hasName;
- llvm::SmallVector<std::string, 3> ASTBuildArgs = {
- // -fnodelayed-template-parsing is the default everywhere but on Windows.
- // Set it explicitly so that tests behave the same on Windows as on other
- // platforms.
- "-fsyntax-only", "-fno-delayed-template-parsing",
- "-std=" +
- std::string(LangStandard::getLangStandardForKind(Std).getName())};
- AnalysisInputs<NoopAnalysis> AI(
- Code, hasName(TargetFun),
- [UseBuiltinModel = Options.BuiltinOpts.has_value()](ASTContext &C,
- Environment &Env) {
- return NoopAnalysis(
- C,
- DataflowAnalysisOptions{
- UseBuiltinModel ? Env.getDataflowAnalysisContext().getOptions()
- : std::optional<BuiltinOptions>()});
- });
- AI.ASTBuildArgs = ASTBuildArgs;
- if (Options.BuiltinOpts)
- AI.BuiltinOptions = *Options.BuiltinOpts;
- return checkDataflow<NoopAnalysis>(
- std::move(AI),
- /*VerifyResults=*/
- [&Match](
- const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
- const AnalysisOutputs &AO) { Match(Results, AO.ASTCtx); });
-}
-
-template <typename Matcher>
-void runDataflow(llvm::StringRef Code, Matcher Match,
+template <typename VerifyResultsT>
+void runDataflow(llvm::StringRef Code, VerifyResultsT VerifyResults,
DataflowAnalysisOptions Options,
LangStandard::Kind Std = LangStandard::lang_cxx17,
llvm::StringRef TargetFun = "target") {
ASSERT_THAT_ERROR(
- runDataflowReturnError(Code, Match, Options, Std, TargetFun),
+ runDataflowReturnError(Code, VerifyResults, Options, Std, TargetFun),
llvm::Succeeded());
}
-template <typename Matcher>
-void runDataflow(llvm::StringRef Code, Matcher Match,
+template <typename VerifyResultsT>
+void runDataflow(llvm::StringRef Code, VerifyResultsT VerifyResults,
LangStandard::Kind Std = LangStandard::lang_cxx17,
bool ApplyBuiltinTransfer = true,
llvm::StringRef TargetFun = "target") {
- runDataflow(Code, std::move(Match),
+ runDataflow(Code, std::move(VerifyResults),
{ApplyBuiltinTransfer ? BuiltinOptions{}
: std::optional<BuiltinOptions>()},
Std, TargetFun);
@@ -2012,22 +1975,19 @@ TEST(TransferTest, AssignmentOperator) {
};
void target() {
- A Foo;
- A Bar;
- (void)Foo.Baz;
+ A Foo = { 1 };
+ A Bar = { 2 };
// [[p1]]
Foo = Bar;
// [[p2]]
+ Foo.Baz = 3;
+ // [[p3]]
}
)";
runDataflow(
Code,
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
ASTContext &ASTCtx) {
- ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1", "p2"));
- const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
- const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2");
-
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
ASSERT_THAT(FooDecl, NotNull());
@@ -2037,35 +1997,81 @@ TEST(TransferTest, AssignmentOperator) {
const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
ASSERT_THAT(BazDecl, NotNull());
- const auto *FooLoc1 =
- cast<AggregateStorageLocation>(Env1.getStorageLocation(*FooDecl));
- const auto *BarLoc1 =
- cast<AggregateStorageLocation>(Env1.getStorageLocation(*BarDecl));
+ // Before copy assignment.
+ {
+ const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
+
+ const auto *FooLoc1 =
+ cast<AggregateStorageLocation>(Env1.getStorageLocation(*FooDecl));
+ const auto *BarLoc1 =
+ cast<AggregateStorageLocation>(Env1.getStorageLocation(*BarDecl));
+ EXPECT_FALSE(recordsEqual(*FooLoc1, *BarLoc1, Env1));
+
+ const auto *FooBazVal1 =
+ cast<IntegerValue>(Env1.getValue(FooLoc1->getChild(*BazDecl)));
+ const auto *BarBazVal1 =
+ cast<IntegerValue>(Env1.getValue(BarLoc1->getChild(*BazDecl)));
+ EXPECT_NE(FooBazVal1, BarBazVal1);
+ }
- const auto *FooVal1 = cast<StructValue>(Env1.getValue(*FooLoc1));
- const auto *BarVal1 = cast<StructValue>(Env1.getValue(*BarLoc1));
- EXPECT_NE(FooVal1, BarVal1);
+ // After copy assignment.
+ {
+ const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2");
- const auto *FooBazVal1 =
- cast<IntegerValue>(Env1.getValue(FooLoc1->getChild(*BazDecl)));
- const auto *BarBazVal1 =
- cast<IntegerValue>(Env1.getValue(BarLoc1->getChild(*BazDecl)));
- EXPECT_NE(FooBazVal1, BarBazVal1);
+ const auto *FooLoc2 =
+ cast<AggregateStorageLocation>(Env2.getStorageLocation(*FooDecl));
+ const auto *BarLoc2 =
+ cast<AggregateStorageLocation>(Env2.getStorageLocation(*BarDecl));
- const auto *FooLoc2 =
- cast<AggregateStorageLocation>(Env2.getStorageLocation(*FooDecl));
- const auto *BarLoc2 =
- cast<AggregateStorageLocation>(Env2.getStorageLocation(*BarDecl));
+ const auto *FooVal2 = cast<StructValue>(Env2.getValue(*FooLoc2));
+ const auto *BarVal2 = cast<StructValue>(Env2.getValue(*BarLoc2));
+ EXPECT_NE(FooVal2, BarVal2);
- const auto *FooVal2 = cast<StructValue>(Env2.getValue(*FooLoc2));
- const auto *BarVal2 = cast<StructValue>(Env2.getValue(*BarLoc2));
- EXPECT_EQ(FooVal2, BarVal2);
+ EXPECT_TRUE(recordsEqual(*FooLoc2, *BarLoc2, Env2));
- const auto *FooBazVal2 =
- cast<IntegerValue>(Env2.getValue(FooLoc1->getChild(*BazDecl)));
- const auto *BarBazVal2 =
- cast<IntegerValue>(Env2.getValue(BarLoc1->getChild(*BazDecl)));
- EXPECT_EQ(FooBazVal2, BarBazVal2);
+ const auto *FooBazVal2 =
+ cast<IntegerValue>(Env2.getValue(FooLoc2->getChild(*BazDecl)));
+ const auto *BarBazVal2 =
+ cast<IntegerValue>(Env2.getValue(BarLoc2->getChild(*BazDecl)));
+ EXPECT_EQ(FooBazVal2, BarBazVal2);
+ }
+
+ // After value update.
+ {
+ const Environment &Env3 = getEnvironmentAtAnnotation(Results, "p3");
+
+ const auto *FooLoc3 =
+ cast<AggregateStorageLocation>(Env3.getStorageLocation(*FooDecl));
+ const auto *BarLoc3 =
+ cast<AggregateStorageLocation>(Env3.getStorageLocation(*BarDecl));
+ EXPECT_FALSE(recordsEqual(*FooLoc3, *BarLoc3, Env3));
+
+ const auto *FooBazVal3 =
+ cast<IntegerValue>(Env3.getValue(FooLoc3->getChild(*BazDecl)));
+ const auto *BarBazVal3 =
+ cast<IntegerValue>(Env3.getValue(BarLoc3->getChild(*BazDecl)));
+ EXPECT_NE(FooBazVal3, BarBazVal3);
+ }
+ });
+}
+
+TEST(TransferTest, AssignmentOperatorFromCallResult) {
+ std::string Code = R"(
+ struct A {};
+ A ReturnA();
+
+ void target() {
+ A MyA;
+ MyA = ReturnA();
+ }
+ )";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ ASTContext &ASTCtx) {
+ // As of this writing, we don't produce a `Value` for the call
+ // `ReturnA()`. The only condition we're testing for is that the
+ // analysis should not crash in this case.
});
}
@@ -2076,19 +2082,17 @@ TEST(TransferTest, CopyConstructor) {
};
void target() {
- A Foo;
- (void)Foo.Baz;
+ A Foo = { 1 };
A Bar = Foo;
- // [[p]]
+ // [[after_copy]]
+ Foo.Baz = 2;
+ // [[after_update]]
}
)";
runDataflow(
Code,
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
ASTContext &ASTCtx) {
- ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
- const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
-
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
ASSERT_THAT(FooDecl, NotNull());
@@ -2098,20 +2102,50 @@ TEST(TransferTest, CopyConstructor) {
const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
ASSERT_THAT(BazDecl, NotNull());
- const auto *FooLoc =
- cast<AggregateStorageLocation>(Env.getStorageLocation(*FooDecl));
- const auto *BarLoc =
- cast<AggregateStorageLocation>(Env.getStorageLocation(*BarDecl));
+ // after_copy
+ {
+ const Environment &Env =
+ getEnvironmentAtAnnotation(Results, "after_copy");
+
+ const auto *FooLoc =
+ cast<AggregateStorageLocation>(Env.getStorageLocation(*FooDecl));
+ const auto *BarLoc =
+ cast<AggregateStorageLocation>(Env.getStorageLocation(*BarDecl));
+
+ // `Foo` and `Bar` have
diff erent `StructValue`s associated with them.
+ const auto *FooVal = cast<StructValue>(Env.getValue(*FooLoc));
+ const auto *BarVal = cast<StructValue>(Env.getValue(*BarLoc));
+ EXPECT_NE(FooVal, BarVal);
+
+ // But the records compare equal.
+ EXPECT_TRUE(recordsEqual(*FooLoc, *BarLoc, Env));
+
+ // In particular, the value of `Baz` in both records is the same.
+ const auto *FooBazVal =
+ cast<IntegerValue>(Env.getValue(FooLoc->getChild(*BazDecl)));
+ const auto *BarBazVal =
+ cast<IntegerValue>(Env.getValue(BarLoc->getChild(*BazDecl)));
+ EXPECT_EQ(FooBazVal, BarBazVal);
+ }
- const auto *FooVal = cast<StructValue>(Env.getValue(*FooLoc));
- const auto *BarVal = cast<StructValue>(Env.getValue(*BarLoc));
- EXPECT_EQ(FooVal, BarVal);
+ // after_update
+ {
+ const Environment &Env =
+ getEnvironmentAtAnnotation(Results, "after_update");
- const auto *FooBazVal =
- cast<IntegerValue>(Env.getValue(FooLoc->getChild(*BazDecl)));
- const auto *BarBazVal =
- cast<IntegerValue>(Env.getValue(BarLoc->getChild(*BazDecl)));
- EXPECT_EQ(FooBazVal, BarBazVal);
+ const auto *FooLoc =
+ cast<AggregateStorageLocation>(Env.getStorageLocation(*FooDecl));
+ const auto *BarLoc =
+ cast<AggregateStorageLocation>(Env.getStorageLocation(*BarDecl));
+
+ EXPECT_FALSE(recordsEqual(*FooLoc, *BarLoc, Env));
+
+ const auto *FooBazVal =
+ cast<IntegerValue>(Env.getValue(FooLoc->getChild(*BazDecl)));
+ const auto *BarBazVal =
+ cast<IntegerValue>(Env.getValue(BarLoc->getChild(*BazDecl)));
+ EXPECT_NE(FooBazVal, BarBazVal);
+ }
});
}
@@ -2150,10 +2184,7 @@ TEST(TransferTest, CopyConstructorWithDefaultArgument) {
cast<AggregateStorageLocation>(Env.getStorageLocation(*FooDecl));
const auto *BarLoc =
cast<AggregateStorageLocation>(Env.getStorageLocation(*BarDecl));
-
- const auto *FooVal = cast<StructValue>(Env.getValue(*FooLoc));
- const auto *BarVal = cast<StructValue>(Env.getValue(*BarLoc));
- EXPECT_EQ(FooVal, BarVal);
+ EXPECT_TRUE(recordsEqual(*FooLoc, *BarLoc, Env));
const auto *FooBazVal =
cast<IntegerValue>(Env.getValue(FooLoc->getChild(*BazDecl)));
@@ -2196,10 +2227,7 @@ TEST(TransferTest, CopyConstructorWithParens) {
cast<AggregateStorageLocation>(Env.getStorageLocation(*FooDecl));
const auto *BarLoc =
cast<AggregateStorageLocation>(Env.getStorageLocation(*BarDecl));
-
- const auto *FooVal = cast<StructValue>(Env.getValue(*FooLoc));
- const auto *BarVal = cast<StructValue>(Env.getValue(*BarLoc));
- EXPECT_EQ(FooVal, BarVal);
+ EXPECT_TRUE(recordsEqual(*FooLoc, *BarLoc, Env));
const auto *FooBazVal =
cast<IntegerValue>(Env.getValue(FooLoc->getChild(*BazDecl)));
@@ -2264,6 +2292,8 @@ TEST(TransferTest, MoveConstructor) {
const auto *BarVal1 = cast<StructValue>(Env1.getValue(*BarLoc1));
EXPECT_NE(FooVal1, BarVal1);
+ EXPECT_FALSE(recordsEqual(*FooLoc1, *BarLoc1, Env1));
+
const auto *FooBazVal1 =
cast<IntegerValue>(Env1.getValue(FooLoc1->getChild(*BazDecl)));
const auto *BarBazVal1 =
@@ -2273,7 +2303,8 @@ TEST(TransferTest, MoveConstructor) {
const auto *FooLoc2 =
cast<AggregateStorageLocation>(Env2.getStorageLocation(*FooDecl));
const auto *FooVal2 = cast<StructValue>(Env2.getValue(*FooLoc2));
- EXPECT_EQ(FooVal2, BarVal1);
+ EXPECT_NE(FooVal2, BarVal1);
+ EXPECT_TRUE(recordsEqual(*FooLoc2, Env2, *BarLoc1, Env1));
const auto *FooBazVal2 =
cast<IntegerValue>(Env2.getValue(FooLoc1->getChild(*BazDecl)));
More information about the cfe-commits
mailing list