[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-19 Thread via cfe-commits

https://github.com/martinboehme closed 
https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-18 Thread Gábor Horváth via cfe-commits

https://github.com/Xazax-hun approved this pull request.

Thanks! At this point all of my comments are addressed, and I am happy with the 
patch. 

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-18 Thread Gábor Horváth via cfe-commits


@@ -212,8 +212,23 @@ TEST(TransferTest, CopyRecordFromDerivedToBase) {
   // [[p]]
 }
   )";
+  auto SyntheticFieldCallback = [](QualType Ty) -> llvm::StringMap {
+CXXRecordDecl *ADecl = nullptr;
+if (Ty.getAsString() == "A")
+  ADecl = Ty->getAsCXXRecordDecl();
+else if (Ty.getAsString() == "B")
+  ADecl = Ty->getAsCXXRecordDecl()
+  ->bases_begin()
+  ->getType()
+  ->getAsCXXRecordDecl();
+else
+  return {};
+QualType IntTy = getFieldNamed(ADecl, "i")->getType();
+return {{"synth_int", IntTy}};
+  };
+  // Copy derived to base class.

Xazax-hun wrote:

Ah, OK, I see! Thanks, I somehow expected the behavior to be present in the 
code as well. Makes sense. 

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-18 Thread via cfe-commits


@@ -14,18 +14,52 @@
 
 #define DEBUG_TYPE "dataflow"
 
-void clang::dataflow::copyRecord(RecordStorageLocation &Src,
- RecordStorageLocation &Dst, Environment &Env) 
{
+namespace clang::dataflow {
+
+static void copyField(const ValueDecl *Field, StorageLocation *SrcFieldLoc,

martinboehme wrote:

Yes, the swapped arguments are a consideration. I'll keep this in mind for a 
possible future change.

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-18 Thread via cfe-commits


@@ -212,8 +212,23 @@ TEST(TransferTest, CopyRecordFromDerivedToBase) {
   // [[p]]
 }
   )";
+  auto SyntheticFieldCallback = [](QualType Ty) -> llvm::StringMap {
+CXXRecordDecl *ADecl = nullptr;
+if (Ty.getAsString() == "A")
+  ADecl = Ty->getAsCXXRecordDecl();
+else if (Ty.getAsString() == "B")
+  ADecl = Ty->getAsCXXRecordDecl()
+  ->bases_begin()
+  ->getType()
+  ->getAsCXXRecordDecl();
+else
+  return {};
+QualType IntTy = getFieldNamed(ADecl, "i")->getType();
+return {{"synth_int", IntTy}};
+  };
+  // Copy derived to base class.

martinboehme wrote:

It doesn't happen in the analyzed code snippet at all. There's an explicit call 
to `copyRecord()` below -- this is where the copy happens.

This test is trying to be a "unit test" for `copyRecord()`. The code snippet is 
only there to establish the record type; the actual behavior under test is the 
`copyRecord()` call (i.e. I'm intentionally calling this function directly 
rather than trying to trigger a call to `copyRecord()` indirectly).

This is different from how we usually do tests in the framework (and in 
compilers in general), but I think if we _can_ do unit tests relatively easily 
(and in this case we can), it makes the tests clearer because you don't need to 
reason about "how does this code trigger the behavior we want to test".

(There are also code-level tests for copy construction and assignment that 
exercise `copyRecord()`; we obviously want to have these too.)

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-18 Thread via cfe-commits

https://github.com/martinboehme edited 
https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-18 Thread via cfe-commits


@@ -35,45 +69,27 @@ void clang::dataflow::copyRecord(RecordStorageLocation &Src,
   });
   assert(compatibleTypes);
 
-  for (auto [Field, DstFieldLoc] : Dst.children()) {
-StorageLocation *SrcFieldLoc = Src.getChild(*Field);
-
-assert(Field->getType()->isReferenceType() ||
-   (SrcFieldLoc != nullptr && DstFieldLoc != nullptr));
-
-if (Field->getType()->isRecordType()) {
-  copyRecord(cast(*SrcFieldLoc),
- cast(*DstFieldLoc), Env);
-} else if (Field->getType()->isReferenceType()) {
-  Dst.setChild(*Field, SrcFieldLoc);
-} else {
-  if (Value *Val = Env.getValue(*SrcFieldLoc))
-Env.setValue(*DstFieldLoc, *Val);
-  else
-Env.clearValue(*DstFieldLoc);
-}
-  }
-
-  for (const auto &[Name, SynthFieldLoc] : Src.synthetic_fields()) {
-if (SynthFieldLoc->getType()->isRecordType()) {
-  copyRecord(*cast(SynthFieldLoc),
- cast(Dst.getSyntheticField(Name)), 
Env);
-} else {
-  if (Value *Val = Env.getValue(*SynthFieldLoc))
-Env.setValue(Dst.getSyntheticField(Name), *Val);
-  else
-Env.clearValue(Dst.getSyntheticField(Name));
-}
+  if (SrcType == DstType || (SrcDecl != nullptr && DstDecl != nullptr &&
+ SrcDecl->isDerivedFrom(DstDecl))) {
+for (auto [Field, DstFieldLoc] : Dst.children())

martinboehme wrote:

The two `lambda()` calls would be in the if and the else branch, respectively? 
I.e.

```cxx
  if (SrcType == DstType || (SrcDecl != nullptr && DstDecl != nullptr &&
 SrcDecl->isDerivedFrom(DstDecl))) {
lambda(Dst);
// And synthetic fields
  } else {
lambda(Src);
// And synthetic fields
  }
```

The problem is that this doesn't work because it changes behavior. This is what 
we have in the if branch right now:

```cxx
for (auto [Field, DstFieldLoc] : Dst.children())
  copyField(Field, Src.getChild(*Field), DstFieldLoc, Dst, Env);
```

Changing this to `lambda(Dst)` would mean that the behavior becomes:

```
for (auto [Field, SrcFieldLoc] : Dst.children())
  copyField(Field, SrcFieldLoc, Dst.getChild(*Field), Dst, Env);
```

Note that the name `SrcFieldLoc` is now essentially wrong, and the 
`copyField()` becomes a no-op because it's copying a field to itself.

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-18 Thread Gábor Horváth via cfe-commits

https://github.com/Xazax-hun approved this pull request.


https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-18 Thread Gábor Horváth via cfe-commits


@@ -212,8 +212,23 @@ TEST(TransferTest, CopyRecordFromDerivedToBase) {
   // [[p]]
 }
   )";
+  auto SyntheticFieldCallback = [](QualType Ty) -> llvm::StringMap {
+CXXRecordDecl *ADecl = nullptr;
+if (Ty.getAsString() == "A")
+  ADecl = Ty->getAsCXXRecordDecl();
+else if (Ty.getAsString() == "B")
+  ADecl = Ty->getAsCXXRecordDecl()
+  ->bases_begin()
+  ->getType()
+  ->getAsCXXRecordDecl();
+else
+  return {};
+QualType IntTy = getFieldNamed(ADecl, "i")->getType();
+return {{"synth_int", IntTy}};
+  };
+  // Copy derived to base class.

Xazax-hun wrote:

I am probably missing something. Where does the copy happen in the analyzed 
code snippet?

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-18 Thread Gábor Horváth via cfe-commits


@@ -35,45 +69,27 @@ void clang::dataflow::copyRecord(RecordStorageLocation &Src,
   });
   assert(compatibleTypes);
 
-  for (auto [Field, DstFieldLoc] : Dst.children()) {
-StorageLocation *SrcFieldLoc = Src.getChild(*Field);
-
-assert(Field->getType()->isReferenceType() ||
-   (SrcFieldLoc != nullptr && DstFieldLoc != nullptr));
-
-if (Field->getType()->isRecordType()) {
-  copyRecord(cast(*SrcFieldLoc),
- cast(*DstFieldLoc), Env);
-} else if (Field->getType()->isReferenceType()) {
-  Dst.setChild(*Field, SrcFieldLoc);
-} else {
-  if (Value *Val = Env.getValue(*SrcFieldLoc))
-Env.setValue(*DstFieldLoc, *Val);
-  else
-Env.clearValue(*DstFieldLoc);
-}
-  }
-
-  for (const auto &[Name, SynthFieldLoc] : Src.synthetic_fields()) {
-if (SynthFieldLoc->getType()->isRecordType()) {
-  copyRecord(*cast(SynthFieldLoc),
- cast(Dst.getSyntheticField(Name)), 
Env);
-} else {
-  if (Value *Val = Env.getValue(*SynthFieldLoc))
-Env.setValue(Dst.getSyntheticField(Name), *Val);
-  else
-Env.clearValue(Dst.getSyntheticField(Name));
-}
+  if (SrcType == DstType || (SrcDecl != nullptr && DstDecl != nullptr &&
+ SrcDecl->isDerivedFrom(DstDecl))) {
+for (auto [Field, DstFieldLoc] : Dst.children())

Xazax-hun wrote:

I had something like this in mind:
```
auto lambda = [&](auto& Iter) {
for (auto [Field, SrcFieldLoc] : Iter.children())
  copyField(Field, SrcFieldLoc, Dst.getChild(*Field), Dst, Env);
};

lambda(Src);
lambda(Dst);
```

That being said, something like this is not necessarily that much clearer, so 
feel free to leave it as is.  

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-18 Thread Gábor Horváth via cfe-commits


@@ -14,18 +14,52 @@
 
 #define DEBUG_TYPE "dataflow"
 
-void clang::dataflow::copyRecord(RecordStorageLocation &Src,
- RecordStorageLocation &Dst, Environment &Env) 
{
+namespace clang::dataflow {
+
+static void copyField(const ValueDecl *Field, StorageLocation *SrcFieldLoc,

Xazax-hun wrote:

> We could consider using StorageLocation constness to encode "can this storage 
> location be written to"

I think there could be some benefit to something like this, in some cases it 
could catch "swapped arguments" at compile time. That being said, I do not 
insist. It is possible that it would not carry its weight.

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-18 Thread via cfe-commits


@@ -212,8 +212,23 @@ TEST(TransferTest, CopyRecordFromDerivedToBase) {
   // [[p]]
 }
   )";
+  auto SyntheticFieldCallback = [](QualType Ty) -> llvm::StringMap {
+CXXRecordDecl *ADecl = nullptr;
+if (Ty.getAsString() == "A")
+  ADecl = Ty->getAsCXXRecordDecl();
+else if (Ty.getAsString() == "B")
+  ADecl = Ty->getAsCXXRecordDecl()
+  ->bases_begin()
+  ->getType()
+  ->getAsCXXRecordDecl();
+else
+  return {};
+QualType IntTy = getFieldNamed(ADecl, "i")->getType();
+return {{"synth_int", IntTy}};
+  };
+  // Copy derived to base class.

martinboehme wrote:

This means that the following lines test the case of copying from the derived 
class to the base class, whereas another piece of the test further down tests 
the case of copying from the base class to the derived class. I'm not sure how 
I could word this better -- I've reworded slightly to "Test copying derived to 
base class", as maybe it wasn't clear that I was describing the behavior to be 
tested?

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-18 Thread via cfe-commits

https://github.com/martinboehme updated 
https://github.com/llvm/llvm-project/pull/85064

>From 9362adfb0d1a61cb56b897f31fd9f2ead1605383 Mon Sep 17 00:00:00 2001
From: Martin Braenne 
Date: Wed, 13 Mar 2024 12:07:24 +
Subject: [PATCH 1/3] [clang][dataflow] Model assignment to derived class from
 base.

This is a relatively rare case, but

- It's still nice to get this right,
- We can remove the special case for this in `VisitCXXOperatorCallExpr()` (that
  simply bails out), and
- With this in place, I can avoid having to add a similar special case in an
  upcoming patch.
---
 .../clang/Analysis/FlowSensitive/RecordOps.h  |  6 +-
 .../lib/Analysis/FlowSensitive/RecordOps.cpp  | 94 +++
 clang/lib/Analysis/FlowSensitive/Transfer.cpp |  9 --
 .../Analysis/FlowSensitive/RecordOpsTest.cpp  | 46 -
 .../Analysis/FlowSensitive/TransferTest.cpp   | 25 -
 5 files changed, 126 insertions(+), 54 deletions(-)

diff --git a/clang/include/clang/Analysis/FlowSensitive/RecordOps.h 
b/clang/include/clang/Analysis/FlowSensitive/RecordOps.h
index 783e53e980aa2c..8fad45fc11d81e 100644
--- a/clang/include/clang/Analysis/FlowSensitive/RecordOps.h
+++ b/clang/include/clang/Analysis/FlowSensitive/RecordOps.h
@@ -31,7 +31,11 @@ namespace dataflow {
 ///
 /// Requirements:
 ///
-///  `Src` and `Dst` must have the same canonical unqualified type.
+///  Either:
+///- `Src` and `Dest` must have the same canonical unqualified type, or
+///- The type of `Src` must be derived from `Dest`, or
+///- The type of `Dest` must be derived from `Src` (in this case, any 
fields
+///  that are only present in `Dest` are not overwritten).
 void copyRecord(RecordStorageLocation &Src, RecordStorageLocation &Dst,
 Environment &Env);
 
diff --git a/clang/lib/Analysis/FlowSensitive/RecordOps.cpp 
b/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
index da4dd6dc078515..4fc4c15a07a1ce 100644
--- a/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
+++ b/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
@@ -14,18 +14,52 @@
 
 #define DEBUG_TYPE "dataflow"
 
-void clang::dataflow::copyRecord(RecordStorageLocation &Src,
- RecordStorageLocation &Dst, Environment &Env) 
{
+namespace clang::dataflow {
+
+static void copyField(const ValueDecl *Field, StorageLocation *SrcFieldLoc,
+  StorageLocation *DstFieldLoc, RecordStorageLocation &Dst,
+  Environment &Env) {
+  assert(Field->getType()->isReferenceType() ||
+ (SrcFieldLoc != nullptr && DstFieldLoc != nullptr));
+
+  if (Field->getType()->isRecordType()) {
+copyRecord(cast(*SrcFieldLoc),
+   cast(*DstFieldLoc), Env);
+  } else if (Field->getType()->isReferenceType()) {
+Dst.setChild(*Field, SrcFieldLoc);
+  } else {
+if (Value *Val = Env.getValue(*SrcFieldLoc))
+  Env.setValue(*DstFieldLoc, *Val);
+else
+  Env.clearValue(*DstFieldLoc);
+  }
+}
+
+static void copySyntheticField(QualType FieldType, StorageLocation 
&SrcFieldLoc,
+   StorageLocation &DstFieldLoc, Environment &Env) 
{
+  if (FieldType->isRecordType()) {
+copyRecord(cast(SrcFieldLoc),
+   cast(DstFieldLoc), Env);
+  } else {
+if (Value *Val = Env.getValue(SrcFieldLoc))
+  Env.setValue(DstFieldLoc, *Val);
+else
+  Env.clearValue(DstFieldLoc);
+  }
+}
+
+void copyRecord(RecordStorageLocation &Src, RecordStorageLocation &Dst,
+Environment &Env) {
   auto SrcType = Src.getType().getCanonicalType().getUnqualifiedType();
   auto DstType = Dst.getType().getCanonicalType().getUnqualifiedType();
 
   auto SrcDecl = SrcType->getAsCXXRecordDecl();
   auto DstDecl = DstType->getAsCXXRecordDecl();
 
-  bool compatibleTypes =
+  [[maybe_unused]] bool compatibleTypes =
   SrcType == DstType ||
-  (SrcDecl && DstDecl && SrcDecl->isDerivedFrom(DstDecl));
-  (void)compatibleTypes;
+  (SrcDecl != nullptr && DstDecl != nullptr &&
+   (SrcDecl->isDerivedFrom(DstDecl) || DstDecl->isDerivedFrom(SrcDecl)));
 
   LLVM_DEBUG({
 if (!compatibleTypes) {
@@ -35,45 +69,27 @@ void clang::dataflow::copyRecord(RecordStorageLocation &Src,
   });
   assert(compatibleTypes);
 
-  for (auto [Field, DstFieldLoc] : Dst.children()) {
-StorageLocation *SrcFieldLoc = Src.getChild(*Field);
-
-assert(Field->getType()->isReferenceType() ||
-   (SrcFieldLoc != nullptr && DstFieldLoc != nullptr));
-
-if (Field->getType()->isRecordType()) {
-  copyRecord(cast(*SrcFieldLoc),
- cast(*DstFieldLoc), Env);
-} else if (Field->getType()->isReferenceType()) {
-  Dst.setChild(*Field, SrcFieldLoc);
-} else {
-  if (Value *Val = Env.getValue(*SrcFieldLoc))
-Env.setValue(*DstFieldLoc, *Val);
-  else
-Env.clearValue(*DstFieldLoc);
-}
-  }
-
-  for (const auto &[Name, SynthFieldLoc] : Src.synthetic_fields()) {
-if (SynthFieldLoc->getType()->isRecordType()) {
-  

[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-18 Thread via cfe-commits


@@ -35,45 +69,27 @@ void clang::dataflow::copyRecord(RecordStorageLocation &Src,
   });
   assert(compatibleTypes);
 
-  for (auto [Field, DstFieldLoc] : Dst.children()) {
-StorageLocation *SrcFieldLoc = Src.getChild(*Field);
-
-assert(Field->getType()->isReferenceType() ||
-   (SrcFieldLoc != nullptr && DstFieldLoc != nullptr));
-
-if (Field->getType()->isRecordType()) {
-  copyRecord(cast(*SrcFieldLoc),
- cast(*DstFieldLoc), Env);
-} else if (Field->getType()->isReferenceType()) {
-  Dst.setChild(*Field, SrcFieldLoc);
-} else {
-  if (Value *Val = Env.getValue(*SrcFieldLoc))
-Env.setValue(*DstFieldLoc, *Val);
-  else
-Env.clearValue(*DstFieldLoc);
-}
-  }
-
-  for (const auto &[Name, SynthFieldLoc] : Src.synthetic_fields()) {
-if (SynthFieldLoc->getType()->isRecordType()) {
-  copyRecord(*cast(SynthFieldLoc),
- cast(Dst.getSyntheticField(Name)), 
Env);
-} else {
-  if (Value *Val = Env.getValue(*SynthFieldLoc))
-Env.setValue(Dst.getSyntheticField(Name), *Val);
-  else
-Env.clearValue(Dst.getSyntheticField(Name));
-}
+  if (SrcType == DstType || (SrcDecl != nullptr && DstDecl != nullptr &&
+ SrcDecl->isDerivedFrom(DstDecl))) {
+for (auto [Field, DstFieldLoc] : Dst.children())
+  copyField(Field, Src.getChild(*Field), DstFieldLoc, Dst, Env);
+for (const auto &[Name, DstFieldLoc] : Dst.synthetic_fields())
+  copySyntheticField(DstFieldLoc->getType(), Src.getSyntheticField(Name),
+ *DstFieldLoc, Env);
+  } else {
+for (auto [Field, SrcFieldLoc] : Src.children())
+  copyField(Field, SrcFieldLoc, Dst.getChild(*Field), Dst, Env);
+for (const auto &[Name, SrcFieldLoc] : Src.synthetic_fields())
+  copySyntheticField(SrcFieldLoc->getType(), *SrcFieldLoc,

martinboehme wrote:

No, this is just a convention, but I'm not sure this is documented anywhere. 
I'll send a PR documenting this.

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-18 Thread via cfe-commits


@@ -35,45 +69,27 @@ void clang::dataflow::copyRecord(RecordStorageLocation &Src,
   });
   assert(compatibleTypes);
 
-  for (auto [Field, DstFieldLoc] : Dst.children()) {
-StorageLocation *SrcFieldLoc = Src.getChild(*Field);
-
-assert(Field->getType()->isReferenceType() ||
-   (SrcFieldLoc != nullptr && DstFieldLoc != nullptr));
-
-if (Field->getType()->isRecordType()) {
-  copyRecord(cast(*SrcFieldLoc),
- cast(*DstFieldLoc), Env);
-} else if (Field->getType()->isReferenceType()) {
-  Dst.setChild(*Field, SrcFieldLoc);
-} else {
-  if (Value *Val = Env.getValue(*SrcFieldLoc))
-Env.setValue(*DstFieldLoc, *Val);
-  else
-Env.clearValue(*DstFieldLoc);
-}
-  }
-
-  for (const auto &[Name, SynthFieldLoc] : Src.synthetic_fields()) {
-if (SynthFieldLoc->getType()->isRecordType()) {
-  copyRecord(*cast(SynthFieldLoc),
- cast(Dst.getSyntheticField(Name)), 
Env);
-} else {
-  if (Value *Val = Env.getValue(*SynthFieldLoc))
-Env.setValue(Dst.getSyntheticField(Name), *Val);
-  else
-Env.clearValue(Dst.getSyntheticField(Name));
-}
+  if (SrcType == DstType || (SrcDecl != nullptr && DstDecl != nullptr &&
+ SrcDecl->isDerivedFrom(DstDecl))) {
+for (auto [Field, DstFieldLoc] : Dst.children())

martinboehme wrote:

I'm not sure exactly what you mean here? Do you mean whether we could factor 
out the whole if block into a lambda so that we can reuse it in the else block?

That doesn't work because the two blocks aren't symmetrical: In one case, we 
iterate over the children of `Src`, in the other we iterate over the children 
of `Dst`, but in both cases, the direction of the copy is the same (source to 
destination). I don't see how this could be factored out in a way that would 
make the code clearer than it is now.

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-18 Thread via cfe-commits


@@ -14,18 +14,52 @@
 
 #define DEBUG_TYPE "dataflow"
 
-void clang::dataflow::copyRecord(RecordStorageLocation &Src,
- RecordStorageLocation &Dst, Environment &Env) 
{
+namespace clang::dataflow {
+
+static void copyField(const ValueDecl *Field, StorageLocation *SrcFieldLoc,

martinboehme wrote:

> Should we pass `Field` by reference?

Yes, done!

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-18 Thread via cfe-commits


@@ -14,18 +14,52 @@
 
 #define DEBUG_TYPE "dataflow"
 
-void clang::dataflow::copyRecord(RecordStorageLocation &Src,
- RecordStorageLocation &Dst, Environment &Env) 
{
+namespace clang::dataflow {
+
+static void copyField(const ValueDecl *Field, StorageLocation *SrcFieldLoc,

martinboehme wrote:

> Could `SrcFieldLoc` be a ptr to const? (Same below).

I think what you want to express is that the source field location is only ever 
read from, while the destination field location is written to?

This isn't really how we use `StorageLocation` constness in the framework 
today. (Maybe we should -- that's a separate discussion -- though I'm not 
convinced about how much this would buy us.) Grepping for `const 
StorageLocation` yields only a few hits in the whole framework[^1], and I think 
that's for good reason: A `StorageLocation` is essentially immutable anyway; 
all of its method are const, as are almost all of the methods in the derived 
class `RecordStorageLocation`, with the exception of `setChild()` (which is a 
bit of a special case). It's similar in this respect to a 
[flyweight](https://en.wikipedia.org/wiki/Flyweight_pattern). So the 
distinction between `const StorageLocation *` and `StorageLocation *` doesn't 
mean much in practice, and I guess we've gravitated to the less verbose 
`StorageLocation`.

We _could_ consider using `StorageLocation` constness to encode "can this 
storage location be written to" (which I think is your intent?), which would 
entail changing the storage location parameter of `setValue(const 
StorageLocation &, Value &)` to be non-const, but that would be a pretty 
far-reaching change because I think we'd need to add const in a lot of places 
in the framework.

[^1]: Notably, `setValue(const StorageLocation &, Value &)` is one of them, so 
passing a const storage location does not prevent us from writing to that 
storage location.

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-18 Thread via cfe-commits

https://github.com/martinboehme updated 
https://github.com/llvm/llvm-project/pull/85064

>From 9362adfb0d1a61cb56b897f31fd9f2ead1605383 Mon Sep 17 00:00:00 2001
From: Martin Braenne 
Date: Wed, 13 Mar 2024 12:07:24 +
Subject: [PATCH 1/2] [clang][dataflow] Model assignment to derived class from
 base.

This is a relatively rare case, but

- It's still nice to get this right,
- We can remove the special case for this in `VisitCXXOperatorCallExpr()` (that
  simply bails out), and
- With this in place, I can avoid having to add a similar special case in an
  upcoming patch.
---
 .../clang/Analysis/FlowSensitive/RecordOps.h  |  6 +-
 .../lib/Analysis/FlowSensitive/RecordOps.cpp  | 94 +++
 clang/lib/Analysis/FlowSensitive/Transfer.cpp |  9 --
 .../Analysis/FlowSensitive/RecordOpsTest.cpp  | 46 -
 .../Analysis/FlowSensitive/TransferTest.cpp   | 25 -
 5 files changed, 126 insertions(+), 54 deletions(-)

diff --git a/clang/include/clang/Analysis/FlowSensitive/RecordOps.h 
b/clang/include/clang/Analysis/FlowSensitive/RecordOps.h
index 783e53e980aa2c..8fad45fc11d81e 100644
--- a/clang/include/clang/Analysis/FlowSensitive/RecordOps.h
+++ b/clang/include/clang/Analysis/FlowSensitive/RecordOps.h
@@ -31,7 +31,11 @@ namespace dataflow {
 ///
 /// Requirements:
 ///
-///  `Src` and `Dst` must have the same canonical unqualified type.
+///  Either:
+///- `Src` and `Dest` must have the same canonical unqualified type, or
+///- The type of `Src` must be derived from `Dest`, or
+///- The type of `Dest` must be derived from `Src` (in this case, any 
fields
+///  that are only present in `Dest` are not overwritten).
 void copyRecord(RecordStorageLocation &Src, RecordStorageLocation &Dst,
 Environment &Env);
 
diff --git a/clang/lib/Analysis/FlowSensitive/RecordOps.cpp 
b/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
index da4dd6dc078515..4fc4c15a07a1ce 100644
--- a/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
+++ b/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
@@ -14,18 +14,52 @@
 
 #define DEBUG_TYPE "dataflow"
 
-void clang::dataflow::copyRecord(RecordStorageLocation &Src,
- RecordStorageLocation &Dst, Environment &Env) 
{
+namespace clang::dataflow {
+
+static void copyField(const ValueDecl *Field, StorageLocation *SrcFieldLoc,
+  StorageLocation *DstFieldLoc, RecordStorageLocation &Dst,
+  Environment &Env) {
+  assert(Field->getType()->isReferenceType() ||
+ (SrcFieldLoc != nullptr && DstFieldLoc != nullptr));
+
+  if (Field->getType()->isRecordType()) {
+copyRecord(cast(*SrcFieldLoc),
+   cast(*DstFieldLoc), Env);
+  } else if (Field->getType()->isReferenceType()) {
+Dst.setChild(*Field, SrcFieldLoc);
+  } else {
+if (Value *Val = Env.getValue(*SrcFieldLoc))
+  Env.setValue(*DstFieldLoc, *Val);
+else
+  Env.clearValue(*DstFieldLoc);
+  }
+}
+
+static void copySyntheticField(QualType FieldType, StorageLocation 
&SrcFieldLoc,
+   StorageLocation &DstFieldLoc, Environment &Env) 
{
+  if (FieldType->isRecordType()) {
+copyRecord(cast(SrcFieldLoc),
+   cast(DstFieldLoc), Env);
+  } else {
+if (Value *Val = Env.getValue(SrcFieldLoc))
+  Env.setValue(DstFieldLoc, *Val);
+else
+  Env.clearValue(DstFieldLoc);
+  }
+}
+
+void copyRecord(RecordStorageLocation &Src, RecordStorageLocation &Dst,
+Environment &Env) {
   auto SrcType = Src.getType().getCanonicalType().getUnqualifiedType();
   auto DstType = Dst.getType().getCanonicalType().getUnqualifiedType();
 
   auto SrcDecl = SrcType->getAsCXXRecordDecl();
   auto DstDecl = DstType->getAsCXXRecordDecl();
 
-  bool compatibleTypes =
+  [[maybe_unused]] bool compatibleTypes =
   SrcType == DstType ||
-  (SrcDecl && DstDecl && SrcDecl->isDerivedFrom(DstDecl));
-  (void)compatibleTypes;
+  (SrcDecl != nullptr && DstDecl != nullptr &&
+   (SrcDecl->isDerivedFrom(DstDecl) || DstDecl->isDerivedFrom(SrcDecl)));
 
   LLVM_DEBUG({
 if (!compatibleTypes) {
@@ -35,45 +69,27 @@ void clang::dataflow::copyRecord(RecordStorageLocation &Src,
   });
   assert(compatibleTypes);
 
-  for (auto [Field, DstFieldLoc] : Dst.children()) {
-StorageLocation *SrcFieldLoc = Src.getChild(*Field);
-
-assert(Field->getType()->isReferenceType() ||
-   (SrcFieldLoc != nullptr && DstFieldLoc != nullptr));
-
-if (Field->getType()->isRecordType()) {
-  copyRecord(cast(*SrcFieldLoc),
- cast(*DstFieldLoc), Env);
-} else if (Field->getType()->isReferenceType()) {
-  Dst.setChild(*Field, SrcFieldLoc);
-} else {
-  if (Value *Val = Env.getValue(*SrcFieldLoc))
-Env.setValue(*DstFieldLoc, *Val);
-  else
-Env.clearValue(*DstFieldLoc);
-}
-  }
-
-  for (const auto &[Name, SynthFieldLoc] : Src.synthetic_fields()) {
-if (SynthFieldLoc->getType()->isRecordType()) {
-  

[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-14 Thread Yitzhak Mandelbaum via cfe-commits

https://github.com/ymand approved this pull request.

modulo Gabor's comments.

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-13 Thread Gábor Horváth via cfe-commits


@@ -14,18 +14,52 @@
 
 #define DEBUG_TYPE "dataflow"
 
-void clang::dataflow::copyRecord(RecordStorageLocation &Src,
- RecordStorageLocation &Dst, Environment &Env) 
{
+namespace clang::dataflow {
+
+static void copyField(const ValueDecl *Field, StorageLocation *SrcFieldLoc,

Xazax-hun wrote:

Should we pass `Field` by reference?

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-13 Thread Gábor Horváth via cfe-commits


@@ -35,45 +69,27 @@ void clang::dataflow::copyRecord(RecordStorageLocation &Src,
   });
   assert(compatibleTypes);
 
-  for (auto [Field, DstFieldLoc] : Dst.children()) {
-StorageLocation *SrcFieldLoc = Src.getChild(*Field);
-
-assert(Field->getType()->isReferenceType() ||
-   (SrcFieldLoc != nullptr && DstFieldLoc != nullptr));
-
-if (Field->getType()->isRecordType()) {
-  copyRecord(cast(*SrcFieldLoc),
- cast(*DstFieldLoc), Env);
-} else if (Field->getType()->isReferenceType()) {
-  Dst.setChild(*Field, SrcFieldLoc);
-} else {
-  if (Value *Val = Env.getValue(*SrcFieldLoc))
-Env.setValue(*DstFieldLoc, *Val);
-  else
-Env.clearValue(*DstFieldLoc);
-}
-  }
-
-  for (const auto &[Name, SynthFieldLoc] : Src.synthetic_fields()) {
-if (SynthFieldLoc->getType()->isRecordType()) {
-  copyRecord(*cast(SynthFieldLoc),
- cast(Dst.getSyntheticField(Name)), 
Env);
-} else {
-  if (Value *Val = Env.getValue(*SynthFieldLoc))
-Env.setValue(Dst.getSyntheticField(Name), *Val);
-  else
-Env.clearValue(Dst.getSyntheticField(Name));
-}
+  if (SrcType == DstType || (SrcDecl != nullptr && DstDecl != nullptr &&
+ SrcDecl->isDerivedFrom(DstDecl))) {
+for (auto [Field, DstFieldLoc] : Dst.children())
+  copyField(Field, Src.getChild(*Field), DstFieldLoc, Dst, Env);
+for (const auto &[Name, DstFieldLoc] : Dst.synthetic_fields())
+  copySyntheticField(DstFieldLoc->getType(), Src.getSyntheticField(Name),
+ *DstFieldLoc, Env);
+  } else {
+for (auto [Field, SrcFieldLoc] : Src.children())
+  copyField(Field, SrcFieldLoc, Dst.getChild(*Field), Dst, Env);
+for (const auto &[Name, SrcFieldLoc] : Src.synthetic_fields())
+  copySyntheticField(SrcFieldLoc->getType(), *SrcFieldLoc,

Xazax-hun wrote:

Do we actually ensure that synthetic fields have the expected behavior (e.g., 
they are inherited)?

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-13 Thread Gábor Horváth via cfe-commits


@@ -35,45 +69,27 @@ void clang::dataflow::copyRecord(RecordStorageLocation &Src,
   });
   assert(compatibleTypes);
 
-  for (auto [Field, DstFieldLoc] : Dst.children()) {
-StorageLocation *SrcFieldLoc = Src.getChild(*Field);
-
-assert(Field->getType()->isReferenceType() ||
-   (SrcFieldLoc != nullptr && DstFieldLoc != nullptr));
-
-if (Field->getType()->isRecordType()) {
-  copyRecord(cast(*SrcFieldLoc),
- cast(*DstFieldLoc), Env);
-} else if (Field->getType()->isReferenceType()) {
-  Dst.setChild(*Field, SrcFieldLoc);
-} else {
-  if (Value *Val = Env.getValue(*SrcFieldLoc))
-Env.setValue(*DstFieldLoc, *Val);
-  else
-Env.clearValue(*DstFieldLoc);
-}
-  }
-
-  for (const auto &[Name, SynthFieldLoc] : Src.synthetic_fields()) {
-if (SynthFieldLoc->getType()->isRecordType()) {
-  copyRecord(*cast(SynthFieldLoc),
- cast(Dst.getSyntheticField(Name)), 
Env);
-} else {
-  if (Value *Val = Env.getValue(*SynthFieldLoc))
-Env.setValue(Dst.getSyntheticField(Name), *Val);
-  else
-Env.clearValue(Dst.getSyntheticField(Name));
-}
+  if (SrcType == DstType || (SrcDecl != nullptr && DstDecl != nullptr &&
+ SrcDecl->isDerivedFrom(DstDecl))) {
+for (auto [Field, DstFieldLoc] : Dst.children())

Xazax-hun wrote:

Alternatively, maybe this could be a lambda called twice (once with Dst, once 
with Src). That being said, if it needs to capture too many things, I am fine 
with it as is. 

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-13 Thread Gábor Horváth via cfe-commits


@@ -14,18 +14,52 @@
 
 #define DEBUG_TYPE "dataflow"
 
-void clang::dataflow::copyRecord(RecordStorageLocation &Src,
- RecordStorageLocation &Dst, Environment &Env) 
{
+namespace clang::dataflow {
+
+static void copyField(const ValueDecl *Field, StorageLocation *SrcFieldLoc,

Xazax-hun wrote:

Could `SrcFieldLoc` be a ptr to const? (Same below).

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-13 Thread Gábor Horváth via cfe-commits


@@ -212,8 +212,23 @@ TEST(TransferTest, CopyRecordFromDerivedToBase) {
   // [[p]]
 }
   )";
+  auto SyntheticFieldCallback = [](QualType Ty) -> llvm::StringMap {
+CXXRecordDecl *ADecl = nullptr;
+if (Ty.getAsString() == "A")
+  ADecl = Ty->getAsCXXRecordDecl();
+else if (Ty.getAsString() == "B")
+  ADecl = Ty->getAsCXXRecordDecl()
+  ->bases_begin()
+  ->getType()
+  ->getAsCXXRecordDecl();
+else
+  return {};
+QualType IntTy = getFieldNamed(ADecl, "i")->getType();
+return {{"synth_int", IntTy}};
+  };
+  // Copy derived to base class.

Xazax-hun wrote:

What does this comment refer to?

https://github.com/llvm/llvm-project/pull/85064
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-13 Thread via cfe-commits

llvmbot wrote:




@llvm/pr-subscribers-clang

Author: None (martinboehme)


Changes

This is a relatively rare case, but

- It's still nice to get this right,
- We can remove the special case for this in `VisitCXXOperatorCallExpr()` (that
  simply bails out), and
- With this in place, I can avoid having to add a similar special case in an
  upcoming patch.


---
Full diff: https://github.com/llvm/llvm-project/pull/85064.diff


5 Files Affected:

- (modified) clang/include/clang/Analysis/FlowSensitive/RecordOps.h (+5-1) 
- (modified) clang/lib/Analysis/FlowSensitive/RecordOps.cpp (+56-38) 
- (modified) clang/lib/Analysis/FlowSensitive/Transfer.cpp (-9) 
- (modified) clang/unittests/Analysis/FlowSensitive/RecordOpsTest.cpp (+44-2) 
- (modified) clang/unittests/Analysis/FlowSensitive/TransferTest.cpp (+21-4) 


``diff
diff --git a/clang/include/clang/Analysis/FlowSensitive/RecordOps.h 
b/clang/include/clang/Analysis/FlowSensitive/RecordOps.h
index 783e53e980aa2c..8fad45fc11d81e 100644
--- a/clang/include/clang/Analysis/FlowSensitive/RecordOps.h
+++ b/clang/include/clang/Analysis/FlowSensitive/RecordOps.h
@@ -31,7 +31,11 @@ namespace dataflow {
 ///
 /// Requirements:
 ///
-///  `Src` and `Dst` must have the same canonical unqualified type.
+///  Either:
+///- `Src` and `Dest` must have the same canonical unqualified type, or
+///- The type of `Src` must be derived from `Dest`, or
+///- The type of `Dest` must be derived from `Src` (in this case, any 
fields
+///  that are only present in `Dest` are not overwritten).
 void copyRecord(RecordStorageLocation &Src, RecordStorageLocation &Dst,
 Environment &Env);
 
diff --git a/clang/lib/Analysis/FlowSensitive/RecordOps.cpp 
b/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
index da4dd6dc078515..4fc4c15a07a1ce 100644
--- a/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
+++ b/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
@@ -14,18 +14,52 @@
 
 #define DEBUG_TYPE "dataflow"
 
-void clang::dataflow::copyRecord(RecordStorageLocation &Src,
- RecordStorageLocation &Dst, Environment &Env) 
{
+namespace clang::dataflow {
+
+static void copyField(const ValueDecl *Field, StorageLocation *SrcFieldLoc,
+  StorageLocation *DstFieldLoc, RecordStorageLocation &Dst,
+  Environment &Env) {
+  assert(Field->getType()->isReferenceType() ||
+ (SrcFieldLoc != nullptr && DstFieldLoc != nullptr));
+
+  if (Field->getType()->isRecordType()) {
+copyRecord(cast(*SrcFieldLoc),
+   cast(*DstFieldLoc), Env);
+  } else if (Field->getType()->isReferenceType()) {
+Dst.setChild(*Field, SrcFieldLoc);
+  } else {
+if (Value *Val = Env.getValue(*SrcFieldLoc))
+  Env.setValue(*DstFieldLoc, *Val);
+else
+  Env.clearValue(*DstFieldLoc);
+  }
+}
+
+static void copySyntheticField(QualType FieldType, StorageLocation 
&SrcFieldLoc,
+   StorageLocation &DstFieldLoc, Environment &Env) 
{
+  if (FieldType->isRecordType()) {
+copyRecord(cast(SrcFieldLoc),
+   cast(DstFieldLoc), Env);
+  } else {
+if (Value *Val = Env.getValue(SrcFieldLoc))
+  Env.setValue(DstFieldLoc, *Val);
+else
+  Env.clearValue(DstFieldLoc);
+  }
+}
+
+void copyRecord(RecordStorageLocation &Src, RecordStorageLocation &Dst,
+Environment &Env) {
   auto SrcType = Src.getType().getCanonicalType().getUnqualifiedType();
   auto DstType = Dst.getType().getCanonicalType().getUnqualifiedType();
 
   auto SrcDecl = SrcType->getAsCXXRecordDecl();
   auto DstDecl = DstType->getAsCXXRecordDecl();
 
-  bool compatibleTypes =
+  [[maybe_unused]] bool compatibleTypes =
   SrcType == DstType ||
-  (SrcDecl && DstDecl && SrcDecl->isDerivedFrom(DstDecl));
-  (void)compatibleTypes;
+  (SrcDecl != nullptr && DstDecl != nullptr &&
+   (SrcDecl->isDerivedFrom(DstDecl) || DstDecl->isDerivedFrom(SrcDecl)));
 
   LLVM_DEBUG({
 if (!compatibleTypes) {
@@ -35,45 +69,27 @@ void clang::dataflow::copyRecord(RecordStorageLocation &Src,
   });
   assert(compatibleTypes);
 
-  for (auto [Field, DstFieldLoc] : Dst.children()) {
-StorageLocation *SrcFieldLoc = Src.getChild(*Field);
-
-assert(Field->getType()->isReferenceType() ||
-   (SrcFieldLoc != nullptr && DstFieldLoc != nullptr));
-
-if (Field->getType()->isRecordType()) {
-  copyRecord(cast(*SrcFieldLoc),
- cast(*DstFieldLoc), Env);
-} else if (Field->getType()->isReferenceType()) {
-  Dst.setChild(*Field, SrcFieldLoc);
-} else {
-  if (Value *Val = Env.getValue(*SrcFieldLoc))
-Env.setValue(*DstFieldLoc, *Val);
-  else
-Env.clearValue(*DstFieldLoc);
-}
-  }
-
-  for (const auto &[Name, SynthFieldLoc] : Src.synthetic_fields()) {
-if (SynthFieldLoc->getType()->isRecordType()) {
-  copyRecord(*cast(SynthFieldLoc),
- cast(Dst.getSyntheticField(Name)), 
Env);
-} else {

[clang] [clang][dataflow] Model assignment to derived class from base. (PR #85064)

2024-03-13 Thread via cfe-commits

https://github.com/martinboehme created 
https://github.com/llvm/llvm-project/pull/85064

This is a relatively rare case, but

- It's still nice to get this right,
- We can remove the special case for this in `VisitCXXOperatorCallExpr()` (that
  simply bails out), and
- With this in place, I can avoid having to add a similar special case in an
  upcoming patch.


>From 9362adfb0d1a61cb56b897f31fd9f2ead1605383 Mon Sep 17 00:00:00 2001
From: Martin Braenne 
Date: Wed, 13 Mar 2024 12:07:24 +
Subject: [PATCH] [clang][dataflow] Model assignment to derived class from
 base.

This is a relatively rare case, but

- It's still nice to get this right,
- We can remove the special case for this in `VisitCXXOperatorCallExpr()` (that
  simply bails out), and
- With this in place, I can avoid having to add a similar special case in an
  upcoming patch.
---
 .../clang/Analysis/FlowSensitive/RecordOps.h  |  6 +-
 .../lib/Analysis/FlowSensitive/RecordOps.cpp  | 94 +++
 clang/lib/Analysis/FlowSensitive/Transfer.cpp |  9 --
 .../Analysis/FlowSensitive/RecordOpsTest.cpp  | 46 -
 .../Analysis/FlowSensitive/TransferTest.cpp   | 25 -
 5 files changed, 126 insertions(+), 54 deletions(-)

diff --git a/clang/include/clang/Analysis/FlowSensitive/RecordOps.h 
b/clang/include/clang/Analysis/FlowSensitive/RecordOps.h
index 783e53e980aa2c..8fad45fc11d81e 100644
--- a/clang/include/clang/Analysis/FlowSensitive/RecordOps.h
+++ b/clang/include/clang/Analysis/FlowSensitive/RecordOps.h
@@ -31,7 +31,11 @@ namespace dataflow {
 ///
 /// Requirements:
 ///
-///  `Src` and `Dst` must have the same canonical unqualified type.
+///  Either:
+///- `Src` and `Dest` must have the same canonical unqualified type, or
+///- The type of `Src` must be derived from `Dest`, or
+///- The type of `Dest` must be derived from `Src` (in this case, any 
fields
+///  that are only present in `Dest` are not overwritten).
 void copyRecord(RecordStorageLocation &Src, RecordStorageLocation &Dst,
 Environment &Env);
 
diff --git a/clang/lib/Analysis/FlowSensitive/RecordOps.cpp 
b/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
index da4dd6dc078515..4fc4c15a07a1ce 100644
--- a/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
+++ b/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
@@ -14,18 +14,52 @@
 
 #define DEBUG_TYPE "dataflow"
 
-void clang::dataflow::copyRecord(RecordStorageLocation &Src,
- RecordStorageLocation &Dst, Environment &Env) 
{
+namespace clang::dataflow {
+
+static void copyField(const ValueDecl *Field, StorageLocation *SrcFieldLoc,
+  StorageLocation *DstFieldLoc, RecordStorageLocation &Dst,
+  Environment &Env) {
+  assert(Field->getType()->isReferenceType() ||
+ (SrcFieldLoc != nullptr && DstFieldLoc != nullptr));
+
+  if (Field->getType()->isRecordType()) {
+copyRecord(cast(*SrcFieldLoc),
+   cast(*DstFieldLoc), Env);
+  } else if (Field->getType()->isReferenceType()) {
+Dst.setChild(*Field, SrcFieldLoc);
+  } else {
+if (Value *Val = Env.getValue(*SrcFieldLoc))
+  Env.setValue(*DstFieldLoc, *Val);
+else
+  Env.clearValue(*DstFieldLoc);
+  }
+}
+
+static void copySyntheticField(QualType FieldType, StorageLocation 
&SrcFieldLoc,
+   StorageLocation &DstFieldLoc, Environment &Env) 
{
+  if (FieldType->isRecordType()) {
+copyRecord(cast(SrcFieldLoc),
+   cast(DstFieldLoc), Env);
+  } else {
+if (Value *Val = Env.getValue(SrcFieldLoc))
+  Env.setValue(DstFieldLoc, *Val);
+else
+  Env.clearValue(DstFieldLoc);
+  }
+}
+
+void copyRecord(RecordStorageLocation &Src, RecordStorageLocation &Dst,
+Environment &Env) {
   auto SrcType = Src.getType().getCanonicalType().getUnqualifiedType();
   auto DstType = Dst.getType().getCanonicalType().getUnqualifiedType();
 
   auto SrcDecl = SrcType->getAsCXXRecordDecl();
   auto DstDecl = DstType->getAsCXXRecordDecl();
 
-  bool compatibleTypes =
+  [[maybe_unused]] bool compatibleTypes =
   SrcType == DstType ||
-  (SrcDecl && DstDecl && SrcDecl->isDerivedFrom(DstDecl));
-  (void)compatibleTypes;
+  (SrcDecl != nullptr && DstDecl != nullptr &&
+   (SrcDecl->isDerivedFrom(DstDecl) || DstDecl->isDerivedFrom(SrcDecl)));
 
   LLVM_DEBUG({
 if (!compatibleTypes) {
@@ -35,45 +69,27 @@ void clang::dataflow::copyRecord(RecordStorageLocation &Src,
   });
   assert(compatibleTypes);
 
-  for (auto [Field, DstFieldLoc] : Dst.children()) {
-StorageLocation *SrcFieldLoc = Src.getChild(*Field);
-
-assert(Field->getType()->isReferenceType() ||
-   (SrcFieldLoc != nullptr && DstFieldLoc != nullptr));
-
-if (Field->getType()->isRecordType()) {
-  copyRecord(cast(*SrcFieldLoc),
- cast(*DstFieldLoc), Env);
-} else if (Field->getType()->isReferenceType()) {
-  Dst.setChild(*Field, SrcFieldLoc);
-} else {
-  if (Value *