[clang] Reland [clang][dataflow] Transfer more cast expressions. (PR #157535)
Samira Bakon via cfe-commits
cfe-commits at lists.llvm.org
Tue Sep 9 11:12:52 PDT 2025
https://github.com/bazuzi updated https://github.com/llvm/llvm-project/pull/157535
>From 4779770aab880315d359005a5cacd4c4976d8649 Mon Sep 17 00:00:00 2001
From: Samira Bakon <bazuzi at google.com>
Date: Mon, 8 Sep 2025 15:08:52 -0400
Subject: [PATCH 1/3] Revert "Revert "[clang][dataflow] Transfer more cast
expressions." (#157148)"
This reverts commit 4c29a600fa34d0c1cabf4ffcf081f2a00b09fddd.
---
.../Analysis/FlowSensitive/StorageLocation.h | 11 +
clang/lib/Analysis/FlowSensitive/Transfer.cpp | 71 ++++-
.../Analysis/FlowSensitive/TransferTest.cpp | 280 +++++++++++++++++-
3 files changed, 349 insertions(+), 13 deletions(-)
diff --git a/clang/include/clang/Analysis/FlowSensitive/StorageLocation.h b/clang/include/clang/Analysis/FlowSensitive/StorageLocation.h
index 8fcc6a44027a0..534b9a017d8f0 100644
--- a/clang/include/clang/Analysis/FlowSensitive/StorageLocation.h
+++ b/clang/include/clang/Analysis/FlowSensitive/StorageLocation.h
@@ -17,6 +17,7 @@
#include "clang/AST/Decl.h"
#include "clang/AST/Type.h"
#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Debug.h"
#include <cassert>
@@ -152,6 +153,11 @@ class RecordStorageLocation final : public StorageLocation {
return {SyntheticFields.begin(), SyntheticFields.end()};
}
+ /// Add a synthetic field, if none by that name is already present.
+ void addSyntheticField(llvm::StringRef Name, StorageLocation &Loc) {
+ SyntheticFields.insert({Name, &Loc});
+ }
+
/// Changes the child storage location for a field `D` of reference type.
/// All other fields cannot change their storage location and always retain
/// the storage location passed to the `RecordStorageLocation` constructor.
@@ -164,6 +170,11 @@ class RecordStorageLocation final : public StorageLocation {
Children[&D] = Loc;
}
+ /// Add a child storage location for a field `D`, if not already present.
+ void addChild(const ValueDecl &D, StorageLocation *Loc) {
+ Children.insert({&D, Loc});
+ }
+
llvm::iterator_range<FieldToLoc::const_iterator> children() const {
return {Children.begin(), Children.end()};
}
diff --git a/clang/lib/Analysis/FlowSensitive/Transfer.cpp b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
index 86a816e2e406c..23a6de45e18b1 100644
--- a/clang/lib/Analysis/FlowSensitive/Transfer.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
@@ -20,14 +20,17 @@
#include "clang/AST/OperationKinds.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/StmtVisitor.h"
+#include "clang/AST/Type.h"
#include "clang/Analysis/FlowSensitive/ASTOps.h"
#include "clang/Analysis/FlowSensitive/AdornedCFG.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/Builtins.h"
+#include "clang/Basic/LLVM.h"
#include "clang/Basic/OperatorKinds.h"
#include "llvm/Support/Casting.h"
#include <assert.h>
@@ -287,7 +290,7 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
}
}
- void VisitImplicitCastExpr(const ImplicitCastExpr *S) {
+ void VisitCastExpr(const CastExpr *S) {
const Expr *SubExpr = S->getSubExpr();
assert(SubExpr != nullptr);
@@ -317,6 +320,60 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
break;
}
+ case CK_BaseToDerived: {
+ // This is a cast of (single-layer) pointer or reference to a record type.
+ // We should now model the fields for the derived type.
+
+ // Get the RecordStorageLocation for the record object underneath.
+ RecordStorageLocation *Loc = nullptr;
+ if (S->getType()->isPointerType()) {
+ auto *PV = Env.get<PointerValue>(*SubExpr);
+ assert(PV != nullptr);
+ if (PV == nullptr)
+ break;
+ Loc = cast<RecordStorageLocation>(&PV->getPointeeLoc());
+ } else {
+ assert(S->getType()->isRecordType());
+ if (SubExpr->isGLValue()) {
+ Loc = Env.get<RecordStorageLocation>(*SubExpr);
+ } else {
+ Loc = &Env.getResultObjectLocation(*SubExpr);
+ }
+ }
+ if (!Loc) {
+ // Nowhere to add children or propagate from, so we're done.
+ break;
+ }
+
+ // Get the derived record type underneath the reference or pointer.
+ QualType Derived = S->getType().getNonReferenceType();
+ if (Derived->isPointerType()) {
+ Derived = Derived->getPointeeType();
+ }
+
+ // Add children to the storage location for fields (including synthetic
+ // fields) of the derived type and initialize their values.
+ for (const FieldDecl *Field :
+ Env.getDataflowAnalysisContext().getModeledFields(Derived)) {
+ assert(Field != nullptr);
+ QualType FieldType = Field->getType();
+ if (FieldType->isReferenceType()) {
+ Loc->addChild(*Field, nullptr);
+ } else {
+ Loc->addChild(*Field, &Env.createStorageLocation(FieldType));
+ }
+
+ for (const auto &Entry :
+ Env.getDataflowAnalysisContext().getSyntheticFields(Derived)) {
+ Loc->addSyntheticField(Entry.getKey(),
+ Env.createStorageLocation(Entry.getValue()));
+ }
+ }
+ Env.initializeFieldsWithValues(*Loc, Derived);
+
+ // Fall through to propagate SubExpr's StorageLocation to the CastExpr.
+ [[fallthrough]];
+ }
case CK_IntegralCast:
// FIXME: This cast creates a new integral value from the
// subexpression. But, because we don't model integers, we don't
@@ -324,10 +381,9 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
// modeling is added, then update this code to create a fresh location and
// value.
case CK_UncheckedDerivedToBase:
+ case CK_DerivedToBase:
case CK_ConstructorConversion:
case CK_UserDefinedConversion:
- // FIXME: Add tests that excercise CK_UncheckedDerivedToBase,
- // CK_ConstructorConversion, and CK_UserDefinedConversion.
case CK_NoOp: {
// FIXME: Consider making `Environment::getStorageLocation` skip noop
// expressions (this and other similar expressions in the file) instead
@@ -684,15 +740,6 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
propagateValue(*SubExpr, *S, Env);
}
- void VisitCXXStaticCastExpr(const CXXStaticCastExpr *S) {
- if (S->getCastKind() == CK_NoOp) {
- const Expr *SubExpr = S->getSubExpr();
- assert(SubExpr != nullptr);
-
- propagateValueOrStorageLocation(*SubExpr, *S, Env);
- }
- }
-
void VisitConditionalOperator(const ConditionalOperator *S) {
const Environment *TrueEnv = StmtToEnv.getEnvironment(*S->getTrueExpr());
const Environment *FalseEnv = StmtToEnv.getEnvironment(*S->getFalseExpr());
diff --git a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
index 214aaee9f97f6..96e759e73c154 100644
--- a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
@@ -9,17 +9,25 @@
#include "TestingSupport.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/AST/OperationKinds.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/NoopAnalysis.h"
+#include "clang/Analysis/FlowSensitive/NoopLattice.h"
#include "clang/Analysis/FlowSensitive/RecordOps.h"
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
#include "clang/Analysis/FlowSensitive/Value.h"
#include "clang/Basic/LangStandard.h"
#include "clang/Testing/TestAST.h"
#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Casting.h"
#include "llvm/Testing/Support/Error.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
@@ -27,6 +35,7 @@
#include <string>
#include <string_view>
#include <utility>
+#include <vector>
namespace clang {
namespace dataflow {
@@ -3541,7 +3550,7 @@ TEST(TransferTest, ResultObjectLocationDontVisitUnevaluatedContexts) {
testFunction(Code, "noexceptTarget");
}
-TEST(TransferTest, StaticCast) {
+TEST(TransferTest, StaticCastNoOp) {
std::string Code = R"(
void target(int Foo) {
int Bar = static_cast<int>(Foo);
@@ -3561,6 +3570,13 @@ TEST(TransferTest, StaticCast) {
const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
ASSERT_THAT(BarDecl, NotNull());
+ const auto *Cast = ast_matchers::selectFirst<CXXStaticCastExpr>(
+ "cast",
+ ast_matchers::match(ast_matchers::cxxStaticCastExpr().bind("cast"),
+ ASTCtx));
+ ASSERT_THAT(Cast, NotNull());
+ ASSERT_EQ(Cast->getCastKind(), CK_NoOp);
+
const auto *FooVal = Env.getValue(*FooDecl);
const auto *BarVal = Env.getValue(*BarDecl);
EXPECT_TRUE(isa<IntegerValue>(FooVal));
@@ -3569,6 +3585,268 @@ TEST(TransferTest, StaticCast) {
});
}
+TEST(TransferTest, StaticCastBaseToDerived) {
+ std::string Code = R"cc(
+ struct Base {
+ char C;
+ };
+ struct Intermediate : public Base {
+ bool B;
+ };
+ struct Derived : public Intermediate {
+ int I;
+ };
+ Base& getBaseRef();
+ void target(Base* BPtr) {
+ Derived* DPtr = static_cast<Derived*>(BPtr);
+ DPtr->C;
+ DPtr->B;
+ DPtr->I;
+ Derived& DRef = static_cast<Derived&>(*BPtr);
+ DRef.C;
+ DRef.B;
+ DRef.I;
+ Derived& DRefFromFunc = static_cast<Derived&>(getBaseRef());
+ DRefFromFunc.C;
+ DRefFromFunc.B;
+ DRefFromFunc.I;
+ // [[p]]
+ }
+ )cc";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
+ const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
+
+ const ValueDecl *BPtrDecl = findValueDecl(ASTCtx, "BPtr");
+ ASSERT_THAT(BPtrDecl, NotNull());
+
+ const ValueDecl *DPtrDecl = findValueDecl(ASTCtx, "DPtr");
+ ASSERT_THAT(DPtrDecl, NotNull());
+
+ const ValueDecl *DRefDecl = findValueDecl(ASTCtx, "DRef");
+ ASSERT_THAT(DRefDecl, NotNull());
+
+ const ValueDecl *DRefFromFuncDecl =
+ findValueDecl(ASTCtx, "DRefFromFunc");
+ ASSERT_THAT(DRefFromFuncDecl, NotNull());
+
+ const auto *Cast = ast_matchers::selectFirst<CXXStaticCastExpr>(
+ "cast",
+ ast_matchers::match(ast_matchers::cxxStaticCastExpr().bind("cast"),
+ ASTCtx));
+ ASSERT_THAT(Cast, NotNull());
+ ASSERT_EQ(Cast->getCastKind(), CK_BaseToDerived);
+
+ EXPECT_EQ(Env.getValue(*BPtrDecl), Env.getValue(*DPtrDecl));
+ EXPECT_EQ(&Env.get<PointerValue>(*BPtrDecl)->getPointeeLoc(),
+ Env.getStorageLocation(*DRefDecl));
+ // For DRefFromFunc, not crashing when analyzing the field accesses is
+ // enough.
+ });
+}
+
+TEST(TransferTest, ExplicitDerivedToBaseCast) {
+ std::string Code = R"cc(
+ struct Base {};
+ struct Derived : public Base {};
+ void target(Derived D) {
+ (Base*)&D;
+ // [[p]]
+ }
+)cc";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
+ const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
+
+ auto *Cast = ast_matchers::selectFirst<ImplicitCastExpr>(
+ "cast", ast_matchers::match(
+ ast_matchers::implicitCastExpr().bind("cast"), ASTCtx));
+ ASSERT_THAT(Cast, NotNull());
+ ASSERT_EQ(Cast->getCastKind(), CK_DerivedToBase);
+
+ auto *AddressOf = ast_matchers::selectFirst<UnaryOperator>(
+ "addressof",
+ ast_matchers::match(ast_matchers::unaryOperator().bind("addressof"),
+ ASTCtx));
+ ASSERT_THAT(AddressOf, NotNull());
+ ASSERT_EQ(AddressOf->getOpcode(), UO_AddrOf);
+
+ EXPECT_EQ(Env.getValue(*Cast), Env.getValue(*AddressOf));
+ });
+}
+
+TEST(TransferTest, ConstructorConversion) {
+ std::string Code = R"cc(
+ struct Base {};
+ struct Derived : public Base {};
+ void target(Derived D) {
+ Base B = (Base)D;
+ // [[p]]
+ }
+)cc";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
+ const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
+
+ auto *Cast = ast_matchers::selectFirst<CStyleCastExpr>(
+ "cast", ast_matchers::match(
+ ast_matchers::cStyleCastExpr().bind("cast"), ASTCtx));
+ ASSERT_THAT(Cast, NotNull());
+ ASSERT_EQ(Cast->getCastKind(), CK_ConstructorConversion);
+
+ auto &DLoc = getLocForDecl<StorageLocation>(ASTCtx, Env, "D");
+ auto &BLoc = getLocForDecl<StorageLocation>(ASTCtx, Env, "B");
+ EXPECT_NE(&BLoc, &DLoc);
+ });
+}
+
+TEST(TransferTest, UserDefinedConversion) {
+ std::string Code = R"cc(
+ struct To {};
+ struct From {
+ operator To();
+ };
+ void target(From F) {
+ To T = (To)F;
+ // [[p]]
+ }
+)cc";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
+ const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
+
+ auto *Cast = ast_matchers::selectFirst<ImplicitCastExpr>(
+ "cast", ast_matchers::match(
+ ast_matchers::implicitCastExpr().bind("cast"), ASTCtx));
+ ASSERT_THAT(Cast, NotNull());
+ ASSERT_EQ(Cast->getCastKind(), CK_UserDefinedConversion);
+
+ auto &FLoc = getLocForDecl<StorageLocation>(ASTCtx, Env, "F");
+ auto &TLoc = getLocForDecl<StorageLocation>(ASTCtx, Env, "T");
+ EXPECT_NE(&TLoc, &FLoc);
+ });
+}
+
+TEST(TransferTest, ImplicitUncheckedDerivedToBaseCast) {
+ std::string Code = R"cc(
+ struct Base {
+ void method();
+ };
+ struct Derived : public Base {};
+ void target(Derived D) {
+ D.method();
+ // [[p]]
+ }
+)cc";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
+ const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
+
+ auto *Cast = ast_matchers::selectFirst<ImplicitCastExpr>(
+ "cast", ast_matchers::match(
+ ast_matchers::implicitCastExpr().bind("cast"), ASTCtx));
+ ASSERT_THAT(Cast, NotNull());
+ ASSERT_EQ(Cast->getCastKind(), CK_UncheckedDerivedToBase);
+
+ auto &DLoc = getLocForDecl<StorageLocation>(ASTCtx, Env, "D");
+ EXPECT_EQ(Env.getStorageLocation(*Cast), &DLoc);
+ });
+}
+
+TEST(TransferTest, ImplicitDerivedToBaseCast) {
+ std::string Code = R"cc(
+ struct Base {};
+ struct Derived : public Base {};
+ void target() {
+ Base* B = new Derived();
+ // [[p]]
+ }
+)cc";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
+ const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
+
+ auto *Cast = ast_matchers::selectFirst<ImplicitCastExpr>(
+ "cast", ast_matchers::match(
+ ast_matchers::implicitCastExpr().bind("cast"), ASTCtx));
+ ASSERT_THAT(Cast, NotNull());
+ ASSERT_EQ(Cast->getCastKind(), CK_DerivedToBase);
+
+ auto *New = ast_matchers::selectFirst<CXXNewExpr>(
+ "new", ast_matchers::match(ast_matchers::cxxNewExpr().bind("new"),
+ ASTCtx));
+ ASSERT_THAT(New, NotNull());
+
+ EXPECT_EQ(Env.getValue(*Cast), Env.getValue(*New));
+ });
+}
+
+TEST(TransferTest, ReinterpretCast) {
+ std::string Code = R"cc(
+ struct S {
+ int I;
+ };
+
+ void target(unsigned char* Bytes) {
+ S& SRef = reinterpret_cast<S&>(Bytes);
+ SRef.I;
+ S* SPtr = reinterpret_cast<S*>(Bytes);
+ SPtr->I;
+ // [[p]]
+ }
+ )cc";
+ runDataflow(Code, [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>>
+ &Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
+ const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
+ const ValueDecl *I = findValueDecl(ASTCtx, "I");
+ ASSERT_THAT(I, NotNull());
+
+ // No particular knowledge of I's value is modeled, but for both casts,
+ // the fields of S are modeled.
+
+ {
+ auto &Loc = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "SRef");
+ std::vector<const ValueDecl *> Children;
+ for (const auto &Entry : Loc.children()) {
+ Children.push_back(Entry.getFirst());
+ }
+
+ EXPECT_THAT(Children, UnorderedElementsAre(I));
+ }
+
+ {
+ auto &Loc = cast<RecordStorageLocation>(
+ getValueForDecl<PointerValue>(ASTCtx, Env, "SPtr").getPointeeLoc());
+ std::vector<const ValueDecl *> Children;
+ for (const auto &Entry : Loc.children()) {
+ Children.push_back(Entry.getFirst());
+ }
+
+ EXPECT_THAT(Children, UnorderedElementsAre(I));
+ }
+ });
+}
+
TEST(TransferTest, IntegralCast) {
std::string Code = R"(
void target(int Foo) {
>From 3941927cb9ad628d6d4d59e294557933c0e15039 Mon Sep 17 00:00:00 2001
From: Samira Bakon <bazuzi at google.com>
Date: Mon, 8 Sep 2025 15:32:20 -0400
Subject: [PATCH 2/3] Fix copyRecord usage for transfer of CXXConstructExpr for
base class initializers.
---
.../clang/Analysis/FlowSensitive/RecordOps.h | 6 +-
.../lib/Analysis/FlowSensitive/RecordOps.cpp | 31 ++++++--
clang/lib/Analysis/FlowSensitive/Transfer.cpp | 10 ++-
.../Analysis/FlowSensitive/RecordOpsTest.cpp | 70 ++++++++++++++++++-
.../Analysis/FlowSensitive/TransferTest.cpp | 34 +++++++++
5 files changed, 142 insertions(+), 9 deletions(-)
diff --git a/clang/include/clang/Analysis/FlowSensitive/RecordOps.h b/clang/include/clang/Analysis/FlowSensitive/RecordOps.h
index 8fad45fc11d81..91204c071af56 100644
--- a/clang/include/clang/Analysis/FlowSensitive/RecordOps.h
+++ b/clang/include/clang/Analysis/FlowSensitive/RecordOps.h
@@ -13,6 +13,7 @@
#ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_RECORDOPS_H
#define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_RECORDOPS_H
+#include "clang/AST/Type.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
@@ -36,8 +37,11 @@ namespace dataflow {
/// - 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).
+/// - The types of `Dest` and `Src` are both derived from a non-null
+/// `TypeToCopy` (in this case, only fields present in `TypeToCopy` are
+/// overwritten).
void copyRecord(RecordStorageLocation &Src, RecordStorageLocation &Dst,
- Environment &Env);
+ Environment &Env, QualType TypeToCopy = QualType());
/// Returns whether the records `Loc1` and `Loc2` are equal.
///
diff --git a/clang/lib/Analysis/FlowSensitive/RecordOps.cpp b/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
index b8401230a83d4..71b2aa9ff2ec4 100644
--- a/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
+++ b/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
@@ -11,6 +11,9 @@
//===----------------------------------------------------------------------===//
#include "clang/Analysis/FlowSensitive/RecordOps.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/Type.h"
#define DEBUG_TYPE "dataflow"
@@ -49,25 +52,30 @@ static void copySyntheticField(QualType FieldType, StorageLocation &SrcFieldLoc,
}
void copyRecord(RecordStorageLocation &Src, RecordStorageLocation &Dst,
- Environment &Env) {
+ Environment &Env, const QualType TypeToCopy) {
auto SrcType = Src.getType().getCanonicalType().getUnqualifiedType();
auto DstType = Dst.getType().getCanonicalType().getUnqualifiedType();
auto SrcDecl = SrcType->getAsCXXRecordDecl();
auto DstDecl = DstType->getAsCXXRecordDecl();
- [[maybe_unused]] bool compatibleTypes =
+ const CXXRecordDecl *DeclToCopy =
+ TypeToCopy.isNull() ? nullptr : TypeToCopy->getAsCXXRecordDecl();
+
+ [[maybe_unused]] bool CompatibleTypes =
SrcType == DstType ||
(SrcDecl != nullptr && DstDecl != nullptr &&
- (SrcDecl->isDerivedFrom(DstDecl) || DstDecl->isDerivedFrom(SrcDecl)));
+ (SrcDecl->isDerivedFrom(DstDecl) || DstDecl->isDerivedFrom(SrcDecl) ||
+ (DeclToCopy != nullptr && SrcDecl->isDerivedFrom(DeclToCopy) &&
+ DstDecl->isDerivedFrom(DeclToCopy))));
LLVM_DEBUG({
- if (!compatibleTypes) {
+ if (!CompatibleTypes) {
llvm::dbgs() << "Source type " << Src.getType() << "\n";
llvm::dbgs() << "Destination type " << Dst.getType() << "\n";
}
});
- assert(compatibleTypes);
+ assert(CompatibleTypes);
if (SrcType == DstType || (SrcDecl != nullptr && DstDecl != nullptr &&
SrcDecl->isDerivedFrom(DstDecl))) {
@@ -76,12 +84,23 @@ void copyRecord(RecordStorageLocation &Src, RecordStorageLocation &Dst,
for (const auto &[Name, DstFieldLoc] : Dst.synthetic_fields())
copySyntheticField(DstFieldLoc->getType(), Src.getSyntheticField(Name),
*DstFieldLoc, Env);
- } else {
+ } else if (DstDecl->isDerivedFrom(SrcDecl)) {
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,
Dst.getSyntheticField(Name), Env);
+ } else {
+ for (const FieldDecl *Field :
+ Env.getDataflowAnalysisContext().getModeledFields(TypeToCopy)) {
+ copyField(*Field, Src.getChild(*Field), Dst.getChild(*Field), Dst, Env);
+ }
+ for (const auto &[SyntheticFieldName, SyntheticFieldType] :
+ Env.getDataflowAnalysisContext().getSyntheticFields(TypeToCopy)) {
+ copySyntheticField(SyntheticFieldType,
+ Src.getSyntheticField(SyntheticFieldName),
+ Dst.getSyntheticField(SyntheticFieldName), Env);
+ }
}
}
diff --git a/clang/lib/Analysis/FlowSensitive/Transfer.cpp b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
index 23a6de45e18b1..60371d9498c25 100644
--- a/clang/lib/Analysis/FlowSensitive/Transfer.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
@@ -610,7 +610,15 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
// Even if the copy/move constructor call is elidable, we choose to copy
// the record in all cases (which isn't wrong, just potentially not
// optimal).
- copyRecord(*ArgLoc, Loc, Env);
+ //
+ // To handle cases of base class initializers in constructors, where a
+ // sibling derived class can be used to initialize a shared-base-class
+ // subobject through a DerivedToBase cast, intentionally copy only the
+ // parts of `ArgLoc` that are part of the base class being initialized.
+ // This is necessary because the type of `Loc` in these cases is the
+ // derived type ultimately being constructed, not the type of the base
+ // class subobject.
+ copyRecord(*ArgLoc, Loc, Env, S->getType());
return;
}
diff --git a/clang/unittests/Analysis/FlowSensitive/RecordOpsTest.cpp b/clang/unittests/Analysis/FlowSensitive/RecordOpsTest.cpp
index 88b92668c850c..57162cd2efcc4 100644
--- a/clang/unittests/Analysis/FlowSensitive/RecordOpsTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/RecordOpsTest.cpp
@@ -8,8 +8,15 @@
#include "clang/Analysis/FlowSensitive/RecordOps.h"
#include "TestingSupport.h"
+#include "clang/AST/Type.h"
+#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+#include "clang/Analysis/FlowSensitive/NoopLattice.h"
+#include "clang/Analysis/FlowSensitive/StorageLocation.h"
+#include "llvm/ADT/StringMap.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
+#include <string>
namespace clang {
namespace dataflow {
@@ -190,7 +197,7 @@ TEST(RecordOpsTest, RecordsEqual) {
});
}
-TEST(TransferTest, CopyRecordBetweenDerivedAndBase) {
+TEST(RecordOpsTest, CopyRecordBetweenDerivedAndBase) {
std::string Code = R"(
struct A {
int i;
@@ -266,6 +273,67 @@ TEST(TransferTest, CopyRecordBetweenDerivedAndBase) {
});
}
+TEST(RecordOpsTest, CopyRecordWithExplicitSharedBaseTypeToCopy) {
+ std::string Code = R"(
+ struct Base {
+ bool BaseField;
+ char UnmodeledField;
+ };
+
+ struct DerivedOne : public Base {
+ int DerivedOneField;
+ };
+
+ struct DerivedTwo : public Base {
+ int DerivedTwoField;
+ };
+
+ void target(Base B, DerivedOne D1, DerivedTwo D2) {
+ (void) B.BaseField;
+ // [[p]]
+ }
+ )";
+ auto SyntheticFieldCallback = [](QualType Ty) -> llvm::StringMap<QualType> {
+ CXXRecordDecl *BaseDecl = nullptr;
+ std::string TypeAsString = Ty.getAsString();
+ if (TypeAsString == "Base")
+ BaseDecl = Ty->getAsCXXRecordDecl();
+ else if (TypeAsString == "DerivedOne" || TypeAsString == "DerivedTwo")
+ BaseDecl = Ty->getAsCXXRecordDecl()
+ ->bases_begin()
+ ->getType()
+ ->getAsCXXRecordDecl();
+ else
+ return {};
+ QualType FieldType = getFieldNamed(BaseDecl, "BaseField")->getType();
+ return {{"synth_field", FieldType}};
+ };
+ // Test copying derived to base class.
+ runDataflow(
+ Code, SyntheticFieldCallback,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ ASTContext &ASTCtx) {
+ Environment Env = getEnvironmentAtAnnotation(Results, "p").fork();
+
+ const ValueDecl *BaseFieldDecl = findValueDecl(ASTCtx, "BaseField");
+ auto &B = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "B");
+ auto &D1 = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "D1");
+ auto &D2 = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "D2");
+
+ EXPECT_NE(Env.getValue(*D1.getChild(*BaseFieldDecl)),
+ Env.getValue(*D2.getChild(*BaseFieldDecl)));
+ EXPECT_NE(Env.getValue(D1.getSyntheticField("synth_field")),
+ Env.getValue(D2.getSyntheticField("synth_field")));
+
+ copyRecord(D1, D2, Env, B.getType());
+
+ EXPECT_EQ(Env.getValue(*D1.getChild(*BaseFieldDecl)),
+ Env.getValue(*D2.getChild(*BaseFieldDecl)));
+ EXPECT_EQ(Env.getValue(D1.getSyntheticField("synth_field")),
+ Env.getValue(D2.getSyntheticField("synth_field")));
+ });
+}
+
} // namespace
} // namespace test
} // namespace dataflow
diff --git a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
index 96e759e73c154..d97e2b0c2425a 100644
--- a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
@@ -1536,6 +1536,40 @@ TEST(TransferTest, BaseClassInitializer) {
llvm::Succeeded());
}
+TEST(TransferTest, BaseClassInitializerFromSiblingDerivedInstance) {
+ using ast_matchers::cxxConstructorDecl;
+ using ast_matchers::hasName;
+ using ast_matchers::ofClass;
+
+ std::string Code = R"(
+ struct Base {
+ bool BaseField;
+ char UnmodeledField;
+ };
+
+ struct DerivedOne : public Base {
+ int DerivedOneField;
+ };
+
+ struct DerivedTwo : public Base {
+ int DerivedTwoField;
+
+ DerivedTwo(const DerivedOne& d1)
+ : Base(d1), DerivedTwoField(d1.DerivedOneField) {
+ (void)BaseField;
+ }
+ };
+ )";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ ASTContext &ASTCtx) {
+ // Regression test only; we used to crash when transferring the base
+ // class initializer from the DerivedToBase-cast `d1`.
+ },
+ LangStandard::lang_cxx17, /*ApplyBuiltinTransfer=*/true, "DerivedTwo");
+}
+
TEST(TransferTest, FieldsDontHaveValuesInConstructor) {
// In a constructor, unlike in regular member functions, we don't want fields
// to be pre-initialized with values, because doing so is the job of the
>From b0c713d5851dcb5dd5002ea15793947e17b4ae1f Mon Sep 17 00:00:00 2001
From: Samira Bakon <bazuzi at google.com>
Date: Tue, 9 Sep 2025 14:12:39 -0400
Subject: [PATCH 3/3] Add null checks
---
clang/lib/Analysis/FlowSensitive/RecordOps.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clang/lib/Analysis/FlowSensitive/RecordOps.cpp b/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
index 71b2aa9ff2ec4..ed827ac2c7c90 100644
--- a/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
+++ b/clang/lib/Analysis/FlowSensitive/RecordOps.cpp
@@ -84,7 +84,8 @@ void copyRecord(RecordStorageLocation &Src, RecordStorageLocation &Dst,
for (const auto &[Name, DstFieldLoc] : Dst.synthetic_fields())
copySyntheticField(DstFieldLoc->getType(), Src.getSyntheticField(Name),
*DstFieldLoc, Env);
- } else if (DstDecl->isDerivedFrom(SrcDecl)) {
+ } else if (SrcDecl != nullptr && DstDecl != nullptr &&
+ DstDecl->isDerivedFrom(SrcDecl)) {
for (auto [Field, SrcFieldLoc] : Src.children())
copyField(*Field, SrcFieldLoc, Dst.getChild(*Field), Dst, Env);
for (const auto &[Name, SrcFieldLoc] : Src.synthetic_fields())
More information about the cfe-commits
mailing list