[clang] 99f7d55 - [clang][dataflow] Add transfer functions for data members and this pointers

Stanislav Gatev via cfe-commits cfe-commits at lists.llvm.org
Tue Jan 11 23:44:09 PST 2022


Author: Stanislav Gatev
Date: 2022-01-12T07:38:26Z
New Revision: 99f7d55eeeecd56566d23ee222e272729e05535c

URL: https://github.com/llvm/llvm-project/commit/99f7d55eeeecd56566d23ee222e272729e05535c
DIFF: https://github.com/llvm/llvm-project/commit/99f7d55eeeecd56566d23ee222e272729e05535c.diff

LOG: [clang][dataflow] Add transfer functions for data members and this pointers

This is part of the implementation of the dataflow analysis framework.
See "[RFC] A dataflow analysis framework for Clang AST" on cfe-dev.

Reviewed-by: ymandel, xazax.hun

Differential Revision: https://reviews.llvm.org/D117012

Added: 
    

Modified: 
    clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
    clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
    clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
    clang/lib/Analysis/FlowSensitive/Transfer.cpp
    clang/unittests/Analysis/FlowSensitive/TestingSupport.h
    clang/unittests/Analysis/FlowSensitive/TransferTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
index 8a836713e3392..d1a06f7ef8d3a 100644
--- a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
+++ b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
@@ -88,6 +88,22 @@ class DataflowAnalysisContext {
     return It == ExprToLoc.end() ? nullptr : It->second;
   }
 
+  /// Assigns `Loc` as the storage location of the `this` pointee.
+  ///
+  /// Requirements:
+  ///
+  ///  The `this` pointee must not be assigned a storage location.
+  void setThisPointeeStorageLocation(StorageLocation &Loc) {
+    assert(ThisPointeeLoc == nullptr);
+    ThisPointeeLoc = &Loc;
+  }
+
+  /// Returns the storage location assigned to the `this` pointee or null if the
+  /// `this` pointee has no assigned storage location.
+  StorageLocation *getThisPointeeStorageLocation() const {
+    return ThisPointeeLoc;
+  }
+
 private:
   // Storage for the state of a program.
   std::vector<std::unique_ptr<StorageLocation>> Locs;
@@ -101,7 +117,7 @@ class DataflowAnalysisContext {
   llvm::DenseMap<const ValueDecl *, StorageLocation *> DeclToLoc;
   llvm::DenseMap<const Expr *, StorageLocation *> ExprToLoc;
 
-  // FIXME: Add `StorageLocation` for `this`.
+  StorageLocation *ThisPointeeLoc = nullptr;
 
   // FIXME: Add support for boolean expressions.
 };

diff  --git a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
index 2138e0fb6da63..9613b2921c3f2 100644
--- a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
+++ b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
@@ -16,6 +16,7 @@
 #define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_DATAFLOWENVIRONMENT_H
 
 #include "clang/AST/Decl.h"
+#include "clang/AST/DeclBase.h"
 #include "clang/AST/Expr.h"
 #include "clang/AST/Type.h"
 #include "clang/AST/TypeOrdering.h"
@@ -39,17 +40,35 @@ enum class SkipPast {
   None,
   /// An optional reference should be skipped past.
   Reference,
+  /// An optional reference should be skipped past, then an optional pointer
+  /// should be skipped past.
+  ReferenceThenPointer,
 };
 
 /// Holds the state of the program (store and heap) at a given program point.
 class Environment {
 public:
-  Environment(DataflowAnalysisContext &DACtx) : DACtx(&DACtx) {}
+  /// Creates an environment that uses `DACtx` to store objects that encompass
+  /// the state of a program.
+  explicit Environment(DataflowAnalysisContext &DACtx) : DACtx(&DACtx) {}
+
+  /// Creates an environment that uses `DACtx` to store objects that encompass
+  /// the state of a program.
+  ///
+  /// If `DeclCtx` is a function, initializes the environment with symbolic
+  /// representations of the function parameters.
+  ///
+  /// If `DeclCtx` is a non-static member function, initializes the environment
+  /// with a symbolic representation of the `this` pointee.
+  Environment(DataflowAnalysisContext &DACtx, const DeclContext &DeclCtx);
 
   bool operator==(const Environment &) const;
 
   LatticeJoinEffect join(const Environment &);
 
+  // FIXME: Rename `createOrGetStorageLocation` to `getOrCreateStorageLocation`,
+  // `getStableStorageLocation`, or something more appropriate.
+
   /// Creates a storage location appropriate for `Type`. Does not assign a value
   /// to the returned storage location in the environment.
   ///
@@ -92,6 +111,11 @@ class Environment {
   /// assigned a storage location in the environment.
   StorageLocation *getStorageLocation(const Expr &E, SkipPast SP) const;
 
+  /// Returns the storage location assigned to the `this` pointee in the
+  /// environment or null if the `this` pointee has no assigned storage location
+  /// in the environment.
+  StorageLocation *getThisPointeeStorageLocation() const;
+
   /// Creates a value appropriate for `Type`, assigns it to `Loc`, and returns
   /// it, if `Type` is supported, otherwise return null. If `Type` is a pointer
   /// or reference type, creates all the necessary storage locations and values

diff  --git a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
index d1e7b2a4b38c5..5824bc13ce7b6 100644
--- a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
+++ b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
@@ -14,6 +14,7 @@
 
 #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
 #include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
 #include "clang/AST/Type.h"
 #include "clang/Analysis/FlowSensitive/DataflowLattice.h"
 #include "clang/Analysis/FlowSensitive/StorageLocation.h"
@@ -40,6 +41,31 @@ llvm::DenseMap<K, V> intersectDenseMaps(const llvm::DenseMap<K, V> &Map1,
   return Result;
 }
 
+Environment::Environment(DataflowAnalysisContext &DACtx,
+                         const DeclContext &DeclCtx)
+    : Environment(DACtx) {
+  if (const auto *FuncDecl = dyn_cast<FunctionDecl>(&DeclCtx)) {
+    for (const auto *ParamDecl : FuncDecl->parameters()) {
+      assert(ParamDecl != nullptr);
+      auto &ParamLoc = createStorageLocation(*ParamDecl);
+      setStorageLocation(*ParamDecl, ParamLoc);
+      initValueInStorageLocation(ParamLoc, ParamDecl->getType());
+    }
+  }
+
+  if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(&DeclCtx)) {
+    if (!MethodDecl->isStatic()) {
+      QualType ThisPointeeType = MethodDecl->getThisObjectType();
+      // FIXME: Add support for union types.
+      if (!ThisPointeeType->isUnionType()) {
+        auto &ThisPointeeLoc = createStorageLocation(ThisPointeeType);
+        DACtx.setThisPointeeStorageLocation(ThisPointeeLoc);
+        initValueInStorageLocation(ThisPointeeLoc, ThisPointeeType);
+      }
+    }
+  }
+}
+
 bool Environment::operator==(const Environment &Other) const {
   assert(DACtx == Other.DACtx);
   return DeclToLoc == Other.DeclToLoc && LocToVal == Other.LocToVal;
@@ -124,6 +150,10 @@ StorageLocation *Environment::getStorageLocation(const Expr &E,
   return It == ExprToLoc.end() ? nullptr : &skip(*It->second, SP);
 }
 
+StorageLocation *Environment::getThisPointeeStorageLocation() const {
+  return DACtx->getThisPointeeStorageLocation();
+}
+
 void Environment::setValue(const StorageLocation &Loc, Value &Value) {
   LocToVal[&Loc] = &Value;
 }
@@ -199,6 +229,8 @@ Value *Environment::initValueInStorageLocationUnlessSelfReferential(
   if (Type->isStructureOrClassType()) {
     auto *AggregateLoc = cast<AggregateStorageLocation>(&Loc);
 
+    // FIXME: Initialize only fields that are accessed in the context that is
+    // being analyzed.
     llvm::DenseMap<const ValueDecl *, Value *> FieldValues;
     for (const FieldDecl *Field : Type->getAsRecordDecl()->fields()) {
       assert(Field != nullptr);
@@ -242,6 +274,11 @@ StorageLocation &Environment::skip(StorageLocation &Loc, SkipPast SP) const {
     if (auto *Val = dyn_cast_or_null<ReferenceValue>(getValue(Loc)))
       return Val->getPointeeLoc();
     return Loc;
+  case SkipPast::ReferenceThenPointer:
+    StorageLocation &LocPastRef = skip(Loc, SkipPast::Reference);
+    if (auto *Val = dyn_cast_or_null<PointerValue>(getValue(LocPastRef)))
+      return Val->getPointeeLoc();
+    return LocPastRef;
   }
   llvm_unreachable("bad SkipPast kind");
 }

diff  --git a/clang/lib/Analysis/FlowSensitive/Transfer.cpp b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
index bae162ae3cd14..f28271101b9c1 100644
--- a/clang/lib/Analysis/FlowSensitive/Transfer.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
@@ -15,6 +15,7 @@
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclBase.h"
 #include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
 #include "clang/AST/OperationKinds.h"
 #include "clang/AST/Stmt.h"
 #include "clang/AST/StmtVisitor.h"
@@ -123,6 +124,46 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
     // FIXME: Add support for UO_AddrOf, UO_LNot.
   }
 
+  void VisitCXXThisExpr(const CXXThisExpr *S) {
+    auto *ThisPointeeLoc = Env.getThisPointeeStorageLocation();
+    assert(ThisPointeeLoc != nullptr);
+
+    auto &Loc = Env.createStorageLocation(*S);
+    Env.setStorageLocation(*S, Loc);
+    Env.setValue(Loc, Env.takeOwnership(
+                          std::make_unique<PointerValue>(*ThisPointeeLoc)));
+  }
+
+  void VisitMemberExpr(const MemberExpr *S) {
+    ValueDecl *Member = S->getMemberDecl();
+    assert(Member != nullptr);
+
+    // FIXME: Consider assigning pointer values to function member expressions.
+    if (Member->isFunctionOrFunctionTemplate())
+      return;
+
+    // The receiver can be either a value or a pointer to a value. Skip past the
+    // indirection to handle both cases.
+    auto *BaseLoc = cast_or_null<AggregateStorageLocation>(
+        Env.getStorageLocation(*S->getBase(), SkipPast::ReferenceThenPointer));
+    if (BaseLoc == nullptr)
+      return;
+
+    // FIXME: Add support for union types.
+    if (BaseLoc->getType()->isUnionType())
+      return;
+
+    auto &MemberLoc = BaseLoc->getChild(*Member);
+    if (MemberLoc.getType()->isReferenceType()) {
+      Env.setStorageLocation(*S, MemberLoc);
+    } else {
+      auto &Loc = Env.createStorageLocation(*S);
+      Env.setStorageLocation(*S, Loc);
+      Env.setValue(
+          Loc, Env.takeOwnership(std::make_unique<ReferenceValue>(MemberLoc)));
+    }
+  }
+
   // FIXME: Add support for:
   // - CallExpr
   // - CXXBindTemporaryExpr
@@ -131,9 +172,7 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
   // - CXXFunctionalCastExpr
   // - CXXOperatorCallExpr
   // - CXXStaticCastExpr
-  // - CXXThisExpr
   // - MaterializeTemporaryExpr
-  // - MemberExpr
 
 private:
   void visitVarDecl(const VarDecl &D) {

diff  --git a/clang/unittests/Analysis/FlowSensitive/TestingSupport.h b/clang/unittests/Analysis/FlowSensitive/TestingSupport.h
index f5137959cfa98..fe955207170ef 100644
--- a/clang/unittests/Analysis/FlowSensitive/TestingSupport.h
+++ b/clang/unittests/Analysis/FlowSensitive/TestingSupport.h
@@ -99,7 +99,7 @@ void checkDataflow(
   ASSERT_TRUE((bool)CFCtx) << "Could not build ControlFlowContext.";
 
   DataflowAnalysisContext DACtx;
-  Environment Env(DACtx);
+  Environment Env(DACtx, *F);
   auto Analysis = MakeAnalysis(Context, Env);
 
   llvm::Expected<llvm::DenseMap<const clang::Stmt *, std::string>>

diff  --git a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
index a54dceb5c5b60..55e15e28ce5eb 100644
--- a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
@@ -67,7 +67,7 @@ static const ValueDecl *findValueDecl(ASTContext &ASTCtx,
 TEST_F(TransferTest, IntVarDecl) {
   std::string Code = R"(
     void target() {
-      int foo;
+      int Foo;
       // [[p]]
     }
   )";
@@ -79,7 +79,7 @@ TEST_F(TransferTest, IntVarDecl) {
         ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
         const Environment &Env = Results[0].second.Env;
 
-        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo");
+        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
         ASSERT_THAT(FooDecl, NotNull());
 
         const StorageLocation *FooLoc =
@@ -87,18 +87,18 @@ TEST_F(TransferTest, IntVarDecl) {
         ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));
 
         const Value *FooVal = Env.getValue(*FooLoc);
-        ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));
+        EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));
       });
 }
 
 TEST_F(TransferTest, StructVarDecl) {
   std::string Code = R"(
-    struct Foo {
+    struct A {
       int Bar;
     };
 
     void target() {
-      Foo foo;
+      A Foo;
       // [[p]]
     }
   )";
@@ -110,7 +110,7 @@ TEST_F(TransferTest, StructVarDecl) {
         ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
         const Environment &Env = Results[0].second.Env;
 
-        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo");
+        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
         ASSERT_THAT(FooDecl, NotNull());
 
         ASSERT_TRUE(FooDecl->getType()->isStructureType());
@@ -133,18 +133,18 @@ TEST_F(TransferTest, StructVarDecl) {
 
         const auto *FooVal = cast<StructValue>(Env.getValue(*FooLoc));
         const auto *BarVal = cast<IntegerValue>(&FooVal->getChild(*BarDecl));
-        ASSERT_EQ(Env.getValue(*BarLoc), BarVal);
+        EXPECT_EQ(Env.getValue(*BarLoc), BarVal);
       });
 }
 
 TEST_F(TransferTest, ClassVarDecl) {
   std::string Code = R"(
-    class Foo {
+    class A {
       int Bar;
     };
 
     void target() {
-      Foo foo;
+      A Foo;
       // [[p]]
     }
   )";
@@ -156,7 +156,7 @@ TEST_F(TransferTest, ClassVarDecl) {
         ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
         const Environment &Env = Results[0].second.Env;
 
-        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo");
+        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
         ASSERT_THAT(FooDecl, NotNull());
 
         ASSERT_TRUE(FooDecl->getType()->isClassType());
@@ -179,18 +179,18 @@ TEST_F(TransferTest, ClassVarDecl) {
 
         const auto *FooVal = cast<StructValue>(Env.getValue(*FooLoc));
         const auto *BarVal = cast<IntegerValue>(&FooVal->getChild(*BarDecl));
-        ASSERT_EQ(Env.getValue(*BarLoc), BarVal);
+        EXPECT_EQ(Env.getValue(*BarLoc), BarVal);
       });
 }
 
 TEST_F(TransferTest, ReferenceVarDecl) {
   std::string Code = R"(
-    struct Foo {};
+    struct A {};
 
-    Foo& getFoo();
+    A &getA();
 
     void target() {
-      Foo& foo = getFoo();
+      A &Foo = getA();
       // [[p]]
     }
   )";
@@ -202,7 +202,7 @@ TEST_F(TransferTest, ReferenceVarDecl) {
         ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
         const Environment &Env = Results[0].second.Env;
 
-        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo");
+        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
         ASSERT_THAT(FooDecl, NotNull());
 
         const StorageLocation *FooLoc =
@@ -212,34 +212,34 @@ TEST_F(TransferTest, ReferenceVarDecl) {
         const ReferenceValue *FooVal =
             cast<ReferenceValue>(Env.getValue(*FooLoc));
         const StorageLocation &FooPointeeLoc = FooVal->getPointeeLoc();
-        ASSERT_TRUE(isa<AggregateStorageLocation>(&FooPointeeLoc));
+        EXPECT_TRUE(isa<AggregateStorageLocation>(&FooPointeeLoc));
 
         const Value *FooPointeeVal = Env.getValue(FooPointeeLoc);
-        ASSERT_TRUE(isa_and_nonnull<StructValue>(FooPointeeVal));
+        EXPECT_TRUE(isa_and_nonnull<StructValue>(FooPointeeVal));
       });
 }
 
 TEST_F(TransferTest, SelfReferentialReferenceVarDecl) {
   std::string Code = R"(
-    struct Foo;
+    struct A;
 
-    struct Baz {};
+    struct B {};
 
-    struct Bar {
-      Foo& FooRef;
-      Foo* FooPtr;
-      Baz& BazRef;
-      Baz* BazPtr;
+    struct C {
+      A &FooRef;
+      A *FooPtr;
+      B &BazRef;
+      B *BazPtr;
     };
 
-    struct Foo {
-      Bar& Bar;
+    struct A {
+      C &Bar;
     };
 
-    Foo& getFoo();
+    A &getA();
 
     void target() {
-      Foo& foo = getFoo();
+      A &Foo = getA();
       // [[p]]
     }
   )";
@@ -250,7 +250,7 @@ TEST_F(TransferTest, SelfReferentialReferenceVarDecl) {
     ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
     const Environment &Env = Results[0].second.Env;
 
-    const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo");
+    const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
     ASSERT_THAT(FooDecl, NotNull());
 
     ASSERT_TRUE(FooDecl->getType()->isReferenceType());
@@ -309,33 +309,33 @@ TEST_F(TransferTest, SelfReferentialReferenceVarDecl) {
     const auto *FooRefVal =
         cast<ReferenceValue>(&BarPointeeVal->getChild(*FooRefDecl));
     const StorageLocation &FooRefPointeeLoc = FooRefVal->getPointeeLoc();
-    ASSERT_THAT(Env.getValue(FooRefPointeeLoc), IsNull());
+    EXPECT_THAT(Env.getValue(FooRefPointeeLoc), IsNull());
 
     const auto *FooPtrVal =
         cast<PointerValue>(&BarPointeeVal->getChild(*FooPtrDecl));
     const StorageLocation &FooPtrPointeeLoc = FooPtrVal->getPointeeLoc();
-    ASSERT_THAT(Env.getValue(FooPtrPointeeLoc), IsNull());
+    EXPECT_THAT(Env.getValue(FooPtrPointeeLoc), IsNull());
 
     const auto *BazRefVal =
         cast<ReferenceValue>(&BarPointeeVal->getChild(*BazRefDecl));
     const StorageLocation &BazRefPointeeLoc = BazRefVal->getPointeeLoc();
-    ASSERT_THAT(Env.getValue(BazRefPointeeLoc), NotNull());
+    EXPECT_THAT(Env.getValue(BazRefPointeeLoc), NotNull());
 
     const auto *BazPtrVal =
         cast<PointerValue>(&BarPointeeVal->getChild(*BazPtrDecl));
     const StorageLocation &BazPtrPointeeLoc = BazPtrVal->getPointeeLoc();
-    ASSERT_THAT(Env.getValue(BazPtrPointeeLoc), NotNull());
+    EXPECT_THAT(Env.getValue(BazPtrPointeeLoc), NotNull());
   });
 }
 
 TEST_F(TransferTest, PointerVarDecl) {
   std::string Code = R"(
-    struct Foo {};
+    struct A {};
 
-    Foo* getFoo();
+    A *getA();
 
     void target() {
-      Foo* foo = getFoo();
+      A *Foo = getA();
       // [[p]]
     }
   )";
@@ -347,7 +347,7 @@ TEST_F(TransferTest, PointerVarDecl) {
         ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
         const Environment &Env = Results[0].second.Env;
 
-        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo");
+        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
         ASSERT_THAT(FooDecl, NotNull());
 
         const StorageLocation *FooLoc =
@@ -356,34 +356,34 @@ TEST_F(TransferTest, PointerVarDecl) {
 
         const PointerValue *FooVal = cast<PointerValue>(Env.getValue(*FooLoc));
         const StorageLocation &FooPointeeLoc = FooVal->getPointeeLoc();
-        ASSERT_TRUE(isa<AggregateStorageLocation>(&FooPointeeLoc));
+        EXPECT_TRUE(isa<AggregateStorageLocation>(&FooPointeeLoc));
 
         const Value *FooPointeeVal = Env.getValue(FooPointeeLoc);
-        ASSERT_TRUE(isa_and_nonnull<StructValue>(FooPointeeVal));
+        EXPECT_TRUE(isa_and_nonnull<StructValue>(FooPointeeVal));
       });
 }
 
 TEST_F(TransferTest, SelfReferentialPointerVarDecl) {
   std::string Code = R"(
-    struct Foo;
+    struct A;
 
-    struct Baz {};
+    struct B {};
 
-    struct Bar {
-      Foo& FooRef;
-      Foo* FooPtr;
-      Baz& BazRef;
-      Baz* BazPtr;
+    struct C {
+      A &FooRef;
+      A *FooPtr;
+      B &BazRef;
+      B *BazPtr;
     };
 
-    struct Foo {
-      Bar* Bar;
+    struct A {
+      C *Bar;
     };
 
-    Foo* getFoo();
+    A *getA();
 
     void target() {
-      Foo* foo = getFoo();
+      A *Foo = getA();
       // [[p]]
     }
   )";
@@ -395,7 +395,7 @@ TEST_F(TransferTest, SelfReferentialPointerVarDecl) {
         ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
         const Environment &Env = Results[0].second.Env;
 
-        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo");
+        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
         ASSERT_THAT(FooDecl, NotNull());
 
         ASSERT_TRUE(FooDecl->getType()->isPointerType());
@@ -466,35 +466,35 @@ TEST_F(TransferTest, SelfReferentialPointerVarDecl) {
         const auto *FooRefVal =
             cast<ReferenceValue>(&BarPointeeVal->getChild(*FooRefDecl));
         const StorageLocation &FooRefPointeeLoc = FooRefVal->getPointeeLoc();
-        ASSERT_THAT(Env.getValue(FooRefPointeeLoc), IsNull());
+        EXPECT_THAT(Env.getValue(FooRefPointeeLoc), IsNull());
 
         const auto *FooPtrVal =
             cast<PointerValue>(&BarPointeeVal->getChild(*FooPtrDecl));
         const StorageLocation &FooPtrPointeeLoc = FooPtrVal->getPointeeLoc();
-        ASSERT_THAT(Env.getValue(FooPtrPointeeLoc), IsNull());
+        EXPECT_THAT(Env.getValue(FooPtrPointeeLoc), IsNull());
 
         const auto *BazRefVal =
             cast<ReferenceValue>(&BarPointeeVal->getChild(*BazRefDecl));
         const StorageLocation &BazRefPointeeLoc = BazRefVal->getPointeeLoc();
-        ASSERT_THAT(Env.getValue(BazRefPointeeLoc), NotNull());
+        EXPECT_THAT(Env.getValue(BazRefPointeeLoc), NotNull());
 
         const auto *BazPtrVal =
             cast<PointerValue>(&BarPointeeVal->getChild(*BazPtrDecl));
         const StorageLocation &BazPtrPointeeLoc = BazPtrVal->getPointeeLoc();
-        ASSERT_THAT(Env.getValue(BazPtrPointeeLoc), NotNull());
+        EXPECT_THAT(Env.getValue(BazPtrPointeeLoc), NotNull());
       });
 }
 
 TEST_F(TransferTest, JoinVarDecl) {
   std::string Code = R"(
-    void target(bool b) {
-      int foo;
+    void target(bool B) {
+      int Foo;
       // [[p1]]
-      if (b) {
-        int bar;
+      if (B) {
+        int Bar;
         // [[p2]]
       } else {
-        int baz;
+        int Baz;
         // [[p3]]
       }
       (void)0;
@@ -507,45 +507,45 @@ TEST_F(TransferTest, JoinVarDecl) {
                        ASTContext &ASTCtx) {
     ASSERT_THAT(Results, ElementsAre(Pair("p4", _), Pair("p3", _),
                                      Pair("p2", _), Pair("p1", _)));
-    const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo");
+    const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
     ASSERT_THAT(FooDecl, NotNull());
 
-    const ValueDecl *BarDecl = findValueDecl(ASTCtx, "bar");
+    const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
     ASSERT_THAT(BarDecl, NotNull());
 
-    const ValueDecl *BazDecl = findValueDecl(ASTCtx, "baz");
+    const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
     ASSERT_THAT(BazDecl, NotNull());
 
     const Environment &Env1 = Results[3].second.Env;
     const StorageLocation *FooLoc =
         Env1.getStorageLocation(*FooDecl, SkipPast::None);
-    ASSERT_THAT(FooLoc, NotNull());
-    ASSERT_THAT(Env1.getStorageLocation(*BarDecl, SkipPast::None), IsNull());
-    ASSERT_THAT(Env1.getStorageLocation(*BazDecl, SkipPast::None), IsNull());
+    EXPECT_THAT(FooLoc, NotNull());
+    EXPECT_THAT(Env1.getStorageLocation(*BarDecl, SkipPast::None), IsNull());
+    EXPECT_THAT(Env1.getStorageLocation(*BazDecl, SkipPast::None), IsNull());
 
     const Environment &Env2 = Results[2].second.Env;
-    ASSERT_EQ(Env2.getStorageLocation(*FooDecl, SkipPast::None), FooLoc);
-    ASSERT_THAT(Env2.getStorageLocation(*BarDecl, SkipPast::None), NotNull());
-    ASSERT_THAT(Env2.getStorageLocation(*BazDecl, SkipPast::None), IsNull());
+    EXPECT_EQ(Env2.getStorageLocation(*FooDecl, SkipPast::None), FooLoc);
+    EXPECT_THAT(Env2.getStorageLocation(*BarDecl, SkipPast::None), NotNull());
+    EXPECT_THAT(Env2.getStorageLocation(*BazDecl, SkipPast::None), IsNull());
 
     const Environment &Env3 = Results[1].second.Env;
-    ASSERT_EQ(Env3.getStorageLocation(*FooDecl, SkipPast::None), FooLoc);
-    ASSERT_THAT(Env3.getStorageLocation(*BarDecl, SkipPast::None), IsNull());
-    ASSERT_THAT(Env3.getStorageLocation(*BazDecl, SkipPast::None), NotNull());
+    EXPECT_EQ(Env3.getStorageLocation(*FooDecl, SkipPast::None), FooLoc);
+    EXPECT_THAT(Env3.getStorageLocation(*BarDecl, SkipPast::None), IsNull());
+    EXPECT_THAT(Env3.getStorageLocation(*BazDecl, SkipPast::None), NotNull());
 
     const Environment &Env4 = Results[0].second.Env;
-    ASSERT_EQ(Env4.getStorageLocation(*FooDecl, SkipPast::None), FooLoc);
-    ASSERT_THAT(Env4.getStorageLocation(*BarDecl, SkipPast::None), IsNull());
-    ASSERT_THAT(Env4.getStorageLocation(*BazDecl, SkipPast::None), IsNull());
+    EXPECT_EQ(Env4.getStorageLocation(*FooDecl, SkipPast::None), FooLoc);
+    EXPECT_THAT(Env4.getStorageLocation(*BarDecl, SkipPast::None), IsNull());
+    EXPECT_THAT(Env4.getStorageLocation(*BazDecl, SkipPast::None), IsNull());
   });
 }
 
 TEST_F(TransferTest, BinaryOperatorAssign) {
   std::string Code = R"(
     void target() {
-      int foo;
-      int bar;
-      (bar) = (foo);
+      int Foo;
+      int Bar;
+      (Bar) = (Foo);
       // [[p]]
     }
   )";
@@ -557,13 +557,13 @@ TEST_F(TransferTest, BinaryOperatorAssign) {
                 ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
                 const Environment &Env = Results[0].second.Env;
 
-                const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo");
+                const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
                 ASSERT_THAT(FooDecl, NotNull());
 
                 const Value *FooVal = Env.getValue(*FooDecl, SkipPast::None);
                 ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));
 
-                const ValueDecl *BarDecl = findValueDecl(ASTCtx, "bar");
+                const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
                 ASSERT_THAT(BarDecl, NotNull());
 
                 EXPECT_EQ(Env.getValue(*BarDecl, SkipPast::None), FooVal);
@@ -573,8 +573,8 @@ TEST_F(TransferTest, BinaryOperatorAssign) {
 TEST_F(TransferTest, VarDeclInitAssign) {
   std::string Code = R"(
     void target() {
-      int foo;
-      int bar = foo;
+      int Foo;
+      int Bar = Foo;
       // [[p]]
     }
   )";
@@ -586,13 +586,13 @@ TEST_F(TransferTest, VarDeclInitAssign) {
                 ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
                 const Environment &Env = Results[0].second.Env;
 
-                const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo");
+                const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
                 ASSERT_THAT(FooDecl, NotNull());
 
                 const Value *FooVal = Env.getValue(*FooDecl, SkipPast::None);
                 ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));
 
-                const ValueDecl *BarDecl = findValueDecl(ASTCtx, "bar");
+                const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
                 ASSERT_THAT(BarDecl, NotNull());
 
                 EXPECT_EQ(Env.getValue(*BarDecl, SkipPast::None), FooVal);
@@ -602,9 +602,9 @@ TEST_F(TransferTest, VarDeclInitAssign) {
 TEST_F(TransferTest, VarDeclInitAssignChained) {
   std::string Code = R"(
     void target() {
-      int foo;
-      int bar;
-      int baz = (bar = foo);
+      int Foo;
+      int Bar;
+      int Baz = (Bar = Foo);
       // [[p]]
     }
   )";
@@ -616,16 +616,16 @@ TEST_F(TransferTest, VarDeclInitAssignChained) {
                 ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
                 const Environment &Env = Results[0].second.Env;
 
-                const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo");
+                const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
                 ASSERT_THAT(FooDecl, NotNull());
 
                 const Value *FooVal = Env.getValue(*FooDecl, SkipPast::None);
                 ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));
 
-                const ValueDecl *BarDecl = findValueDecl(ASTCtx, "bar");
+                const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
                 ASSERT_THAT(BarDecl, NotNull());
 
-                const ValueDecl *BazDecl = findValueDecl(ASTCtx, "baz");
+                const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
                 ASSERT_THAT(BazDecl, NotNull());
 
                 EXPECT_EQ(Env.getValue(*BarDecl, SkipPast::None), FooVal);
@@ -636,10 +636,10 @@ TEST_F(TransferTest, VarDeclInitAssignChained) {
 TEST_F(TransferTest, VarDeclInitAssignPtrDeref) {
   std::string Code = R"(
     void target() {
-      int foo;
-      int *bar;
-      *(bar) = foo;
-      int baz = *(bar);
+      int Foo;
+      int *Bar;
+      *(Bar) = Foo;
+      int Baz = *(Bar);
       // [[p]]
     }
   )";
@@ -651,20 +651,20 @@ TEST_F(TransferTest, VarDeclInitAssignPtrDeref) {
                 ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
                 const Environment &Env = Results[0].second.Env;
 
-                const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo");
+                const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
                 ASSERT_THAT(FooDecl, NotNull());
 
                 const Value *FooVal = Env.getValue(*FooDecl, SkipPast::None);
                 ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));
 
-                const ValueDecl *BarDecl = findValueDecl(ASTCtx, "bar");
+                const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
                 ASSERT_THAT(BarDecl, NotNull());
 
                 const auto *BarVal =
                     cast<PointerValue>(Env.getValue(*BarDecl, SkipPast::None));
                 EXPECT_EQ(Env.getValue(BarVal->getPointeeLoc()), FooVal);
 
-                const ValueDecl *BazDecl = findValueDecl(ASTCtx, "baz");
+                const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
                 ASSERT_THAT(BazDecl, NotNull());
 
                 EXPECT_EQ(Env.getValue(*BazDecl, SkipPast::None), FooVal);
@@ -674,13 +674,13 @@ TEST_F(TransferTest, VarDeclInitAssignPtrDeref) {
 TEST_F(TransferTest, AssignToAndFromReference) {
   std::string Code = R"(
     void target() {
-      int foo;
-      int bar;
-      int& baz = foo;
+      int Foo;
+      int Bar;
+      int &Baz = Foo;
       // [[p1]]
-      baz = bar;
-      int qux = baz;
-      int& quux = baz;
+      Baz = Bar;
+      int Qux = Baz;
+      int &Quux = Baz;
       // [[p2]]
     }
   )";
@@ -693,19 +693,19 @@ TEST_F(TransferTest, AssignToAndFromReference) {
         const Environment &Env1 = Results[0].second.Env;
         const Environment &Env2 = Results[1].second.Env;
 
-        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo");
+        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
         ASSERT_THAT(FooDecl, NotNull());
 
         const Value *FooVal = Env1.getValue(*FooDecl, SkipPast::None);
         ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));
 
-        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "bar");
+        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
         ASSERT_THAT(BarDecl, NotNull());
 
         const Value *BarVal = Env1.getValue(*BarDecl, SkipPast::None);
         ASSERT_TRUE(isa_and_nonnull<IntegerValue>(BarVal));
 
-        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "baz");
+        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
         ASSERT_THAT(BazDecl, NotNull());
 
         EXPECT_EQ(Env1.getValue(*BazDecl, SkipPast::Reference), FooVal);
@@ -713,14 +713,462 @@ TEST_F(TransferTest, AssignToAndFromReference) {
         EXPECT_EQ(Env2.getValue(*BazDecl, SkipPast::Reference), BarVal);
         EXPECT_EQ(Env2.getValue(*FooDecl, SkipPast::None), BarVal);
 
-        const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "qux");
+        const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux");
         ASSERT_THAT(QuxDecl, NotNull());
         EXPECT_EQ(Env2.getValue(*QuxDecl, SkipPast::None), BarVal);
 
-        const ValueDecl *QuuxDecl = findValueDecl(ASTCtx, "quux");
+        const ValueDecl *QuuxDecl = findValueDecl(ASTCtx, "Quux");
         ASSERT_THAT(QuuxDecl, NotNull());
         EXPECT_EQ(Env2.getValue(*QuuxDecl, SkipPast::Reference), BarVal);
       });
 }
 
+TEST_F(TransferTest, MultipleParamDecls) {
+  std::string Code = R"(
+    void target(int Foo, int Bar) {
+      (void)0;
+      // [[p]]
+    }
+  )";
+  runDataflow(Code,
+              [](llvm::ArrayRef<
+                     std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+                     Results,
+                 ASTContext &ASTCtx) {
+                ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+                const Environment &Env = Results[0].second.Env;
+
+                const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+                ASSERT_THAT(FooDecl, NotNull());
+
+                const StorageLocation *FooLoc =
+                    Env.getStorageLocation(*FooDecl, SkipPast::None);
+                ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));
+
+                const Value *FooVal = Env.getValue(*FooLoc);
+                ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));
+
+                const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
+                ASSERT_THAT(BarDecl, NotNull());
+
+                const StorageLocation *BarLoc =
+                    Env.getStorageLocation(*BarDecl, SkipPast::None);
+                ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(BarLoc));
+
+                const Value *BarVal = Env.getValue(*BarLoc);
+                EXPECT_TRUE(isa_and_nonnull<IntegerValue>(BarVal));
+              });
+}
+
+TEST_F(TransferTest, StructParamDecl) {
+  std::string Code = R"(
+    struct A {
+      int Bar;
+    };
+
+    void target(A Foo) {
+      (void)0;
+      // [[p]]
+    }
+  )";
+  runDataflow(
+      Code, [](llvm::ArrayRef<
+                   std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+                   Results,
+               ASTContext &ASTCtx) {
+        ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+        const Environment &Env = Results[0].second.Env;
+
+        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+        ASSERT_THAT(FooDecl, NotNull());
+
+        ASSERT_TRUE(FooDecl->getType()->isStructureType());
+        auto FooFields = FooDecl->getType()->getAsRecordDecl()->fields();
+
+        FieldDecl *BarDecl = nullptr;
+        for (FieldDecl *Field : FooFields) {
+          if (Field->getNameAsString() == "Bar") {
+            BarDecl = Field;
+          } else {
+            FAIL() << "Unexpected field: " << Field->getNameAsString();
+          }
+        }
+        ASSERT_THAT(BarDecl, NotNull());
+
+        const auto *FooLoc = cast<AggregateStorageLocation>(
+            Env.getStorageLocation(*FooDecl, SkipPast::None));
+        const auto *BarLoc =
+            cast<ScalarStorageLocation>(&FooLoc->getChild(*BarDecl));
+
+        const auto *FooVal = cast<StructValue>(Env.getValue(*FooLoc));
+        const auto *BarVal = cast<IntegerValue>(&FooVal->getChild(*BarDecl));
+        EXPECT_EQ(Env.getValue(*BarLoc), BarVal);
+      });
+}
+
+TEST_F(TransferTest, ReferenceParamDecl) {
+  std::string Code = R"(
+    struct A {};
+
+    void target(A &Foo) {
+      (void)0;
+      // [[p]]
+    }
+  )";
+  runDataflow(Code,
+              [](llvm::ArrayRef<
+                     std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+                     Results,
+                 ASTContext &ASTCtx) {
+                ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+                const Environment &Env = Results[0].second.Env;
+
+                const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+                ASSERT_THAT(FooDecl, NotNull());
+
+                const StorageLocation *FooLoc =
+                    Env.getStorageLocation(*FooDecl, SkipPast::None);
+                ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));
+
+                const ReferenceValue *FooVal =
+                    dyn_cast<ReferenceValue>(Env.getValue(*FooLoc));
+                ASSERT_THAT(FooVal, NotNull());
+
+                const StorageLocation &FooPointeeLoc = FooVal->getPointeeLoc();
+                EXPECT_TRUE(isa<AggregateStorageLocation>(&FooPointeeLoc));
+
+                const Value *FooPointeeVal = Env.getValue(FooPointeeLoc);
+                EXPECT_TRUE(isa_and_nonnull<StructValue>(FooPointeeVal));
+              });
+}
+
+TEST_F(TransferTest, PointerParamDecl) {
+  std::string Code = R"(
+    struct A {};
+
+    void target(A *Foo) {
+      (void)0;
+      // [[p]]
+    }
+  )";
+  runDataflow(
+      Code, [](llvm::ArrayRef<
+                   std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+                   Results,
+               ASTContext &ASTCtx) {
+        ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+        const Environment &Env = Results[0].second.Env;
+
+        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+        ASSERT_THAT(FooDecl, NotNull());
+
+        const StorageLocation *FooLoc =
+            Env.getStorageLocation(*FooDecl, SkipPast::None);
+        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));
+
+        const PointerValue *FooVal = cast<PointerValue>(Env.getValue(*FooLoc));
+        const StorageLocation &FooPointeeLoc = FooVal->getPointeeLoc();
+        EXPECT_TRUE(isa<AggregateStorageLocation>(&FooPointeeLoc));
+
+        const Value *FooPointeeVal = Env.getValue(FooPointeeLoc);
+        EXPECT_TRUE(isa_and_nonnull<StructValue>(FooPointeeVal));
+      });
+}
+
+TEST_F(TransferTest, StructMember) {
+  std::string Code = R"(
+    struct A {
+      int Bar;
+    };
+
+    void target(A Foo) {
+      int Baz = Foo.Bar;
+      // [[p]]
+    }
+  )";
+  runDataflow(
+      Code, [](llvm::ArrayRef<
+                   std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+                   Results,
+               ASTContext &ASTCtx) {
+        ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+        const Environment &Env = Results[0].second.Env;
+
+        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+        ASSERT_THAT(FooDecl, NotNull());
+
+        ASSERT_TRUE(FooDecl->getType()->isStructureType());
+        auto FooFields = FooDecl->getType()->getAsRecordDecl()->fields();
+
+        FieldDecl *BarDecl = nullptr;
+        for (FieldDecl *Field : FooFields) {
+          if (Field->getNameAsString() == "Bar") {
+            BarDecl = Field;
+          } else {
+            FAIL() << "Unexpected field: " << Field->getNameAsString();
+          }
+        }
+        ASSERT_THAT(BarDecl, NotNull());
+
+        const auto *FooLoc = cast<AggregateStorageLocation>(
+            Env.getStorageLocation(*FooDecl, SkipPast::None));
+        const auto *FooVal = cast<StructValue>(Env.getValue(*FooLoc));
+        const auto *BarVal = cast<IntegerValue>(&FooVal->getChild(*BarDecl));
+
+        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
+        ASSERT_THAT(BazDecl, NotNull());
+
+        EXPECT_EQ(Env.getValue(*BazDecl, SkipPast::None), BarVal);
+      });
+}
+
+TEST_F(TransferTest, ClassMember) {
+  std::string Code = R"(
+    class A {
+    public:
+      int Bar;
+    };
+
+    void target(A Foo) {
+      int Baz = Foo.Bar;
+      // [[p]]
+    }
+  )";
+  runDataflow(
+      Code, [](llvm::ArrayRef<
+                   std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+                   Results,
+               ASTContext &ASTCtx) {
+        ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+        const Environment &Env = Results[0].second.Env;
+
+        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+        ASSERT_THAT(FooDecl, NotNull());
+
+        ASSERT_TRUE(FooDecl->getType()->isClassType());
+        auto FooFields = FooDecl->getType()->getAsRecordDecl()->fields();
+
+        FieldDecl *BarDecl = nullptr;
+        for (FieldDecl *Field : FooFields) {
+          if (Field->getNameAsString() == "Bar") {
+            BarDecl = Field;
+          } else {
+            FAIL() << "Unexpected field: " << Field->getNameAsString();
+          }
+        }
+        ASSERT_THAT(BarDecl, NotNull());
+
+        const auto *FooLoc = cast<AggregateStorageLocation>(
+            Env.getStorageLocation(*FooDecl, SkipPast::None));
+        const auto *FooVal = cast<StructValue>(Env.getValue(*FooLoc));
+        const auto *BarVal = cast<IntegerValue>(&FooVal->getChild(*BarDecl));
+
+        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
+        ASSERT_THAT(BazDecl, NotNull());
+
+        EXPECT_EQ(Env.getValue(*BazDecl, SkipPast::None), BarVal);
+      });
+}
+
+TEST_F(TransferTest, ReferenceMember) {
+  std::string Code = R"(
+    struct A {
+      int &Bar;
+    };
+
+    void target(A Foo) {
+      int Baz = Foo.Bar;
+      // [[p]]
+    }
+  )";
+  runDataflow(
+      Code, [](llvm::ArrayRef<
+                   std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+                   Results,
+               ASTContext &ASTCtx) {
+        ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+        const Environment &Env = Results[0].second.Env;
+
+        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+        ASSERT_THAT(FooDecl, NotNull());
+
+        ASSERT_TRUE(FooDecl->getType()->isStructureType());
+        auto FooFields = FooDecl->getType()->getAsRecordDecl()->fields();
+
+        FieldDecl *BarDecl = nullptr;
+        for (FieldDecl *Field : FooFields) {
+          if (Field->getNameAsString() == "Bar") {
+            BarDecl = Field;
+          } else {
+            FAIL() << "Unexpected field: " << Field->getNameAsString();
+          }
+        }
+        ASSERT_THAT(BarDecl, NotNull());
+
+        const auto *FooLoc = cast<AggregateStorageLocation>(
+            Env.getStorageLocation(*FooDecl, SkipPast::None));
+        const auto *FooVal = cast<StructValue>(Env.getValue(*FooLoc));
+        const auto *BarVal = cast<ReferenceValue>(&FooVal->getChild(*BarDecl));
+        const auto *BarPointeeVal =
+            cast<IntegerValue>(Env.getValue(BarVal->getPointeeLoc()));
+
+        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
+        ASSERT_THAT(BazDecl, NotNull());
+
+        EXPECT_EQ(Env.getValue(*BazDecl, SkipPast::None), BarPointeeVal);
+      });
+}
+
+TEST_F(TransferTest, StructThisMember) {
+  std::string Code = R"(
+    struct A {
+      int Bar;
+
+      struct B {
+        int Baz;
+      };
+
+      B Qux;
+
+      void target() {
+        int Foo = Bar;
+        int Quux = Qux.Baz;
+        // [[p]]
+      }
+    };
+  )";
+  runDataflow(
+      Code, [](llvm::ArrayRef<
+                   std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+                   Results,
+               ASTContext &ASTCtx) {
+        ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+        const Environment &Env = Results[0].second.Env;
+
+        const auto *ThisLoc = dyn_cast<AggregateStorageLocation>(
+            Env.getThisPointeeStorageLocation());
+        ASSERT_THAT(ThisLoc, NotNull());
+
+        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
+        ASSERT_THAT(BarDecl, NotNull());
+
+        const auto *BarLoc =
+            cast<ScalarStorageLocation>(&ThisLoc->getChild(*BarDecl));
+        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(BarLoc));
+
+        const Value *BarVal = Env.getValue(*BarLoc);
+        ASSERT_TRUE(isa_and_nonnull<IntegerValue>(BarVal));
+
+        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+        ASSERT_THAT(FooDecl, NotNull());
+        EXPECT_EQ(Env.getValue(*FooDecl, SkipPast::None), BarVal);
+
+        const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux");
+        ASSERT_THAT(QuxDecl, NotNull());
+
+        ASSERT_TRUE(QuxDecl->getType()->isStructureType());
+        auto QuxFields = QuxDecl->getType()->getAsRecordDecl()->fields();
+
+        FieldDecl *BazDecl = nullptr;
+        for (FieldDecl *Field : QuxFields) {
+          if (Field->getNameAsString() == "Baz") {
+            BazDecl = Field;
+          } else {
+            FAIL() << "Unexpected field: " << Field->getNameAsString();
+          }
+        }
+        ASSERT_THAT(BazDecl, NotNull());
+
+        const auto *QuxLoc =
+            cast<AggregateStorageLocation>(&ThisLoc->getChild(*QuxDecl));
+        const auto *QuxVal = dyn_cast<StructValue>(Env.getValue(*QuxLoc));
+        ASSERT_THAT(QuxVal, NotNull());
+
+        const auto *BazLoc =
+            cast<ScalarStorageLocation>(&QuxLoc->getChild(*BazDecl));
+        const auto *BazVal = cast<IntegerValue>(&QuxVal->getChild(*BazDecl));
+        EXPECT_EQ(Env.getValue(*BazLoc), BazVal);
+
+        const ValueDecl *QuuxDecl = findValueDecl(ASTCtx, "Quux");
+        ASSERT_THAT(QuuxDecl, NotNull());
+        EXPECT_EQ(Env.getValue(*QuuxDecl, SkipPast::None), BazVal);
+      });
+}
+
+TEST_F(TransferTest, ClassThisMember) {
+  std::string Code = R"(
+    class A {
+      int Bar;
+
+      class B {
+      public:
+        int Baz;
+      };
+
+      B Qux;
+
+      void target() {
+        int Foo = Bar;
+        int Quux = Qux.Baz;
+        // [[p]]
+      }
+    };
+  )";
+  runDataflow(
+      Code, [](llvm::ArrayRef<
+                   std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+                   Results,
+               ASTContext &ASTCtx) {
+        ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+        const Environment &Env = Results[0].second.Env;
+
+        const auto *ThisLoc =
+            cast<AggregateStorageLocation>(Env.getThisPointeeStorageLocation());
+
+        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
+        ASSERT_THAT(BarDecl, NotNull());
+
+        const auto *BarLoc =
+            cast<ScalarStorageLocation>(&ThisLoc->getChild(*BarDecl));
+        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(BarLoc));
+
+        const Value *BarVal = Env.getValue(*BarLoc);
+        ASSERT_TRUE(isa_and_nonnull<IntegerValue>(BarVal));
+
+        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+        ASSERT_THAT(FooDecl, NotNull());
+        EXPECT_EQ(Env.getValue(*FooDecl, SkipPast::None), BarVal);
+
+        const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux");
+        ASSERT_THAT(QuxDecl, NotNull());
+
+        ASSERT_TRUE(QuxDecl->getType()->isClassType());
+        auto QuxFields = QuxDecl->getType()->getAsRecordDecl()->fields();
+
+        FieldDecl *BazDecl = nullptr;
+        for (FieldDecl *Field : QuxFields) {
+          if (Field->getNameAsString() == "Baz") {
+            BazDecl = Field;
+          } else {
+            FAIL() << "Unexpected field: " << Field->getNameAsString();
+          }
+        }
+        ASSERT_THAT(BazDecl, NotNull());
+
+        const auto *QuxLoc =
+            cast<AggregateStorageLocation>(&ThisLoc->getChild(*QuxDecl));
+        const auto *QuxVal = dyn_cast<StructValue>(Env.getValue(*QuxLoc));
+        ASSERT_THAT(QuxVal, NotNull());
+
+        const auto *BazLoc =
+            cast<ScalarStorageLocation>(&QuxLoc->getChild(*BazDecl));
+        const auto *BazVal = cast<IntegerValue>(&QuxVal->getChild(*BazDecl));
+        EXPECT_EQ(Env.getValue(*BazLoc), BazVal);
+
+        const ValueDecl *QuuxDecl = findValueDecl(ASTCtx, "Quux");
+        ASSERT_THAT(QuuxDecl, NotNull());
+        EXPECT_EQ(Env.getValue(*QuuxDecl, SkipPast::None), BazVal);
+      });
+}
+
 } // namespace


        


More information about the cfe-commits mailing list