//===- unittests/Analysis/FlowSensitive/TransferTest.cpp ------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "TestingSupport.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/NoopAnalysis.h"
#include "clang/Analysis/FlowSensitive/RecordOps.h"
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
#include "clang/Analysis/FlowSensitive/Value.h"
#include "clang/Basic/LangStandard.h"
#include "clang/Testing/TestAST.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Testing/Support/Error.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <optional>
#include <string>
#include <utility>

namespace clang {
namespace dataflow {
namespace {
AST_MATCHER(FunctionDecl, isTemplated) { return Node.isTemplated(); }
} // namespace
} // namespace dataflow
} // namespace clang

namespace {

using namespace clang;
using namespace dataflow;
using namespace test;
using ::testing::Eq;
using ::testing::IsNull;
using ::testing::Ne;
using ::testing::NotNull;
using ::testing::UnorderedElementsAre;

// Declares a minimal coroutine library.
constexpr llvm::StringRef CoroutineLibrary = R"cc(
struct promise;
struct task;

namespace std {
template <class, class...>
struct coroutine_traits {};
template <>
struct coroutine_traits<task> {
    using promise_type = promise;
};

template <class Promise = void>
struct coroutine_handle {
    static constexpr coroutine_handle from_address(void *addr) { return {}; }
};
}  // namespace std

struct awaitable {
    bool await_ready() const noexcept;
    void await_suspend(std::coroutine_handle<promise>) const noexcept;
    void await_resume() const noexcept;
};
struct task {};
struct promise {
    task get_return_object();
    awaitable initial_suspend();
    awaitable final_suspend() noexcept;
    void unhandled_exception();
    void return_void();
};
)cc";

void runDataflow(
    llvm::StringRef Code,
    std::function<
        void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
             ASTContext &)>
        VerifyResults,
    DataflowAnalysisOptions Options,
    LangStandard::Kind Std = LangStandard::lang_cxx17,
    llvm::StringRef TargetFun = "target") {
  ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(Code, VerifyResults, Options,
                                                  Std, TargetFun),
                    llvm::Succeeded());
}

void runDataflow(
    llvm::StringRef Code,
    std::function<
        void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
             ASTContext &)>
        VerifyResults,
    LangStandard::Kind Std = LangStandard::lang_cxx17,
    bool ApplyBuiltinTransfer = true, llvm::StringRef TargetFun = "target") {
  runDataflow(Code, std::move(VerifyResults),
              {ApplyBuiltinTransfer ? BuiltinOptions{}
                                    : std::optional<BuiltinOptions>()},
              Std, TargetFun);
}

void runDataflowOnLambda(
    llvm::StringRef Code,
    std::function<
        void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
             ASTContext &)>
        VerifyResults,
    DataflowAnalysisOptions Options,
    LangStandard::Kind Std = LangStandard::lang_cxx17) {
  ASSERT_THAT_ERROR(
      checkDataflowWithNoopAnalysis(
          Code,
          ast_matchers::hasDeclContext(
              ast_matchers::cxxRecordDecl(ast_matchers::isLambda())),
          VerifyResults, Options, Std),
      llvm::Succeeded());
}

void runDataflowOnLambda(
    llvm::StringRef Code,
    std::function<
        void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
             ASTContext &)>
        VerifyResults,
    LangStandard::Kind Std = LangStandard::lang_cxx17,
    bool ApplyBuiltinTransfer = true) {
  runDataflowOnLambda(Code, std::move(VerifyResults),
                      {ApplyBuiltinTransfer ? BuiltinOptions{}
                                            : std::optional<BuiltinOptions>()},
                      Std);
}

const Formula &getFormula(const ValueDecl &D, const Environment &Env) {
  return cast<BoolValue>(Env.getValue(D))->formula();
}

TEST(TransferTest, CNotSupported) {
  TestInputs Inputs("void target() {}");
  Inputs.Language = TestLanguage::Lang_C89;
  clang::TestAST AST(Inputs);
  const auto *Target =
      cast<FunctionDecl>(test::findValueDecl(AST.context(), "target"));
  ASSERT_THAT_ERROR(AdornedCFG::build(*Target).takeError(),
                    llvm::FailedWithMessage("Can only analyze C++"));
}

TEST(TransferTest, ObjectiveCNotSupported) {
  TestInputs Inputs("void target() {}");
  Inputs.Language = TestLanguage::Lang_OBJC;
  clang::TestAST AST(Inputs);
  const auto *Target =
      cast<FunctionDecl>(test::findValueDecl(AST.context(), "target"));
  ASSERT_THAT_ERROR(AdornedCFG::build(*Target).takeError(),
                    llvm::FailedWithMessage("Can only analyze C++"));
}

TEST(TransferTest, ObjectiveCXXNotSupported) {
  TestInputs Inputs("void target() {}");
  Inputs.Language = TestLanguage::Lang_OBJCXX;
  clang::TestAST AST(Inputs);
  const auto *Target =
      cast<FunctionDecl>(test::findValueDecl(AST.context(), "target"));
  ASSERT_THAT_ERROR(AdornedCFG::build(*Target).takeError(),
                    llvm::FailedWithMessage("Can only analyze C++"));
}

TEST(TransferTest, IntVarDeclNotTrackedWhenTransferDisabled) {
  std::string Code = R"(
    void target() {
      int Foo;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        EXPECT_EQ(Env.getStorageLocation(*FooDecl), nullptr);
      },
      LangStandard::lang_cxx17,
      /*ApplyBuiltinTransfer=*/false);
}

TEST(TransferTest, BoolVarDecl) {
  std::string Code = R"(
    void target() {
      bool Foo;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));

        const Value *FooVal = Env.getValue(*FooLoc);
        EXPECT_TRUE(isa_and_nonnull<BoolValue>(FooVal));
      });
}

TEST(TransferTest, IntVarDecl) {
  std::string Code = R"(
    void target() {
      int Foo;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));

        const Value *FooVal = Env.getValue(*FooLoc);
        EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));
      });
}

TEST(TransferTest, StructIncomplete) {
  std::string Code = R"(
    struct A;

    void target() {
      A* Foo;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());
        auto *FooValue = dyn_cast_or_null<PointerValue>(Env.getValue(*FooDecl));
        ASSERT_THAT(FooValue, NotNull());

        EXPECT_TRUE(isa<RecordStorageLocation>(FooValue->getPointeeLoc()));
      });
}

// As a memory optimization, we prevent modeling fields nested below a certain
// level (currently, depth 3). This test verifies this lack of modeling. We also
// include a regression test for the case that the unmodeled field is a
// reference to a struct; previously, we crashed when accessing such a field.
TEST(TransferTest, StructFieldUnmodeled) {
  std::string Code = R"(
    struct S { int X; };
    S GlobalS;
    struct A { S &Unmodeled = GlobalS; };
    struct B { A F3; };
    struct C { B F2; };
    struct D { C F1; };

    void target() {
      D Bar;
      A &Foo = Bar.F1.F2.F3;
      int Zab = Foo.Unmodeled.X;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());
        QualType FooReferentType = FooDecl->getType()->getPointeeType();
        ASSERT_TRUE(FooReferentType->isStructureType());
        auto FooFields = FooReferentType->getAsRecordDecl()->fields();

        FieldDecl *UnmodeledDecl = nullptr;
        for (FieldDecl *Field : FooFields) {
          if (Field->getNameAsString() == "Unmodeled") {
            UnmodeledDecl = Field;
          } else {
            FAIL() << "Unexpected field: " << Field->getNameAsString();
          }
        }
        ASSERT_THAT(UnmodeledDecl, NotNull());

        const auto *FooLoc =
            cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl));
        const auto &UnmodeledLoc =
            *cast<RecordStorageLocation>(FooLoc->getChild(*UnmodeledDecl));
        StorageLocation &UnmodeledXLoc = getFieldLoc(UnmodeledLoc, "X", ASTCtx);
        EXPECT_EQ(Env.getValue(UnmodeledXLoc), nullptr);

        const ValueDecl *ZabDecl = findValueDecl(ASTCtx, "Zab");
        ASSERT_THAT(ZabDecl, NotNull());
        EXPECT_THAT(Env.getValue(*ZabDecl), NotNull());
      });
}

TEST(TransferTest, StructVarDecl) {
  std::string Code = R"(
    struct A {
      int Bar;
    };

    void target() {
      A Foo;
      (void)Foo.Bar;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        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<RecordStorageLocation>(Env.getStorageLocation(*FooDecl));
        EXPECT_TRUE(isa<IntegerValue>(getFieldValue(FooLoc, *BarDecl, Env)));
      });
}

TEST(TransferTest, StructVarDeclWithInit) {
  std::string Code = R"(
    struct A {
      int Bar;
    };

    A Gen();

    void target() {
      A Foo = Gen();
      (void)Foo.Bar;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        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<RecordStorageLocation>(Env.getStorageLocation(*FooDecl));
        EXPECT_TRUE(isa<IntegerValue>(getFieldValue(FooLoc, *BarDecl, Env)));
      });
}

TEST(TransferTest, StructArrayVarDecl) {
  std::string Code = R"(
    struct A {};

    void target() {
      A Array[2];
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *ArrayDecl = findValueDecl(ASTCtx, "Array");

        // We currently don't create values for arrays.
        ASSERT_THAT(Env.getValue(*ArrayDecl), IsNull());
      });
}

TEST(TransferTest, ClassVarDecl) {
  std::string Code = R"(
    class A {
     public:
      int Bar;
    };

    void target() {
      A Foo;
      (void)Foo.Bar;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        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<RecordStorageLocation>(Env.getStorageLocation(*FooDecl));
        EXPECT_TRUE(isa<IntegerValue>(getFieldValue(FooLoc, *BarDecl, Env)));
      });
}

TEST(TransferTest, ReferenceVarDecl) {
  std::string Code = R"(
    struct A {};

    A &getA();

    void target() {
      A &Foo = getA();
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<RecordStorageLocation>(FooLoc));
      });
}

TEST(TransferTest, SelfReferentialReferenceVarDecl) {
  std::string Code = R"(
    struct A;

    struct B {};

    struct C {
      A &FooRef;
      A *FooPtr;
      B &BazRef;
      B *BazPtr;
    };

    struct A {
      C &Bar;
    };

    A &getA();

    void target() {
      A &Foo = getA();
      (void)Foo.Bar.FooRef;
      (void)Foo.Bar.FooPtr;
      (void)Foo.Bar.BazRef;
      (void)Foo.Bar.BazPtr;
      // [[p]]
    }
  )";
  runDataflow(Code, [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>>
                           &Results,
                       ASTContext &ASTCtx) {
    ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
    const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

    const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
    ASSERT_THAT(FooDecl, NotNull());

    ASSERT_TRUE(FooDecl->getType()->isReferenceType());
    ASSERT_TRUE(FooDecl->getType().getNonReferenceType()->isStructureType());
    const auto FooFields =
        FooDecl->getType().getNonReferenceType()->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());

    ASSERT_TRUE(BarDecl->getType()->isReferenceType());
    ASSERT_TRUE(BarDecl->getType().getNonReferenceType()->isStructureType());
    const auto BarFields =
        BarDecl->getType().getNonReferenceType()->getAsRecordDecl()->fields();

    FieldDecl *FooRefDecl = nullptr;
    FieldDecl *FooPtrDecl = nullptr;
    FieldDecl *BazRefDecl = nullptr;
    FieldDecl *BazPtrDecl = nullptr;
    for (FieldDecl *Field : BarFields) {
      if (Field->getNameAsString() == "FooRef") {
        FooRefDecl = Field;
      } else if (Field->getNameAsString() == "FooPtr") {
        FooPtrDecl = Field;
      } else if (Field->getNameAsString() == "BazRef") {
        BazRefDecl = Field;
      } else if (Field->getNameAsString() == "BazPtr") {
        BazPtrDecl = Field;
      } else {
        FAIL() << "Unexpected field: " << Field->getNameAsString();
      }
    }
    ASSERT_THAT(FooRefDecl, NotNull());
    ASSERT_THAT(FooPtrDecl, NotNull());
    ASSERT_THAT(BazRefDecl, NotNull());
    ASSERT_THAT(BazPtrDecl, NotNull());

    const auto &FooLoc =
        *cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl));

    const auto &BarLoc =
        *cast<RecordStorageLocation>(FooLoc.getChild(*BarDecl));

    const auto &FooReferentLoc =
        *cast<RecordStorageLocation>(BarLoc.getChild(*FooRefDecl));
    EXPECT_EQ(Env.getValue(*cast<RecordStorageLocation>(
                                FooReferentLoc.getChild(*BarDecl))
                                ->getChild(*FooPtrDecl)),
              nullptr);

    const auto &FooPtrVal =
        *cast<PointerValue>(getFieldValue(&BarLoc, *FooPtrDecl, Env));
    const auto &FooPtrPointeeLoc =
        cast<RecordStorageLocation>(FooPtrVal.getPointeeLoc());
    EXPECT_EQ(Env.getValue(*cast<RecordStorageLocation>(
                                FooPtrPointeeLoc.getChild(*BarDecl))
                                ->getChild(*FooPtrDecl)),
              nullptr);

    EXPECT_TRUE(isa<PointerValue>(getFieldValue(&BarLoc, *BazPtrDecl, Env)));
  });
}

TEST(TransferTest, PointerVarDecl) {
  std::string Code = R"(
    struct A {};

    A *getA();

    void target() {
      A *Foo = getA();
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));

        const PointerValue *FooVal = cast<PointerValue>(Env.getValue(*FooLoc));
        const StorageLocation &FooPointeeLoc = FooVal->getPointeeLoc();
        EXPECT_TRUE(isa<RecordStorageLocation>(&FooPointeeLoc));
      });
}

TEST(TransferTest, SelfReferentialPointerVarDecl) {
  std::string Code = R"(
    struct A;

    struct B {};

    struct C {
      A &FooRef;
      A *FooPtr;
      B &BazRef;
      B *BazPtr;
    };

    struct A {
      C *Bar;
    };

    A *getA();

    void target() {
      A *Foo = getA();
      (void)Foo->Bar->FooRef;
      (void)Foo->Bar->FooPtr;
      (void)Foo->Bar->BazRef;
      (void)Foo->Bar->BazPtr;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        ASSERT_TRUE(FooDecl->getType()->isPointerType());
        ASSERT_TRUE(FooDecl->getType()
                        ->getAs<PointerType>()
                        ->getPointeeType()
                        ->isStructureType());
        const auto FooFields = FooDecl->getType()
                                   ->getAs<PointerType>()
                                   ->getPointeeType()
                                   ->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());

        ASSERT_TRUE(BarDecl->getType()->isPointerType());
        ASSERT_TRUE(BarDecl->getType()
                        ->getAs<PointerType>()
                        ->getPointeeType()
                        ->isStructureType());
        const auto BarFields = BarDecl->getType()
                                   ->getAs<PointerType>()
                                   ->getPointeeType()
                                   ->getAsRecordDecl()
                                   ->fields();

        FieldDecl *FooRefDecl = nullptr;
        FieldDecl *FooPtrDecl = nullptr;
        FieldDecl *BazRefDecl = nullptr;
        FieldDecl *BazPtrDecl = nullptr;
        for (FieldDecl *Field : BarFields) {
          if (Field->getNameAsString() == "FooRef") {
            FooRefDecl = Field;
          } else if (Field->getNameAsString() == "FooPtr") {
            FooPtrDecl = Field;
          } else if (Field->getNameAsString() == "BazRef") {
            BazRefDecl = Field;
          } else if (Field->getNameAsString() == "BazPtr") {
            BazPtrDecl = Field;
          } else {
            FAIL() << "Unexpected field: " << Field->getNameAsString();
          }
        }
        ASSERT_THAT(FooRefDecl, NotNull());
        ASSERT_THAT(FooPtrDecl, NotNull());
        ASSERT_THAT(BazRefDecl, NotNull());
        ASSERT_THAT(BazPtrDecl, NotNull());

        const auto &FooLoc =
            *cast<ScalarStorageLocation>(Env.getStorageLocation(*FooDecl));
        const auto &FooVal = *cast<PointerValue>(Env.getValue(FooLoc));
        const auto &FooPointeeLoc =
            cast<RecordStorageLocation>(FooVal.getPointeeLoc());

        const auto &BarVal =
            *cast<PointerValue>(getFieldValue(&FooPointeeLoc, *BarDecl, Env));
        const auto &BarPointeeLoc =
            cast<RecordStorageLocation>(BarVal.getPointeeLoc());

        const auto &FooPtrVal = *cast<PointerValue>(
            getFieldValue(&BarPointeeLoc, *FooPtrDecl, Env));
        const auto &FooPtrPointeeLoc =
            cast<RecordStorageLocation>(FooPtrVal.getPointeeLoc());
        EXPECT_EQ(Env.getValue(*FooPtrPointeeLoc.getChild(*BarDecl)), nullptr);

        EXPECT_TRUE(
            isa<PointerValue>(getFieldValue(&BarPointeeLoc, *BazPtrDecl, Env)));
      });
}

TEST(TransferTest, DirectlySelfReferentialReference) {
  std::string Code = R"(
    struct target {
      target() {
        (void)0;
        // [[p]]
      }
      target &self = *this;
    };
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
        const ValueDecl *SelfDecl = findValueDecl(ASTCtx, "self");

        auto *ThisLoc = Env.getThisPointeeStorageLocation();
        ASSERT_EQ(ThisLoc->getChild(*SelfDecl), ThisLoc);
      });
}

TEST(TransferTest, MultipleVarsDecl) {
  std::string Code = R"(
    void target() {
      int Foo, Bar;
      (void)0;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));

        const StorageLocation *BarLoc = Env.getStorageLocation(*BarDecl);
        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(BarLoc));

        const Value *FooVal = Env.getValue(*FooLoc);
        EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));

        const Value *BarVal = Env.getValue(*BarLoc);
        EXPECT_TRUE(isa_and_nonnull<IntegerValue>(BarVal));
      });
}

TEST(TransferTest, JoinVarDecl) {
  std::string Code = R"(
    void target(bool B) {
      int Foo;
      // [[p1]]
      if (B) {
        int Bar;
        // [[p2]]
      } else {
        int Baz;
        // [[p3]]
      }
      (void)0;
      // [[p4]]
    }
  )";
  runDataflow(Code, [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>>
                           &Results,
                       ASTContext &ASTCtx) {
    ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1", "p2", "p3", "p4"));

    const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
    ASSERT_THAT(FooDecl, NotNull());

    const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
    ASSERT_THAT(BarDecl, NotNull());

    const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
    ASSERT_THAT(BazDecl, NotNull());

    const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");

    const StorageLocation *FooLoc = Env1.getStorageLocation(*FooDecl);
    EXPECT_THAT(FooLoc, NotNull());
    EXPECT_THAT(Env1.getStorageLocation(*BarDecl), IsNull());
    EXPECT_THAT(Env1.getStorageLocation(*BazDecl), IsNull());

    const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2");
    EXPECT_EQ(Env2.getStorageLocation(*FooDecl), FooLoc);
    EXPECT_THAT(Env2.getStorageLocation(*BarDecl), NotNull());
    EXPECT_THAT(Env2.getStorageLocation(*BazDecl), IsNull());

    const Environment &Env3 = getEnvironmentAtAnnotation(Results, "p3");
    EXPECT_EQ(Env3.getStorageLocation(*FooDecl), FooLoc);
    EXPECT_THAT(Env3.getStorageLocation(*BarDecl), IsNull());
    EXPECT_THAT(Env3.getStorageLocation(*BazDecl), NotNull());

    const Environment &Env4 = getEnvironmentAtAnnotation(Results, "p4");
    EXPECT_EQ(Env4.getStorageLocation(*FooDecl), FooLoc);
    EXPECT_THAT(Env4.getStorageLocation(*BarDecl), IsNull());
    EXPECT_THAT(Env4.getStorageLocation(*BazDecl), IsNull());
  });
}

TEST(TransferTest, BinaryOperatorAssign) {
  std::string Code = R"(
    void target() {
      int Foo;
      int Bar;
      (Bar) = (Foo);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const Value *FooVal = Env.getValue(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        EXPECT_EQ(Env.getValue(*BarDecl), FooVal);
      });
}

TEST(TransferTest, BinaryOperatorAssignIntegerLiteral) {
  std::string Code = R"(
    void target() {
      int Foo = 1;
      // [[before]]
      Foo = 2;
      // [[after]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Before =
            getEnvironmentAtAnnotation(Results, "before");
        const Environment &After = getEnvironmentAtAnnotation(Results, "after");

        const auto &ValBefore =
            getValueForDecl<IntegerValue>(ASTCtx, Before, "Foo");
        const auto &ValAfter =
            getValueForDecl<IntegerValue>(ASTCtx, After, "Foo");
        EXPECT_NE(&ValBefore, &ValAfter);
      });
}

TEST(TransferTest, VarDeclInitAssign) {
  std::string Code = R"(
    void target() {
      int Foo;
      int Bar = Foo;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const Value *FooVal = Env.getValue(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        EXPECT_EQ(Env.getValue(*BarDecl), FooVal);
      });
}

TEST(TransferTest, VarDeclInitAssignChained) {
  std::string Code = R"(
    void target() {
      int Foo;
      int Bar;
      int Baz = (Bar = Foo);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const Value *FooVal = Env.getValue(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        EXPECT_EQ(Env.getValue(*BarDecl), FooVal);
        EXPECT_EQ(Env.getValue(*BazDecl), FooVal);
      });
}

TEST(TransferTest, VarDeclInitAssignPtrDeref) {
  std::string Code = R"(
    void target() {
      int Foo;
      int *Bar;
      *(Bar) = Foo;
      int Baz = *(Bar);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const Value *FooVal = Env.getValue(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const auto *BarVal = cast<PointerValue>(Env.getValue(*BarDecl));
        EXPECT_EQ(Env.getValue(BarVal->getPointeeLoc()), FooVal);

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        EXPECT_EQ(Env.getValue(*BazDecl), FooVal);
      });
}

TEST(TransferTest, AssignToAndFromReference) {
  std::string Code = R"(
    void target() {
      int Foo;
      int Bar;
      int &Baz = Foo;
      // [[p1]]
      Baz = Bar;
      int Qux = Baz;
      int &Quux = Baz;
      // [[p2]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1", "p2"));
        const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
        const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const Value *FooVal = Env1.getValue(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const Value *BarVal = Env1.getValue(*BarDecl);
        ASSERT_TRUE(isa_and_nonnull<IntegerValue>(BarVal));

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        EXPECT_EQ(Env1.getValue(*BazDecl), FooVal);

        EXPECT_EQ(Env2.getValue(*BazDecl), BarVal);
        EXPECT_EQ(Env2.getValue(*FooDecl), BarVal);

        const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux");
        ASSERT_THAT(QuxDecl, NotNull());
        EXPECT_EQ(Env2.getValue(*QuxDecl), BarVal);

        const ValueDecl *QuuxDecl = findValueDecl(ASTCtx, "Quux");
        ASSERT_THAT(QuuxDecl, NotNull());
        EXPECT_EQ(Env2.getValue(*QuuxDecl), BarVal);
      });
}

TEST(TransferTest, MultipleParamDecls) {
  std::string Code = R"(
    void target(int Foo, int Bar) {
      (void)0;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl);
        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);
        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(BarLoc));

        const Value *BarVal = Env.getValue(*BarLoc);
        EXPECT_TRUE(isa_and_nonnull<IntegerValue>(BarVal));
      });
}

TEST(TransferTest, StructParamDecl) {
  std::string Code = R"(
    struct A {
      int Bar;
    };

    void target(A Foo) {
      (void)Foo.Bar;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        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<RecordStorageLocation>(Env.getStorageLocation(*FooDecl));
        EXPECT_TRUE(isa<IntegerValue>(getFieldValue(FooLoc, *BarDecl, Env)));
      });
}

TEST(TransferTest, ReferenceParamDecl) {
  std::string Code = R"(
    struct A {};

    void target(A &Foo) {
      (void)0;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<RecordStorageLocation>(FooLoc));
      });
}

TEST(TransferTest, PointerParamDecl) {
  std::string Code = R"(
    struct A {};

    void target(A *Foo) {
      (void)0;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));

        const PointerValue *FooVal = cast<PointerValue>(Env.getValue(*FooLoc));
        const StorageLocation &FooPointeeLoc = FooVal->getPointeeLoc();
        EXPECT_TRUE(isa<RecordStorageLocation>(&FooPointeeLoc));
      });
}

TEST(TransferTest, StructMember) {
  std::string Code = R"(
    struct A {
      int Bar;
    };

    void target(A Foo) {
      int Baz = Foo.Bar;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        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<RecordStorageLocation>(Env.getStorageLocation(*FooDecl));
        const auto *BarVal =
            cast<IntegerValue>(getFieldValue(FooLoc, *BarDecl, Env));

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        EXPECT_EQ(Env.getValue(*BazDecl), BarVal);
      });
}

TEST(TransferTest, StructMemberEnum) {
  std::string Code = R"(
    struct A {
      int Bar;
      enum E { ONE, TWO };
    };

    void target(A Foo) {
      A::E Baz = Foo.ONE;
      // [[p]]
    }
  )";
  // Minimal expectations -- we're just testing that it doesn't crash, since
  // enums aren't interpreted.
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        EXPECT_THAT(Results.keys(), UnorderedElementsAre("p"));
      });
}

TEST(TransferTest, DerivedBaseMemberClass) {
  std::string Code = R"(
    class A {
      int ADefault;
    protected:
      int AProtected;
    private:
      int APrivate;
    public:
      int APublic;

    private:
      friend void target();
    };

    class B : public A {
      int BDefault;
    protected:
      int BProtected;
    private:
      int BPrivate;

    private:
      friend void target();
    };

    void target() {
      B Foo;
      (void)Foo.ADefault;
      (void)Foo.AProtected;
      (void)Foo.APrivate;
      (void)Foo.APublic;
      (void)Foo.BDefault;
      (void)Foo.BProtected;
      (void)Foo.BPrivate;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());
        ASSERT_TRUE(FooDecl->getType()->isRecordType());

        // Derived-class fields.
        const FieldDecl *BDefaultDecl = nullptr;
        const FieldDecl *BProtectedDecl = nullptr;
        const FieldDecl *BPrivateDecl = nullptr;
        for (const FieldDecl *Field :
             FooDecl->getType()->getAsRecordDecl()->fields()) {
          if (Field->getNameAsString() == "BDefault") {
            BDefaultDecl = Field;
          } else if (Field->getNameAsString() == "BProtected") {
            BProtectedDecl = Field;
          } else if (Field->getNameAsString() == "BPrivate") {
            BPrivateDecl = Field;
          } else {
            FAIL() << "Unexpected field: " << Field->getNameAsString();
          }
        }
        ASSERT_THAT(BDefaultDecl, NotNull());
        ASSERT_THAT(BProtectedDecl, NotNull());
        ASSERT_THAT(BPrivateDecl, NotNull());

        // Base-class fields.
        const FieldDecl *ADefaultDecl = nullptr;
        const FieldDecl *APrivateDecl = nullptr;
        const FieldDecl *AProtectedDecl = nullptr;
        const FieldDecl *APublicDecl = nullptr;
        for (const clang::CXXBaseSpecifier &Base :
             FooDecl->getType()->getAsCXXRecordDecl()->bases()) {
          QualType BaseType = Base.getType();
          ASSERT_TRUE(BaseType->isRecordType());
          for (const FieldDecl *Field : BaseType->getAsRecordDecl()->fields()) {
            if (Field->getNameAsString() == "ADefault") {
              ADefaultDecl = Field;
            } else if (Field->getNameAsString() == "AProtected") {
              AProtectedDecl = Field;
            } else if (Field->getNameAsString() == "APrivate") {
              APrivateDecl = Field;
            } else if (Field->getNameAsString() == "APublic") {
              APublicDecl = Field;
            } else {
              FAIL() << "Unexpected field: " << Field->getNameAsString();
            }
          }
        }
        ASSERT_THAT(ADefaultDecl, NotNull());
        ASSERT_THAT(AProtectedDecl, NotNull());
        ASSERT_THAT(APrivateDecl, NotNull());
        ASSERT_THAT(APublicDecl, NotNull());

        ASSERT_TRUE(
            isa<RecordStorageLocation>(Env.getStorageLocation(*FooDecl)));
      });
}

static void derivedBaseMemberExpectations(
    const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
    ASTContext &ASTCtx) {
  ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
  const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

  const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
  ASSERT_THAT(FooDecl, NotNull());

  ASSERT_TRUE(FooDecl->getType()->isRecordType());
  const FieldDecl *BarDecl = nullptr;
  for (const clang::CXXBaseSpecifier &Base :
       FooDecl->getType()->getAsCXXRecordDecl()->bases()) {
    QualType BaseType = Base.getType();
    ASSERT_TRUE(BaseType->isStructureType());

    for (const FieldDecl *Field : BaseType->getAsRecordDecl()->fields()) {
      if (Field->getNameAsString() == "Bar") {
        BarDecl = Field;
      } else {
        FAIL() << "Unexpected field: " << Field->getNameAsString();
      }
    }
  }
  ASSERT_THAT(BarDecl, NotNull());

  const auto &FooLoc =
      *cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl));
  EXPECT_NE(Env.getValue(*FooLoc.getChild(*BarDecl)), nullptr);
}

TEST(TransferTest, DerivedBaseMemberStructDefault) {
  std::string Code = R"(
    struct A {
      int Bar;
    };
    struct B : public A {
    };

    void target() {
      B Foo;
      (void)Foo.Bar;
      // [[p]]
    }
  )";
  runDataflow(Code, derivedBaseMemberExpectations);
}

TEST(TransferTest, DerivedBaseMemberPrivateFriend) {
  // Include an access to `Foo.Bar` to verify the analysis doesn't crash on that
  // access.
  std::string Code = R"(
    struct A {
    private:
      friend void target();
      int Bar;
    };
    struct B : public A {
    };

    void target() {
      B Foo;
      (void)Foo.Bar;
      // [[p]]
    }
  )";
  runDataflow(Code, derivedBaseMemberExpectations);
}

TEST(TransferTest, ClassMember) {
  std::string Code = R"(
    class A {
    public:
      int Bar;
    };

    void target(A Foo) {
      int Baz = Foo.Bar;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        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<RecordStorageLocation>(Env.getStorageLocation(*FooDecl));
        const auto *BarVal =
            cast<IntegerValue>(getFieldValue(FooLoc, *BarDecl, Env));

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        EXPECT_EQ(Env.getValue(*BazDecl), BarVal);
      });
}

TEST(TransferTest, BaseClassInitializer) {
  using ast_matchers::cxxConstructorDecl;
  using ast_matchers::hasName;
  using ast_matchers::ofClass;

  std::string Code = R"(
    class A {
    public:
      A(int I) : Bar(I) {}
      int Bar;
    };

    class B : public A {
    public:
      B(int I) : A(I) {
        (void)0;
        // [[p]]
      }
    };
  )";
  ASSERT_THAT_ERROR(
      checkDataflow<NoopAnalysis>(
          AnalysisInputs<NoopAnalysis>(
              Code, cxxConstructorDecl(ofClass(hasName("B"))),
              [](ASTContext &C, Environment &) { return NoopAnalysis(C); })
              .withASTBuildArgs(
                  {"-fsyntax-only", "-fno-delayed-template-parsing",
                   "-std=" + std::string(LangStandard::getLangStandardForKind(
                                             LangStandard::lang_cxx17)
                                             .getName())}),
          /*VerifyResults=*/
          [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
             const AnalysisOutputs &) {
            // Regression test to verify that base-class initializers do not
            // trigger an assertion. If we add support for such initializers in
            // the future, we can expand this test to check more specific
            // properties.
            EXPECT_THAT(Results.keys(), UnorderedElementsAre("p"));
          }),
      llvm::Succeeded());
}

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
  // constructor.
  std::string Code = R"(
    struct target {
      target() {
        0;
        // [[p]]
        // Mention the field so it is modeled;
        Val;
      }

      int Val;
    };
 )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
        EXPECT_EQ(getFieldValue(Env.getThisPointeeStorageLocation(), "Val",
                                ASTCtx, Env),
                  nullptr);
      });
}

TEST(TransferTest, FieldsDontHaveValuesInConstructorWithBaseClass) {
  // See above, but for a class with a base class.
  std::string Code = R"(
    struct Base {
        int BaseVal;
    };

    struct target  : public Base {
      target() {
        0;
        // [[p]]
        // Mention the fields so they are modeled.
        BaseVal;
        Val;
      }

      int Val;
    };
 )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
        // The field of the base class should already have been initialized with
        // a value by the base constructor.
        EXPECT_NE(getFieldValue(Env.getThisPointeeStorageLocation(), "BaseVal",
                                ASTCtx, Env),
                  nullptr);
        EXPECT_EQ(getFieldValue(Env.getThisPointeeStorageLocation(), "Val",
                                ASTCtx, Env),
                  nullptr);
      });
}

TEST(TransferTest, StructModeledFieldsWithAccessor) {
  std::string Code = R"(
    class S {
      int *Ptr;
      int *PtrNonConst;
      int Int;
      int IntWithInc;
      int IntNotAccessed;
      int IntRef;
    public:
      int *getPtr() const { return Ptr; }
      int *getPtrNonConst() { return PtrNonConst; }
      int getInt(int i) const { return Int; }
      int getWithInc(int i) { IntWithInc += i; return IntWithInc; }
      int getIntNotAccessed() const { return IntNotAccessed; }
      int getIntNoDefinition() const;
      int &getIntRef() { return IntRef; }
      void returnVoid() const { return; }
    };

    void target() {
      S s;
      int *p1 = s.getPtr();
      int *p2 = s.getPtrNonConst();
      int i1 = s.getInt(1);
      int i2 = s.getWithInc(1);
      int i3 = s.getIntNoDefinition();
      int &iref = s.getIntRef();

      // Regression test: Don't crash on an indirect call (which doesn't have
      // an associated `CXXMethodDecl`).
      auto ptr_to_member_fn = &S::getPtr;
      p1 = (s.*ptr_to_member_fn)();

      // Regression test: Don't crash on a return statement without a value.
      s.returnVoid();
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env =
              getEnvironmentAtAnnotation(Results, "p");
        auto &SLoc = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s");
        std::vector<const ValueDecl*> Fields;
        for (auto [Field, _] : SLoc.children())
          Fields.push_back(Field);
        // Only the fields that have simple accessor methods (that have a
        // single statement body that returns the member variable) should be
        // modeled.
        ASSERT_THAT(Fields, UnorderedElementsAre(
            findValueDecl(ASTCtx, "Ptr"), findValueDecl(ASTCtx, "PtrNonConst"),
            findValueDecl(ASTCtx, "Int"), findValueDecl(ASTCtx, "IntRef")));
      });
}

TEST(TransferTest, StructModeledFieldsInTypeid) {
  // Test that we model fields mentioned inside a `typeid()` expression only if
  // that expression is potentially evaluated -- i.e. if the expression inside
  // `typeid()` is a glvalue of polymorphic type (see
  // `CXXTypeidExpr::isPotentiallyEvaluated()` and [expr.typeid]p3).
  std::string Code = R"(
    // Definitions needed for `typeid`.
    namespace std {
      class type_info {};
      class bad_typeid {};
    }  // namespace std

    struct NonPolymorphic {};

    struct Polymorphic {
      virtual ~Polymorphic() = default;
    };

    struct S {
      NonPolymorphic *NonPoly;
      Polymorphic *Poly;
    };

    void target(S &s) {
      typeid(*s.NonPoly);
      typeid(*s.Poly);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
        auto &SLoc = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s");
        std::vector<const ValueDecl *> Fields;
        for (auto [Field, _] : SLoc.children())
          Fields.push_back(Field);
        EXPECT_THAT(Fields,
                    UnorderedElementsAre(findValueDecl(ASTCtx, "Poly")));
      });
}

TEST(TransferTest, StructModeledFieldsWithComplicatedInheritance) {
  std::string Code = R"(
    struct Base1 {
      int base1_1;
      int base1_2;
    };
    struct Intermediate : Base1 {
      int intermediate_1;
      int intermediate_2;
    };
    struct Base2 {
      int base2_1;
      int base2_2;
    };
    struct MostDerived : public Intermediate, Base2 {
      int most_derived_1;
      int most_derived_2;
    };

    void target() {
      MostDerived MD;
      MD.base1_2 = 1;
      MD.intermediate_2 = 1;
      MD.base2_2 = 1;
      MD.most_derived_2 = 1;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env =
              getEnvironmentAtAnnotation(Results, "p");

        // Only the accessed fields should exist in the model.
        auto &MDLoc = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "MD");
        std::vector<const ValueDecl*> Fields;
        for (auto [Field, _] : MDLoc.children())
          Fields.push_back(Field);
        ASSERT_THAT(Fields, UnorderedElementsAre(
            findValueDecl(ASTCtx, "base1_2"),
            findValueDecl(ASTCtx, "intermediate_2"),
            findValueDecl(ASTCtx, "base2_2"),
            findValueDecl(ASTCtx, "most_derived_2")));
      });
}

TEST(TransferTest, StructInitializerListWithComplicatedInheritance) {
  std::string Code = R"(
    struct Base1 {
      int base1;
    };
    struct Intermediate : Base1 {
      int intermediate;
    };
    struct Base2 {
      int base2;
    };
    struct MostDerived : public Intermediate, Base2 {
      int most_derived;
    };

    void target() {
      MostDerived MD = {};
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env =
              getEnvironmentAtAnnotation(Results, "p");

        // When a struct is initialized with a initializer list, all the
        // fields are considered "accessed", and therefore do exist.
        auto &MD = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "MD");
        ASSERT_THAT(cast<IntegerValue>(
            getFieldValue(&MD, *findValueDecl(ASTCtx, "base1"), Env)),
            NotNull());
        ASSERT_THAT(cast<IntegerValue>(
            getFieldValue(&MD, *findValueDecl(ASTCtx, "intermediate"), Env)),
            NotNull());
        ASSERT_THAT(cast<IntegerValue>(
            getFieldValue(&MD, *findValueDecl(ASTCtx, "base2"), Env)),
            NotNull());
        ASSERT_THAT(cast<IntegerValue>(
            getFieldValue(&MD, *findValueDecl(ASTCtx, "most_derived"), Env)),
            NotNull());
      });
}

TEST(TransferTest, ReferenceMember) {
  std::string Code = R"(
    struct A {
      int &Bar;
    };

    void target(A Foo) {
      int Baz = Foo.Bar;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        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<RecordStorageLocation>(Env.getStorageLocation(*FooDecl));
        const auto *BarReferentVal =
            cast<IntegerValue>(getFieldValue(FooLoc, *BarDecl, Env));

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        EXPECT_EQ(Env.getValue(*BazDecl), BarReferentVal);
      });
}

TEST(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,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const auto *ThisLoc = 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), 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<RecordStorageLocation>(ThisLoc->getChild(*QuxDecl));

        const auto *BazVal =
            cast<IntegerValue>(getFieldValue(QuxLoc, *BazDecl, Env));

        const ValueDecl *QuuxDecl = findValueDecl(ASTCtx, "Quux");
        ASSERT_THAT(QuuxDecl, NotNull());
        EXPECT_EQ(Env.getValue(*QuuxDecl), BazVal);
      });
}

TEST(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,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const auto *ThisLoc = 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), 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<RecordStorageLocation>(ThisLoc->getChild(*QuxDecl));

        const auto *BazVal =
            cast<IntegerValue>(getFieldValue(QuxLoc, *BazDecl, Env));

        const ValueDecl *QuuxDecl = findValueDecl(ASTCtx, "Quux");
        ASSERT_THAT(QuuxDecl, NotNull());
        EXPECT_EQ(Env.getValue(*QuuxDecl), BazVal);
      });
}

TEST(TransferTest, UnionThisMember) {
  std::string Code = R"(
    union A {
      int Foo;
      int Bar;

      void target() {
        A a;
        // Mention the fields to ensure they're included in the analysis.
        (void)a.Foo;
        (void)a.Bar;
        // [[p]]
      }
    };
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const auto *ThisLoc = Env.getThisPointeeStorageLocation();
        ASSERT_THAT(ThisLoc, NotNull());

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const auto *FooLoc =
            cast<ScalarStorageLocation>(ThisLoc->getChild(*FooDecl));
        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 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));
      });
}

TEST(TransferTest, StructThisInLambda) {
  std::string ThisCaptureCode = R"(
    struct A {
      void frob() {
        [this]() {
          int Foo = Bar;
          // [[p1]]
        }();
      }

      int Bar;
    };
  )";
  runDataflow(
      ThisCaptureCode,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p1");

        const auto *ThisLoc = 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), BarVal);
      },
      LangStandard::lang_cxx17, /*ApplyBuiltinTransfer=*/true, "operator()");

  std::string RefCaptureDefaultCode = R"(
    struct A {
      void frob() {
        [&]() {
          int Foo = Bar;
          // [[p2]]
        }();
      }

      int Bar;
    };
  )";
  runDataflow(
      RefCaptureDefaultCode,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p2"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p2");

        const auto *ThisLoc = 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), BarVal);
      },
      LangStandard::lang_cxx17, /*ApplyBuiltinTransfer=*/true, "operator()");

  std::string FreeFunctionLambdaCode = R"(
    void foo() {
      int Bar;
      [&]() {
        int Foo = Bar;
        // [[p3]]
      }();
    }
  )";
  runDataflow(
      FreeFunctionLambdaCode,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p3"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p3");

        EXPECT_THAT(Env.getThisPointeeStorageLocation(), IsNull());
      },
      LangStandard::lang_cxx17, /*ApplyBuiltinTransfer=*/true, "operator()");
}

TEST(TransferTest, ConstructorInitializer) {
  std::string Code = R"(
    struct target {
      int Bar;

      target(int Foo) : Bar(Foo) {
        int Qux = Bar;
        // [[p]]
      }
    };
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const auto *ThisLoc = Env.getThisPointeeStorageLocation();
        ASSERT_THAT(ThisLoc, NotNull());

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const auto *FooVal = cast<IntegerValue>(Env.getValue(*FooDecl));

        const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux");
        ASSERT_THAT(QuxDecl, NotNull());
        EXPECT_EQ(Env.getValue(*QuxDecl), FooVal);
      });
}

TEST(TransferTest, DefaultInitializer) {
  std::string Code = R"(
    struct target {
      int Bar;
      int Baz = Bar;

      target(int Foo) : Bar(Foo) {
        int Qux = Baz;
        // [[p]]
      }
    };
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const auto *ThisLoc = Env.getThisPointeeStorageLocation();
        ASSERT_THAT(ThisLoc, NotNull());

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const auto *FooVal = cast<IntegerValue>(Env.getValue(*FooDecl));

        const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux");
        ASSERT_THAT(QuxDecl, NotNull());
        EXPECT_EQ(Env.getValue(*QuxDecl), FooVal);
      });
}

TEST(TransferTest, DefaultInitializerReference) {
  std::string Code = R"(
    struct target {
      int &Bar;
      int &Baz = Bar;

      target(int &Foo) : Bar(Foo) {
        int &Qux = Baz;
        // [[p]]
      }
    };
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const auto *ThisLoc = Env.getThisPointeeStorageLocation();
        ASSERT_THAT(ThisLoc, NotNull());

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const auto *FooLoc = Env.getStorageLocation(*FooDecl);

        const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux");
        ASSERT_THAT(QuxDecl, NotNull());

        const auto *QuxLoc = Env.getStorageLocation(*QuxDecl);
        EXPECT_EQ(QuxLoc, FooLoc);
      });
}

TEST(TransferTest, TemporaryObject) {
  std::string Code = R"(
    struct A {
      int Bar;
    };

    void target() {
      A Foo = A();
      (void)Foo.Bar;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const auto *FooLoc =
            cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl));
        EXPECT_TRUE(isa<IntegerValue>(getFieldValue(FooLoc, *BarDecl, Env)));
      });
}

TEST(TransferTest, ElidableConstructor) {
  // This test is effectively the same as TransferTest.TemporaryObject, but
  // the code is compiled as C++14.
  std::string Code = R"(
    struct A {
      int Bar;
    };

    void target() {
      A Foo = A();
      (void)Foo.Bar;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const auto *FooLoc =
            cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl));
        EXPECT_TRUE(isa<IntegerValue>(getFieldValue(FooLoc, *BarDecl, Env)));
      },
      LangStandard::lang_cxx14);
}

TEST(TransferTest, AssignmentOperator) {
  std::string Code = R"(
    struct A {
      int Baz;
    };

    void target() {
      A Foo = { 1 };
      A Bar = { 2 };
      // [[p1]]
      A &Rval = (Foo = Bar);
      // [[p2]]
      Foo.Baz = 3;
      // [[p3]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        // Before copy assignment.
        {
          const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");

          const auto *FooLoc1 =
              cast<RecordStorageLocation>(Env1.getStorageLocation(*FooDecl));
          const auto *BarLoc1 =
              cast<RecordStorageLocation>(Env1.getStorageLocation(*BarDecl));
          EXPECT_FALSE(recordsEqual(*FooLoc1, *BarLoc1, Env1));

          const auto *FooBazVal1 =
              cast<IntegerValue>(getFieldValue(FooLoc1, *BazDecl, Env1));
          const auto *BarBazVal1 =
              cast<IntegerValue>(getFieldValue(BarLoc1, *BazDecl, Env1));
          EXPECT_NE(FooBazVal1, BarBazVal1);
        }

        // After copy assignment.
        {
          const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2");

          const auto *FooLoc2 =
              cast<RecordStorageLocation>(Env2.getStorageLocation(*FooDecl));
          const auto *BarLoc2 =
              cast<RecordStorageLocation>(Env2.getStorageLocation(*BarDecl));

          EXPECT_TRUE(recordsEqual(*FooLoc2, *BarLoc2, Env2));
          EXPECT_EQ(&getLocForDecl(ASTCtx, Env2, "Rval"), FooLoc2);

          const auto *FooBazVal2 =
              cast<IntegerValue>(getFieldValue(FooLoc2, *BazDecl, Env2));
          const auto *BarBazVal2 =
              cast<IntegerValue>(getFieldValue(BarLoc2, *BazDecl, Env2));
          EXPECT_EQ(FooBazVal2, BarBazVal2);
        }

        // After value update.
        {
          const Environment &Env3 = getEnvironmentAtAnnotation(Results, "p3");

          const auto *FooLoc3 =
              cast<RecordStorageLocation>(Env3.getStorageLocation(*FooDecl));
          const auto *BarLoc3 =
              cast<RecordStorageLocation>(Env3.getStorageLocation(*BarDecl));
          EXPECT_FALSE(recordsEqual(*FooLoc3, *BarLoc3, Env3));

          const auto *FooBazVal3 =
              cast<IntegerValue>(getFieldValue(FooLoc3, *BazDecl, Env3));
          const auto *BarBazVal3 =
              cast<IntegerValue>(getFieldValue(BarLoc3, *BazDecl, Env3));
          EXPECT_NE(FooBazVal3, BarBazVal3);
        }
      });
}

// It's legal for the assignment operator to take its source parameter by value.
// Check that we handle this correctly. (This is a repro -- we used to
// assert-fail on this.)
TEST(TransferTest, AssignmentOperator_ArgByValue) {
  std::string Code = R"(
    struct A {
      int Baz;
      A &operator=(A);
    };

    void target() {
      A Foo = { 1 };
      A Bar = { 2 };
      Foo = Bar;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");

        const auto &FooLoc =
            getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Foo");
        const auto &BarLoc =
            getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Bar");

        const auto *FooBazVal =
            cast<IntegerValue>(getFieldValue(&FooLoc, *BazDecl, Env));
        const auto *BarBazVal =
            cast<IntegerValue>(getFieldValue(&BarLoc, *BazDecl, Env));
        EXPECT_EQ(FooBazVal, BarBazVal);
      });
}

TEST(TransferTest, AssignmentOperatorFromBase) {
  std::string Code = R"(
    struct Base {
      int base;
    };
    struct Derived : public Base {
      using Base::operator=;
      int derived;
    };
    void target(Base B, Derived D) {
      D.base = 1;
      D.derived = 1;
      // [[before]]
      D = B;
      // [[after]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &EnvBefore =
            getEnvironmentAtAnnotation(Results, "before");
        const Environment &EnvAfter =
            getEnvironmentAtAnnotation(Results, "after");

        auto &BLoc =
            getLocForDecl<RecordStorageLocation>(ASTCtx, EnvBefore, "B");
        auto &DLoc =
            getLocForDecl<RecordStorageLocation>(ASTCtx, EnvBefore, "D");

        EXPECT_NE(getFieldValue(&BLoc, "base", ASTCtx, EnvBefore),
                  getFieldValue(&DLoc, "base", ASTCtx, EnvBefore));
        EXPECT_EQ(getFieldValue(&BLoc, "base", ASTCtx, EnvAfter),
                  getFieldValue(&DLoc, "base", ASTCtx, EnvAfter));

        EXPECT_EQ(getFieldValue(&DLoc, "derived", ASTCtx, EnvBefore),
                  getFieldValue(&DLoc, "derived", ASTCtx, EnvAfter));
      });
}

TEST(TransferTest, AssignmentOperatorFromCallResult) {
  std::string Code = R"(
    struct A {};
    A ReturnA();

    void target() {
      A MyA;
      MyA = ReturnA();
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        // As of this writing, we don't produce a `Value` for the call
        // `ReturnA()`. The only condition we're testing for is that the
        // analysis should not crash in this case.
      });
}

TEST(TransferTest, AssignmentOperatorWithInitAndInheritance) {
  // This is a crash repro.
  std::string Code = R"(
    struct B { int Foo; };
    struct S : public B {};
    void target() {
      S S1 = { 1 };
      S S2;
      S S3;
      S1 = S2;  // Only Dst has InitListExpr.
      S3 = S1;  // Only Src has InitListExpr.
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {});
}

TEST(TransferTest, AssignmentOperatorReturnsVoid) {
  // This is a crash repro.
  std::string Code = R"(
    struct S {
      void operator=(S&& other);
    };
    void target() {
      S s;
      s = S();
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {});
}

TEST(TransferTest, AssignmentOperatorReturnsByValue) {
  // This is a crash repro.
  std::string Code = R"(
    struct S {
      S operator=(const S&);
      int i;
    };
    void target() {
      S S1 = { 1 };
      S S2 = { 2 };
      S S3 = { 3 };
      // [[before]]
      // Test that the returned value is modeled by assigning to another value.
      S1 = (S2 = S3);
      (void)0;
      // [[after]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const ValueDecl *S1Decl = findValueDecl(ASTCtx, "S1");
        const ValueDecl *S2Decl = findValueDecl(ASTCtx, "S2");
        const ValueDecl *S3Decl = findValueDecl(ASTCtx, "S3");

        const Environment &EnvBefore =
            getEnvironmentAtAnnotation(Results, "before");

        EXPECT_FALSE(recordsEqual(
            *EnvBefore.get<RecordStorageLocation>(*S1Decl),
            *EnvBefore.get<RecordStorageLocation>(*S2Decl), EnvBefore));
        EXPECT_FALSE(recordsEqual(
            *EnvBefore.get<RecordStorageLocation>(*S2Decl),
            *EnvBefore.get<RecordStorageLocation>(*S3Decl), EnvBefore));

        const Environment &EnvAfter =
            getEnvironmentAtAnnotation(Results, "after");

        EXPECT_TRUE(recordsEqual(*EnvAfter.get<RecordStorageLocation>(*S1Decl),
                                 *EnvAfter.get<RecordStorageLocation>(*S2Decl),
                                 EnvAfter));
        EXPECT_TRUE(recordsEqual(*EnvAfter.get<RecordStorageLocation>(*S2Decl),
                                 *EnvAfter.get<RecordStorageLocation>(*S3Decl),
                                 EnvAfter));
      });
}

TEST(TransferTest, AssignmentOperatorReturnsDifferentTypeByRef) {
  // This is a crash repro.
  std::string Code = R"(
    struct DifferentType {};
    struct S {
      DifferentType& operator=(const S&);
    };
    void target() {
      S s;
      s = S();
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {});
}

TEST(TransferTest, AssignmentOperatorReturnsDifferentTypeByValue) {
  // This is a crash repro.
  std::string Code = R"(
    struct DifferentType {};
    struct S {
      DifferentType operator=(const S&);
    };
    void target() {
      S s;
      s = S();
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {});
}

TEST(TransferTest, InitListExprAsXValue) {
  // This is a crash repro.
  std::string Code = R"(
    void target() {
      bool&& Foo{false};
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
        const auto &FooVal = getValueForDecl<BoolValue>(ASTCtx, Env, "Foo");
        ASSERT_TRUE(FooVal.formula().isLiteral(false));
      });
}

TEST(TransferTest, ArrayInitListExprOneRecordElement) {
  // This is a crash repro.
  std::string Code = R"cc(
    struct S {};

    void target() { S foo[] = {S()}; }
  )cc";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        // Just verify that it doesn't crash.
      });
}

TEST(TransferTest, InitListExprAsUnion) {
  // This is a crash repro.
  std::string Code = R"cc(
    class target {
      union {
        int *a;
        bool *b;
      } F;

     public:
      constexpr target() : F{nullptr} {
        int *null = nullptr;
        F.b;  // Make sure we reference 'b' so it is modeled.
        // [[p]]
      }
    };
  )cc";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto &FLoc = getFieldLoc<RecordStorageLocation>(
            *Env.getThisPointeeStorageLocation(), "F", ASTCtx);
        auto *AVal = cast<PointerValue>(getFieldValue(&FLoc, "a", ASTCtx, Env));
        EXPECT_EQ(AVal, &getValueForDecl<PointerValue>(ASTCtx, Env, "null"));
        EXPECT_EQ(getFieldValue(&FLoc, "b", ASTCtx, Env), nullptr);
      });
}

TEST(TransferTest, EmptyInitListExprForUnion) {
  // This is a crash repro.
  std::string Code = R"cc(
    class target {
      union {
        int *a;
        bool *b;
      } F;

     public:
      // Empty initializer list means that `F` is aggregate-initialized.
      // For a union, this has the effect that the first member of the union
      // is copy-initialized from an empty initializer list; in this specific
      // case, this has the effect of initializing `a` with null.
      constexpr target() : F{} {
        int *null = nullptr;
        F.b;  // Make sure we reference 'b' so it is modeled.
        // [[p]]
      }
    };
  )cc";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto &FLoc = getFieldLoc<RecordStorageLocation>(
            *Env.getThisPointeeStorageLocation(), "F", ASTCtx);
        auto *AVal = cast<PointerValue>(getFieldValue(&FLoc, "a", ASTCtx, Env));
        EXPECT_EQ(AVal, &getValueForDecl<PointerValue>(ASTCtx, Env, "null"));
        EXPECT_EQ(getFieldValue(&FLoc, "b", ASTCtx, Env), nullptr);
      });
}

TEST(TransferTest, EmptyInitListExprForStruct) {
  std::string Code = R"cc(
    class target {
      struct {
        int *a;
        bool *b;
      } F;

     public:
      constexpr target() : F{} {
        int *NullIntPtr = nullptr;
        bool *NullBoolPtr = nullptr;
        // [[p]]
      }
    };
  )cc";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto &FLoc = getFieldLoc<RecordStorageLocation>(
            *Env.getThisPointeeStorageLocation(), "F", ASTCtx);
        auto *AVal = cast<PointerValue>(getFieldValue(&FLoc, "a", ASTCtx, Env));
        EXPECT_EQ(AVal,
                  &getValueForDecl<PointerValue>(ASTCtx, Env, "NullIntPtr"));
        auto *BVal = cast<PointerValue>(getFieldValue(&FLoc, "b", ASTCtx, Env));
        EXPECT_EQ(BVal,
                  &getValueForDecl<PointerValue>(ASTCtx, Env, "NullBoolPtr"));
      });
}

TEST(TransferTest, CopyConstructor) {
  std::string Code = R"(
    struct A {
      int Baz;
    };

    void target() {
      A Foo = { 1 };
      A Bar = Foo;
      // [[after_copy]]
      Foo.Baz = 2;
      // [[after_update]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        // after_copy
        {
          const Environment &Env =
              getEnvironmentAtAnnotation(Results, "after_copy");

          const auto *FooLoc =
              cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl));
          const auto *BarLoc =
              cast<RecordStorageLocation>(Env.getStorageLocation(*BarDecl));

          // The records compare equal.
          EXPECT_TRUE(recordsEqual(*FooLoc, *BarLoc, Env));

          // In particular, the value of `Baz` in both records is the same.
          const auto *FooBazVal =
              cast<IntegerValue>(getFieldValue(FooLoc, *BazDecl, Env));
          const auto *BarBazVal =
              cast<IntegerValue>(getFieldValue(BarLoc, *BazDecl, Env));
          EXPECT_EQ(FooBazVal, BarBazVal);
        }

        // after_update
        {
          const Environment &Env =
              getEnvironmentAtAnnotation(Results, "after_update");

          const auto *FooLoc =
              cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl));
          const auto *BarLoc =
              cast<RecordStorageLocation>(Env.getStorageLocation(*BarDecl));

          EXPECT_FALSE(recordsEqual(*FooLoc, *BarLoc, Env));

          const auto *FooBazVal =
              cast<IntegerValue>(getFieldValue(FooLoc, *BazDecl, Env));
          const auto *BarBazVal =
              cast<IntegerValue>(getFieldValue(BarLoc, *BazDecl, Env));
          EXPECT_NE(FooBazVal, BarBazVal);
        }
      });
}

TEST(TransferTest, CopyConstructorWithDefaultArgument) {
  std::string Code = R"(
    struct A {
      int Baz;
      A() = default;
      A(const A& a, bool def = true) { Baz = a.Baz; }
    };

    void target() {
      A Foo;
      (void)Foo.Baz;
      A Bar = Foo;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        const auto *FooLoc =
            cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl));
        const auto *BarLoc =
            cast<RecordStorageLocation>(Env.getStorageLocation(*BarDecl));
        EXPECT_TRUE(recordsEqual(*FooLoc, *BarLoc, Env));

        const auto *FooBazVal =
            cast<IntegerValue>(getFieldValue(FooLoc, *BazDecl, Env));
        const auto *BarBazVal =
            cast<IntegerValue>(getFieldValue(BarLoc, *BazDecl, Env));
        EXPECT_EQ(FooBazVal, BarBazVal);
      });
}

TEST(TransferTest, CopyConstructorWithParens) {
  std::string Code = R"(
    struct A {
      int Baz;
    };

    void target() {
      A Foo;
      (void)Foo.Baz;
      A Bar((A(Foo)));
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        const auto *FooLoc =
            cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl));
        const auto *BarLoc =
            cast<RecordStorageLocation>(Env.getStorageLocation(*BarDecl));
        EXPECT_TRUE(recordsEqual(*FooLoc, *BarLoc, Env));

        const auto *FooBazVal =
            cast<IntegerValue>(getFieldValue(FooLoc, *BazDecl, Env));
        const auto *BarBazVal =
            cast<IntegerValue>(getFieldValue(BarLoc, *BazDecl, Env));
        EXPECT_EQ(FooBazVal, BarBazVal);
      });
}

TEST(TransferTest, CopyConstructorWithInitializerListAsSyntacticSugar) {
  std::string Code = R"(
  struct A {
    int Baz;
  };
  void target() {
    A Foo = {3};
    (void)Foo.Baz;
    A Bar = {A(Foo)};
    // [[p]]
  }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");

        const auto &FooLoc =
            getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Foo");
        const auto &BarLoc =
            getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Bar");

        const auto *FooBazVal =
            cast<IntegerValue>(getFieldValue(&FooLoc, *BazDecl, Env));
        const auto *BarBazVal =
            cast<IntegerValue>(getFieldValue(&BarLoc, *BazDecl, Env));
        EXPECT_EQ(FooBazVal, BarBazVal);
      });
}

TEST(TransferTest, CopyConstructorArgIsRefReturnedByFunction) {
  // This is a crash repro.
  std::string Code = R"(
    struct S {};
    const S &returnsSRef();
    void target() {
      S s(returnsSRef());
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {});
}

TEST(TransferTest, MoveConstructor) {
  std::string Code = R"(
    namespace std {

    template <typename T> struct remove_reference      { using type = T; };
    template <typename T> struct remove_reference<T&>  { using type = T; };
    template <typename T> struct remove_reference<T&&> { using type = T; };

    template <typename T>
    using remove_reference_t = typename remove_reference<T>::type;

    template <typename T>
    std::remove_reference_t<T>&& move(T&& x);

    } // namespace std

    struct A {
      int Baz;
    };

    void target() {
      A Foo;
      A Bar;
      (void)Foo.Baz;
      // [[p1]]
      Foo = std::move(Bar);
      // [[p2]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1", "p2"));
        const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
        const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        const auto *FooLoc1 =
            cast<RecordStorageLocation>(Env1.getStorageLocation(*FooDecl));
        const auto *BarLoc1 =
            cast<RecordStorageLocation>(Env1.getStorageLocation(*BarDecl));

        EXPECT_FALSE(recordsEqual(*FooLoc1, *BarLoc1, Env1));

        const auto *FooBazVal1 =
            cast<IntegerValue>(getFieldValue(FooLoc1, *BazDecl, Env1));
        const auto *BarBazVal1 =
            cast<IntegerValue>(getFieldValue(BarLoc1, *BazDecl, Env1));
        EXPECT_NE(FooBazVal1, BarBazVal1);

        const auto *FooLoc2 =
            cast<RecordStorageLocation>(Env2.getStorageLocation(*FooDecl));
        EXPECT_TRUE(recordsEqual(*FooLoc2, Env2, *BarLoc1, Env1));

        const auto *FooBazVal2 =
            cast<IntegerValue>(getFieldValue(FooLoc1, *BazDecl, Env2));
        EXPECT_EQ(FooBazVal2, BarBazVal1);
      });
}

TEST(TransferTest, BindTemporary) {
  std::string Code = R"(
    struct A {
      virtual ~A() = default;

      int Baz;
    };

    void target(A Foo) {
      int Bar = A(Foo).Baz;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        const auto &FooLoc =
            *cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl));
        const auto *BarVal = cast<IntegerValue>(Env.getValue(*BarDecl));
        EXPECT_EQ(BarVal, getFieldValue(&FooLoc, *BazDecl, Env));
      });
}

TEST(TransferTest, ResultObjectLocation) {
  std::string Code = R"(
    struct A {
      virtual ~A() = default;
    };

    void target() {
      0, A();
      (void)0; // [[p]]
    }
  )";
  using ast_matchers::binaryOperator;
  using ast_matchers::cxxBindTemporaryExpr;
  using ast_matchers::cxxTemporaryObjectExpr;
  using ast_matchers::exprWithCleanups;
  using ast_matchers::has;
  using ast_matchers::hasOperatorName;
  using ast_matchers::hasRHS;
  using ast_matchers::match;
  using ast_matchers::selectFirst;
  using ast_matchers::traverse;
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        // The expression `0, A()` in the code above produces the following
        // structure, consisting of four prvalues of record type.
        // `Env.getResultObjectLocation()` should return the same location for
        // all of these.
        auto MatchResult = match(
            traverse(TK_AsIs,
                     exprWithCleanups(
                         has(binaryOperator(
                                 hasOperatorName(","),
                                 hasRHS(cxxBindTemporaryExpr(
                                            has(cxxTemporaryObjectExpr().bind(
                                                "toe")))
                                            .bind("bte")))
                                 .bind("comma")))
                         .bind("ewc")),
            ASTCtx);
        auto *TOE = selectFirst<CXXTemporaryObjectExpr>("toe", MatchResult);
        ASSERT_NE(TOE, nullptr);
        auto *Comma = selectFirst<BinaryOperator>("comma", MatchResult);
        ASSERT_NE(Comma, nullptr);
        auto *EWC = selectFirst<ExprWithCleanups>("ewc", MatchResult);
        ASSERT_NE(EWC, nullptr);
        auto *BTE = selectFirst<CXXBindTemporaryExpr>("bte", MatchResult);
        ASSERT_NE(BTE, nullptr);

        RecordStorageLocation &Loc = Env.getResultObjectLocation(*TOE);
        EXPECT_EQ(&Loc, &Env.getResultObjectLocation(*Comma));
        EXPECT_EQ(&Loc, &Env.getResultObjectLocation(*EWC));
        EXPECT_EQ(&Loc, &Env.getResultObjectLocation(*BTE));
      });
}

TEST(TransferTest, ResultObjectLocationForDefaultArgExpr) {
  std::string Code = R"(
    struct Inner {};
    struct Outer {
        Inner I = {};
    };

    void funcWithDefaultArg(Outer O = {});
    void target() {
      funcWithDefaultArg();
      // [[p]]
    }
  )";

  using ast_matchers::cxxDefaultArgExpr;
  using ast_matchers::match;
  using ast_matchers::selectFirst;
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto *DefaultArg = selectFirst<CXXDefaultArgExpr>(
            "default_arg",
            match(cxxDefaultArgExpr().bind("default_arg"), ASTCtx));
        ASSERT_NE(DefaultArg, nullptr);

        // The values for default arguments aren't modeled; we merely verify
        // that we can get a result object location for a default arg.
        Env.getResultObjectLocation(*DefaultArg);
      });
}

TEST(TransferTest, ResultObjectLocationForDefaultInitExpr) {
  std::string Code = R"(
    struct S {};
    struct target {
      target () {
        (void)0;
        // [[p]]
      }
      S s = {};
    };
  )";

  using ast_matchers::cxxCtorInitializer;
  using ast_matchers::match;
  using ast_matchers::selectFirst;
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *SField = findValueDecl(ASTCtx, "s");

        auto *CtorInit = selectFirst<CXXCtorInitializer>(
            "ctor_initializer",
            match(cxxCtorInitializer().bind("ctor_initializer"), ASTCtx));
        ASSERT_NE(CtorInit, nullptr);

        auto *DefaultInit = cast<CXXDefaultInitExpr>(CtorInit->getInit());

        RecordStorageLocation &Loc = Env.getResultObjectLocation(*DefaultInit);

        EXPECT_EQ(&Loc, Env.getThisPointeeStorageLocation()->getChild(*SField));
      });
}

// This test ensures that CXXOperatorCallExpr returning prvalues are correctly
// handled by the transfer functions, especially that `getResultObjectLocation`
// correctly returns a storage location for those.
TEST(TransferTest, ResultObjectLocationForCXXOperatorCallExpr) {
  std::string Code = R"(
    struct A {
      A operator+(int);
    };

    void target() {
      A a;
      a + 3;
      (void)0; // [[p]]
    }
  )";
  using ast_matchers::cxxOperatorCallExpr;
  using ast_matchers::match;
  using ast_matchers::selectFirst;
  using ast_matchers::traverse;
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto *CallExpr = selectFirst<CXXOperatorCallExpr>(
            "call_expr",
            match(cxxOperatorCallExpr().bind("call_expr"), ASTCtx));

        EXPECT_NE(&Env.getResultObjectLocation(*CallExpr), nullptr);
      });
}

TEST(TransferTest, ResultObjectLocationForInitListExpr) {
  std::string Code = R"cc(
    struct Inner {};

    struct Outer { Inner I; };

    void target() {
      Outer O = { Inner() };
      // [[p]]
    }
  )cc";
  using ast_matchers::asString;
  using ast_matchers::cxxConstructExpr;
  using ast_matchers::hasType;
  using ast_matchers::match;
  using ast_matchers::selectFirst;
  using ast_matchers::traverse;
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto *Construct = selectFirst<CXXConstructExpr>(
            "construct",
            match(
                cxxConstructExpr(hasType(asString("Inner"))).bind("construct"),
                ASTCtx));

        EXPECT_EQ(
            &Env.getResultObjectLocation(*Construct),
            &getFieldLoc(getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "O"),
                         "I", ASTCtx));
      });
}

TEST(TransferTest, ResultObjectLocationForParenInitListExpr) {
  std::string Code = R"cc(
    struct Inner {};

    struct Outer { Inner I; };

    void target() {
      Outer O((Inner()));
      // [[p]]
    }
  )cc";
  using ast_matchers::asString;
  using ast_matchers::cxxConstructExpr;
  using ast_matchers::hasType;
  using ast_matchers::match;
  using ast_matchers::selectFirst;
  using ast_matchers::traverse;
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto *Construct = selectFirst<CXXConstructExpr>(
            "construct",
            match(
                cxxConstructExpr(hasType(asString("Inner"))).bind("construct"),
                ASTCtx));

        EXPECT_EQ(
            &Env.getResultObjectLocation(*Construct),
            &getFieldLoc(getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "O"),
                         "I", ASTCtx));
      },
      LangStandard::lang_cxx20);
}

// Check that the `std::strong_ordering` object returned by builtin `<=>` has a
// correctly modeled result object location.
TEST(TransferTest, ResultObjectLocationForBuiltinSpaceshipOperator) {
  std::string Code = R"(
    namespace std {
      // This is the minimal definition required to get
      // `Sema::CheckComparisonCategoryType()` to accept this fake.
      struct strong_ordering {
        enum class ordering { less, equal, greater };
        ordering o;
        static const strong_ordering less;
        static const strong_ordering equivalent;
        static const strong_ordering equal;
        static const strong_ordering greater;
      };

      inline constexpr strong_ordering strong_ordering::less =
        { strong_ordering::ordering::less };
      inline constexpr strong_ordering strong_ordering::equal =
        { strong_ordering::ordering::equal };
      inline constexpr strong_ordering strong_ordering::equivalent =
        { strong_ordering::ordering::equal };
      inline constexpr strong_ordering strong_ordering::greater =
        { strong_ordering::ordering::greater };
    }
    void target(int i, int j) {
      auto ordering = i <=> j;
      // [[p]]
    }
  )";
  using ast_matchers::binaryOperator;
  using ast_matchers::hasOperatorName;
  using ast_matchers::match;
  using ast_matchers::selectFirst;
  using ast_matchers::traverse;
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto *Spaceship = selectFirst<BinaryOperator>(
            "op",
            match(binaryOperator(hasOperatorName("<=>")).bind("op"), ASTCtx));

        EXPECT_EQ(
            &Env.getResultObjectLocation(*Spaceship),
            &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "ordering"));
      },
      LangStandard::lang_cxx20);
}

TEST(TransferTest, ResultObjectLocationForStdInitializerListExpr) {
  std::string Code = R"(
    namespace std {
    template <typename T>
    struct initializer_list { const T *a, *b; };
    } // namespace std

    void target() {
      std::initializer_list<int> list = {1};
      // [[p]]
    }
  )";

  using ast_matchers::cxxStdInitializerListExpr;
  using ast_matchers::match;
  using ast_matchers::selectFirst;
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto *StdInitList = selectFirst<CXXStdInitializerListExpr>(
            "std_init_list",
            match(cxxStdInitializerListExpr().bind("std_init_list"), ASTCtx));
        ASSERT_NE(StdInitList, nullptr);

        EXPECT_EQ(&Env.getResultObjectLocation(*StdInitList),
                  &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "list"));
      });
}

TEST(TransferTest, ResultObjectLocationForStmtExpr) {
  std::string Code = R"(
    struct S {};
    void target() {
      S s = ({ S(); });
      // [[p]]
    }
  )";
  using ast_matchers::cxxConstructExpr;
  using ast_matchers::match;
  using ast_matchers::selectFirst;
  using ast_matchers::traverse;
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto *Construct = selectFirst<CXXConstructExpr>(
            "construct", match(cxxConstructExpr().bind("construct"), ASTCtx));

        EXPECT_EQ(&Env.getResultObjectLocation(*Construct),
                  &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s"));
      });
}

TEST(TransferTest, ResultObjectLocationForBuiltinBitCastExpr) {
  std::string Code = R"(
    struct S { int i; };
    void target(int i) {
      S s = __builtin_bit_cast(S, i);
      // [[p]]
    }
  )";
  using ast_matchers::explicitCastExpr;
  using ast_matchers::match;
  using ast_matchers::selectFirst;
  using ast_matchers::traverse;
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto *BuiltinBitCast = selectFirst<BuiltinBitCastExpr>(
            "cast", match(explicitCastExpr().bind("cast"), ASTCtx));

        EXPECT_EQ(&Env.getResultObjectLocation(*BuiltinBitCast),
                  &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s"));
      });
}

TEST(TransferTest, ResultObjectLocationForAtomicExpr) {
  std::string Code = R"(
    struct S {};
    void target(_Atomic(S) *ptr) {
      S s = __c11_atomic_load(ptr, __ATOMIC_SEQ_CST);
      // [[p]]
    }
  )";
  using ast_matchers::atomicExpr;
  using ast_matchers::match;
  using ast_matchers::selectFirst;
  using ast_matchers::traverse;
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto *Atomic = selectFirst<AtomicExpr>(
            "atomic", match(atomicExpr().bind("atomic"), ASTCtx));

        EXPECT_EQ(&Env.getResultObjectLocation(*Atomic),
                  &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s"));
      });
}

TEST(TransferTest, ResultObjectLocationPropagatesThroughConditionalOperator) {
  std::string Code = R"(
    struct A {
      A(int);
    };

    void target(bool b) {
      A a = b ? A(0) : A(1);
      (void)0; // [[p]]
    }
  )";
  using ast_matchers::cxxConstructExpr;
  using ast_matchers::equals;
  using ast_matchers::hasArgument;
  using ast_matchers::integerLiteral;
  using ast_matchers::match;
  using ast_matchers::selectFirst;
  using ast_matchers::traverse;
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto *ConstructExpr0 = selectFirst<CXXConstructExpr>(
            "construct",
            match(cxxConstructExpr(hasArgument(0, integerLiteral(equals(0))))
                      .bind("construct"),
                  ASTCtx));
        auto *ConstructExpr1 = selectFirst<CXXConstructExpr>(
            "construct",
            match(cxxConstructExpr(hasArgument(0, integerLiteral(equals(1))))
                      .bind("construct"),
                  ASTCtx));

        auto &ALoc = getLocForDecl<StorageLocation>(ASTCtx, Env, "a");
        EXPECT_EQ(&Env.getResultObjectLocation(*ConstructExpr0), &ALoc);
        EXPECT_EQ(&Env.getResultObjectLocation(*ConstructExpr1), &ALoc);
      });
}

TEST(TransferTest, ResultObjectLocationDontVisitNestedRecordDecl) {
  // This is a crash repro.
  // We used to crash because when propagating result objects, we would visit
  // nested record and function declarations, but we don't model fields used
  // only in these.
  std::string Code = R"(
    struct S1 {};
    struct S2 { S1 s1; };
    void target() {
      struct Nested {
        void f() {
          S2 s2 = { S1() };
        }
      };
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {});
}

TEST(TransferTest, ResultObjectLocationDontVisitUnevaluatedContexts) {
  // This is a crash repro.
  // We used to crash because when propagating result objects, we would visit
  // unevaluated contexts, but we don't model fields used only in these.

  auto testFunction = [](llvm::StringRef Code, llvm::StringRef TargetFun) {
    runDataflow(
        Code,
        [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
           ASTContext &ASTCtx) {},
        LangStandard::lang_gnucxx17,
        /* ApplyBuiltinTransfer= */ true, TargetFun);
  };

  std::string Code = R"cc(
    // Definitions needed for `typeid`.
    namespace std {
      class type_info {};
      class bad_typeid {};
    }  // namespace std

    struct S1 {};
    struct S2 { S1 s1; };

    // We test each type of unevaluated context from a different target
    // function. Some types of unevaluated contexts may actually cause the
    // field `s1` to be modeled, and we don't want this to "pollute" the tests
    // for the other unevaluated contexts.
    void decltypeTarget() {
        decltype(S2{}) Dummy;
    }
    void typeofTarget() {
        typeof(S2{}) Dummy;
    }
    void typeidTarget() {
#if __has_feature(cxx_rtti)
        typeid(S2{});
#endif
    }
    void sizeofTarget() {
        sizeof(S2{});
    }
    void noexceptTarget() {
        noexcept(S2{});
    }
  )cc";

  testFunction(Code, "decltypeTarget");
  testFunction(Code, "typeofTarget");
  testFunction(Code, "typeidTarget");
  testFunction(Code, "sizeofTarget");
  testFunction(Code, "noexceptTarget");
}

TEST(TransferTest, StaticCast) {
  std::string Code = R"(
    void target(int Foo) {
      int Bar = static_cast<int>(Foo);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const auto *FooVal = Env.getValue(*FooDecl);
        const auto *BarVal = Env.getValue(*BarDecl);
        EXPECT_TRUE(isa<IntegerValue>(FooVal));
        EXPECT_TRUE(isa<IntegerValue>(BarVal));
        EXPECT_EQ(FooVal, BarVal);
      });
}

TEST(TransferTest, IntegralCast) {
  std::string Code = R"(
    void target(int Foo) {
      long Bar = Foo;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const auto &FooVal = getValueForDecl<IntegerValue>(ASTCtx, Env, "Foo");
        const auto &BarVal = getValueForDecl<IntegerValue>(ASTCtx, Env, "Bar");
        EXPECT_EQ(&FooVal, &BarVal);
      });
}

TEST(TransferTest, IntegraltoBooleanCast) {
  std::string Code = R"(
    void target(int Foo) {
      bool Bar = Foo;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const auto &FooVal = getValueForDecl(ASTCtx, Env, "Foo");
        const auto &BarVal = getValueForDecl(ASTCtx, Env, "Bar");
        EXPECT_TRUE(isa<IntegerValue>(FooVal));
        EXPECT_TRUE(isa<BoolValue>(BarVal));
      });
}

TEST(TransferTest, IntegralToBooleanCastFromBool) {
  std::string Code = R"(
    void target(bool Foo) {
      int Zab = Foo;
      bool Bar = Zab;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const auto &FooVal = getValueForDecl<BoolValue>(ASTCtx, Env, "Foo");
        const auto &BarVal = getValueForDecl<BoolValue>(ASTCtx, Env, "Bar");
        EXPECT_EQ(&FooVal, &BarVal);
      });
}

TEST(TransferTest, WidenBoolValueInIntegerVariable) {
  // This is a crash repro.
  // This test sets up a case where we perform widening on an integer variable
  // that contains a `BoolValue` for the previous iteration and an
  // `IntegerValue` for the current iteration. We used to crash on this because
  // `widenDistinctValues()` assumed that if the previous iteration had a
  // `BoolValue`, the current iteration would too.
  // FIXME: The real fix here is to make sure we never store `BoolValue`s in
  // integer variables; see also the comment in `widenDistinctValues()`.
  std::string Code = R"cc(
    struct S {
      int i;
      S *next;
    };
    void target(S *s) {
      for (; s; s = s->next)
        s->i = false;
    }
  )cc";
  runDataflow(Code,
              [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
                 ASTContext &) {});
}

TEST(TransferTest, NullToPointerCast) {
  std::string Code = R"(
    using my_nullptr_t = decltype(nullptr);
    struct Baz {};
    void target() {
      int *FooX = nullptr;
      int *FooY = nullptr;
      bool **Bar = nullptr;
      Baz *Baz = nullptr;
      my_nullptr_t Null = 0;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooXDecl = findValueDecl(ASTCtx, "FooX");
        ASSERT_THAT(FooXDecl, NotNull());

        const ValueDecl *FooYDecl = findValueDecl(ASTCtx, "FooY");
        ASSERT_THAT(FooYDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        const ValueDecl *NullDecl = findValueDecl(ASTCtx, "Null");
        ASSERT_THAT(NullDecl, NotNull());

        const auto *FooXVal = cast<PointerValue>(Env.getValue(*FooXDecl));
        const auto *FooYVal = cast<PointerValue>(Env.getValue(*FooYDecl));
        const auto *BarVal = cast<PointerValue>(Env.getValue(*BarDecl));
        const auto *BazVal = cast<PointerValue>(Env.getValue(*BazDecl));
        const auto *NullVal = cast<PointerValue>(Env.getValue(*NullDecl));

        EXPECT_EQ(FooXVal, FooYVal);
        EXPECT_NE(FooXVal, BarVal);
        EXPECT_NE(FooXVal, BazVal);
        EXPECT_NE(BarVal, BazVal);

        const StorageLocation &FooPointeeLoc = FooXVal->getPointeeLoc();
        EXPECT_TRUE(isa<ScalarStorageLocation>(FooPointeeLoc));
        EXPECT_THAT(Env.getValue(FooPointeeLoc), IsNull());

        const StorageLocation &BarPointeeLoc = BarVal->getPointeeLoc();
        EXPECT_TRUE(isa<ScalarStorageLocation>(BarPointeeLoc));
        EXPECT_THAT(Env.getValue(BarPointeeLoc), IsNull());

        const StorageLocation &BazPointeeLoc = BazVal->getPointeeLoc();
        EXPECT_TRUE(isa<RecordStorageLocation>(BazPointeeLoc));
        EXPECT_EQ(BazVal, &Env.fork().getOrCreateNullPointerValue(
                              BazPointeeLoc.getType()));

        const StorageLocation &NullPointeeLoc = NullVal->getPointeeLoc();
        EXPECT_TRUE(isa<ScalarStorageLocation>(NullPointeeLoc));
        EXPECT_THAT(Env.getValue(NullPointeeLoc), IsNull());
      });
}

TEST(TransferTest, PointerToMemberVariable) {
  std::string Code = R"(
    struct S {
      int i;
    };
    void target() {
      int S::*MemberPointer = &S::i;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *MemberPointerDecl =
            findValueDecl(ASTCtx, "MemberPointer");
        ASSERT_THAT(MemberPointerDecl, NotNull());
        ASSERT_THAT(Env.getValue(*MemberPointerDecl), IsNull());
      });
}

TEST(TransferTest, PointerToMemberFunction) {
  std::string Code = R"(
    struct S {
      void Method();
    };
    void target() {
      void (S::*MemberPointer)() = &S::Method;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *MemberPointerDecl =
            findValueDecl(ASTCtx, "MemberPointer");
        ASSERT_THAT(MemberPointerDecl, NotNull());
        ASSERT_THAT(Env.getValue(*MemberPointerDecl), IsNull());
      });
}

TEST(TransferTest, NullToMemberPointerCast) {
  std::string Code = R"(
    struct Foo {};
    void target() {
      int Foo::*MemberPointer = nullptr;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *MemberPointerDecl =
            findValueDecl(ASTCtx, "MemberPointer");
        ASSERT_THAT(MemberPointerDecl, NotNull());
        ASSERT_THAT(Env.getValue(*MemberPointerDecl), IsNull());
      });
}

TEST(TransferTest, AddrOfValue) {
  std::string Code = R"(
    void target() {
      int Foo;
      int *Bar = &Foo;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const auto *FooLoc =
            cast<ScalarStorageLocation>(Env.getStorageLocation(*FooDecl));
        const auto *BarVal = cast<PointerValue>(Env.getValue(*BarDecl));
        EXPECT_EQ(&BarVal->getPointeeLoc(), FooLoc);
      });
}

TEST(TransferTest, AddrOfReference) {
  std::string Code = R"(
    void target(int *Foo) {
      int *Bar = &(*Foo);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const auto *FooVal = cast<PointerValue>(Env.getValue(*FooDecl));
        const auto *BarVal = cast<PointerValue>(Env.getValue(*BarDecl));
        EXPECT_EQ(&BarVal->getPointeeLoc(), &FooVal->getPointeeLoc());
      });
}

TEST(TransferTest, Preincrement) {
  std::string Code = R"(
    void target(int I) {
      (void)0; // [[before]]
      int &IRef = ++I;
      // [[after]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &EnvBefore =
            getEnvironmentAtAnnotation(Results, "before");
        const Environment &EnvAfter =
            getEnvironmentAtAnnotation(Results, "after");

        EXPECT_EQ(&getLocForDecl(ASTCtx, EnvAfter, "IRef"),
                  &getLocForDecl(ASTCtx, EnvBefore, "I"));

        const ValueDecl *IDecl = findValueDecl(ASTCtx, "I");
        EXPECT_NE(EnvBefore.getValue(*IDecl), nullptr);
        EXPECT_EQ(EnvAfter.getValue(*IDecl), nullptr);
      });
}

TEST(TransferTest, Postincrement) {
  std::string Code = R"(
    void target(int I) {
      (void)0; // [[before]]
      int OldVal = I++;
      // [[after]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &EnvBefore =
            getEnvironmentAtAnnotation(Results, "before");
        const Environment &EnvAfter =
            getEnvironmentAtAnnotation(Results, "after");

        EXPECT_EQ(&getValueForDecl(ASTCtx, EnvBefore, "I"),
                  &getValueForDecl(ASTCtx, EnvAfter, "OldVal"));

        const ValueDecl *IDecl = findValueDecl(ASTCtx, "I");
        EXPECT_EQ(EnvAfter.getValue(*IDecl), nullptr);
      });
}

// We test just one of the compound assignment operators because we know the
// code for propagating the storage location is shared among all of them.
TEST(TransferTest, AddAssign) {
  std::string Code = R"(
    void target(int I) {
      int &IRef = (I += 1);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        EXPECT_EQ(&getLocForDecl(ASTCtx, Env, "IRef"),
                  &getLocForDecl(ASTCtx, Env, "I"));
      });
}

TEST(TransferTest, CannotAnalyzeFunctionTemplate) {
  std::string Code = R"(
    template <typename T>
    void target() {}
  )";
  ASSERT_THAT_ERROR(
      checkDataflowWithNoopAnalysis(Code),
      llvm::FailedWithMessage("Cannot analyze templated declarations"));
}

TEST(TransferTest, CannotAnalyzeMethodOfClassTemplate) {
  std::string Code = R"(
    template <typename T>
    struct A {
      void target() {}
    };
  )";
  ASSERT_THAT_ERROR(
      checkDataflowWithNoopAnalysis(Code),
      llvm::FailedWithMessage("Cannot analyze templated declarations"));
}

TEST(TransferTest, VarDeclInitAssignConditionalOperator) {
  std::string Code = R"(
    struct A {
      int i;
    };

    void target(A Foo, A Bar, bool Cond) {
      A Baz = Cond ?  A(Foo) : A(Bar);
      // Make sure A::i is modeled.
      Baz.i;
      /*[[p]]*/
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto *FooIVal = cast<IntegerValue>(getFieldValue(
            &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Foo"), "i",
            ASTCtx, Env));
        auto *BarIVal = cast<IntegerValue>(getFieldValue(
            &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Bar"), "i",
            ASTCtx, Env));
        auto *BazIVal = cast<IntegerValue>(getFieldValue(
            &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Baz"), "i",
            ASTCtx, Env));

        EXPECT_NE(BazIVal, FooIVal);
        EXPECT_NE(BazIVal, BarIVal);
      });
}

TEST(TransferTest, VarDeclInDoWhile) {
  std::string Code = R"(
    void target(int *Foo) {
      do {
        int Bar = *Foo;
        // [[in_loop]]
      } while (false);
      (void)0;
      // [[after_loop]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &EnvInLoop =
            getEnvironmentAtAnnotation(Results, "in_loop");
        const Environment &EnvAfterLoop =
            getEnvironmentAtAnnotation(Results, "after_loop");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const auto *FooVal =
            cast<PointerValue>(EnvAfterLoop.getValue(*FooDecl));
        const auto *FooPointeeVal =
            cast<IntegerValue>(EnvAfterLoop.getValue(FooVal->getPointeeLoc()));

        const auto *BarVal = cast<IntegerValue>(EnvInLoop.getValue(*BarDecl));
        EXPECT_EQ(BarVal, FooPointeeVal);

        ASSERT_THAT(EnvAfterLoop.getValue(*BarDecl), IsNull());
      });
}

TEST(TransferTest, UnreachableAfterWhileTrue) {
  std::string Code = R"(
    void target() {
      while (true) {}
      (void)0;
      /*[[p]]*/
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        // The node after the while-true is pruned because it is trivially
        // known to be unreachable.
        ASSERT_TRUE(Results.empty());
      });
}

TEST(TransferTest, AggregateInitialization) {
  std::string BracesCode = R"(
    struct A {
      int Foo;
    };

    struct B {
      int Bar;
      A Baz;
      int Qux;
    };

    void target(int BarArg, int FooArg, int QuxArg) {
      B Quux{BarArg, {FooArg}, QuxArg};
      B OtherB;
      /*[[p]]*/
    }
  )";
  std::string BraceElisionCode = R"(
    struct A {
      int Foo;
    };

    struct B {
      int Bar;
      A Baz;
      int Qux;
    };

    void target(int BarArg, int FooArg, int QuxArg) {
      B Quux = {BarArg, FooArg, QuxArg};
      B OtherB;
      /*[[p]]*/
    }
  )";
  for (const std::string &Code : {BracesCode, BraceElisionCode}) {
    runDataflow(
        Code,
        [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
           ASTContext &ASTCtx) {
          ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
          const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

          const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
          ASSERT_THAT(FooDecl, NotNull());

          const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
          ASSERT_THAT(BarDecl, NotNull());

          const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
          ASSERT_THAT(BazDecl, NotNull());

          const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux");
          ASSERT_THAT(QuxDecl, NotNull());

          const ValueDecl *FooArgDecl = findValueDecl(ASTCtx, "FooArg");
          ASSERT_THAT(FooArgDecl, NotNull());

          const ValueDecl *BarArgDecl = findValueDecl(ASTCtx, "BarArg");
          ASSERT_THAT(BarArgDecl, NotNull());

          const ValueDecl *QuxArgDecl = findValueDecl(ASTCtx, "QuxArg");
          ASSERT_THAT(QuxArgDecl, NotNull());

          const ValueDecl *QuuxDecl = findValueDecl(ASTCtx, "Quux");
          ASSERT_THAT(QuuxDecl, NotNull());

          const auto *FooArgVal = cast<IntegerValue>(Env.getValue(*FooArgDecl));
          const auto *BarArgVal = cast<IntegerValue>(Env.getValue(*BarArgDecl));
          const auto *QuxArgVal = cast<IntegerValue>(Env.getValue(*QuxArgDecl));

          const auto &QuuxLoc =
              *cast<RecordStorageLocation>(Env.getStorageLocation(*QuuxDecl));
          const auto &BazLoc =
              *cast<RecordStorageLocation>(QuuxLoc.getChild(*BazDecl));

          EXPECT_EQ(getFieldValue(&QuuxLoc, *BarDecl, Env), BarArgVal);
          EXPECT_EQ(getFieldValue(&BazLoc, *FooDecl, Env), FooArgVal);
          EXPECT_EQ(getFieldValue(&QuuxLoc, *QuxDecl, Env), QuxArgVal);

          // Check that fields initialized in an initializer list are always
          // modeled in other instances of the same type.
          const auto &OtherBLoc =
              getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "OtherB");
          EXPECT_THAT(OtherBLoc.getChild(*BarDecl), NotNull());
          EXPECT_THAT(OtherBLoc.getChild(*BazDecl), NotNull());
          EXPECT_THAT(OtherBLoc.getChild(*QuxDecl), NotNull());
        });
  }
}

TEST(TransferTest, AggregateInitializationReferenceField) {
  std::string Code = R"(
    struct S {
      int &RefField;
    };

    void target(int i) {
      S s = { i };
      /*[[p]]*/
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *RefFieldDecl = findValueDecl(ASTCtx, "RefField");

        auto &ILoc = getLocForDecl<StorageLocation>(ASTCtx, Env, "i");
        auto &SLoc = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s");

        EXPECT_EQ(SLoc.getChild(*RefFieldDecl), &ILoc);
      });
}

TEST(TransferTest, AggregateInitialization_NotExplicitlyInitializedField) {
  std::string Code = R"(
    struct S {
      int i1;
      int i2;
    };

    void target(int i) {
      S s = { i };
      /*[[p]]*/
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *I1FieldDecl = findValueDecl(ASTCtx, "i1");
        const ValueDecl *I2FieldDecl = findValueDecl(ASTCtx, "i2");

        auto &SLoc = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s");

        auto &IValue = getValueForDecl<IntegerValue>(ASTCtx, Env, "i");
        auto &I1Value =
            *cast<IntegerValue>(getFieldValue(&SLoc, *I1FieldDecl, Env));
        EXPECT_EQ(&I1Value, &IValue);
        auto &I2Value =
            *cast<IntegerValue>(getFieldValue(&SLoc, *I2FieldDecl, Env));
        EXPECT_NE(&I2Value, &IValue);
      });
}

TEST(TransferTest, AggregateInitializationFunctionPointer) {
  // This is a repro for an assertion failure.
  // nullptr takes on the type of a const function pointer, but its type was
  // asserted to be equal to the *unqualified* type of Field, which no longer
  // included the const.
  std::string Code = R"(
    struct S {
      void (*const Field)();
    };

    void target() {
      S s{nullptr};
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {});
}

TEST(TransferTest, AssignToUnionMember) {
  std::string Code = R"(
    union A {
      int Foo;
    };

    void target(int Bar) {
      A Baz;
      Baz.Foo = Bar;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());
        ASSERT_TRUE(BazDecl->getType()->isUnionType());

        auto BazFields = BazDecl->getType()->getAsRecordDecl()->fields();
        FieldDecl *FooDecl = nullptr;
        for (FieldDecl *Field : BazFields) {
          if (Field->getNameAsString() == "Foo") {
            FooDecl = Field;
          } else {
            FAIL() << "Unexpected field: " << Field->getNameAsString();
          }
        }
        ASSERT_THAT(FooDecl, NotNull());

        const auto *BazLoc = dyn_cast_or_null<RecordStorageLocation>(
            Env.getStorageLocation(*BazDecl));
        ASSERT_THAT(BazLoc, NotNull());

        const auto *FooVal =
            cast<IntegerValue>(getFieldValue(BazLoc, *FooDecl, Env));

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());
        const auto *BarLoc = Env.getStorageLocation(*BarDecl);
        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(BarLoc));

        EXPECT_EQ(Env.getValue(*BarLoc), FooVal);
      });
}

TEST(TransferTest, AssignFromBoolLiteral) {
  std::string Code = R"(
    void target() {
      bool Foo = true;
      bool Bar = false;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const auto *FooVal =
            dyn_cast_or_null<BoolValue>(Env.getValue(*FooDecl));
        ASSERT_THAT(FooVal, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const auto *BarVal =
            dyn_cast_or_null<BoolValue>(Env.getValue(*BarDecl));
        ASSERT_THAT(BarVal, NotNull());

        EXPECT_EQ(FooVal, &Env.getBoolLiteralValue(true));
        EXPECT_EQ(BarVal, &Env.getBoolLiteralValue(false));
      });
}

TEST(TransferTest, AssignFromCompositeBoolExpression) {
  {
    std::string Code = R"(
    void target(bool Foo, bool Bar, bool Qux) {
      bool Baz = (Foo) && (Bar || Qux);
      // [[p]]
    }
  )";
    runDataflow(
        Code,
        [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
           ASTContext &ASTCtx) {
          ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
          const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

          const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
          ASSERT_THAT(FooDecl, NotNull());

          const auto *FooVal =
              dyn_cast_or_null<BoolValue>(Env.getValue(*FooDecl));
          ASSERT_THAT(FooVal, NotNull());

          const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
          ASSERT_THAT(BarDecl, NotNull());

          const auto *BarVal =
              dyn_cast_or_null<BoolValue>(Env.getValue(*BarDecl));
          ASSERT_THAT(BarVal, NotNull());

          const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux");
          ASSERT_THAT(QuxDecl, NotNull());

          const auto *QuxVal =
              dyn_cast_or_null<BoolValue>(Env.getValue(*QuxDecl));
          ASSERT_THAT(QuxVal, NotNull());

          const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
          ASSERT_THAT(BazDecl, NotNull());

          const auto *BazVal =
              dyn_cast_or_null<BoolValue>(Env.getValue(*BazDecl));
          ASSERT_THAT(BazVal, NotNull());
          auto &A = Env.arena();
          EXPECT_EQ(&BazVal->formula(),
                    &A.makeAnd(FooVal->formula(),
                               A.makeOr(BarVal->formula(), QuxVal->formula())));
        });
  }

  {
    std::string Code = R"(
    void target(bool Foo, bool Bar, bool Qux) {
      bool Baz = (Foo && Qux) || (Bar);
      // [[p]]
    }
  )";
    runDataflow(
        Code,
        [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
           ASTContext &ASTCtx) {
          ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
          const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

          const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
          ASSERT_THAT(FooDecl, NotNull());

          const auto *FooVal =
              dyn_cast_or_null<BoolValue>(Env.getValue(*FooDecl));
          ASSERT_THAT(FooVal, NotNull());

          const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
          ASSERT_THAT(BarDecl, NotNull());

          const auto *BarVal =
              dyn_cast_or_null<BoolValue>(Env.getValue(*BarDecl));
          ASSERT_THAT(BarVal, NotNull());

          const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux");
          ASSERT_THAT(QuxDecl, NotNull());

          const auto *QuxVal =
              dyn_cast_or_null<BoolValue>(Env.getValue(*QuxDecl));
          ASSERT_THAT(QuxVal, NotNull());

          const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
          ASSERT_THAT(BazDecl, NotNull());

          const auto *BazVal =
              dyn_cast_or_null<BoolValue>(Env.getValue(*BazDecl));
          ASSERT_THAT(BazVal, NotNull());
          auto &A = Env.arena();
          EXPECT_EQ(&BazVal->formula(),
                    &A.makeOr(A.makeAnd(FooVal->formula(), QuxVal->formula()),
                              BarVal->formula()));
        });
  }

  {
    std::string Code = R"(
      void target(bool A, bool B, bool C, bool D) {
        bool Foo = ((A && B) && C) && D;
        // [[p]]
      }
    )";
    runDataflow(
        Code,
        [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
           ASTContext &ASTCtx) {
          ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
          const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

          const ValueDecl *ADecl = findValueDecl(ASTCtx, "A");
          ASSERT_THAT(ADecl, NotNull());

          const auto *AVal = dyn_cast_or_null<BoolValue>(Env.getValue(*ADecl));
          ASSERT_THAT(AVal, NotNull());

          const ValueDecl *BDecl = findValueDecl(ASTCtx, "B");
          ASSERT_THAT(BDecl, NotNull());

          const auto *BVal = dyn_cast_or_null<BoolValue>(Env.getValue(*BDecl));
          ASSERT_THAT(BVal, NotNull());

          const ValueDecl *CDecl = findValueDecl(ASTCtx, "C");
          ASSERT_THAT(CDecl, NotNull());

          const auto *CVal = dyn_cast_or_null<BoolValue>(Env.getValue(*CDecl));
          ASSERT_THAT(CVal, NotNull());

          const ValueDecl *DDecl = findValueDecl(ASTCtx, "D");
          ASSERT_THAT(DDecl, NotNull());

          const auto *DVal = dyn_cast_or_null<BoolValue>(Env.getValue(*DDecl));
          ASSERT_THAT(DVal, NotNull());

          const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
          ASSERT_THAT(FooDecl, NotNull());

          const auto *FooVal =
              dyn_cast_or_null<BoolValue>(Env.getValue(*FooDecl));
          ASSERT_THAT(FooVal, NotNull());
          auto &A = Env.arena();
          EXPECT_EQ(
              &FooVal->formula(),
              &A.makeAnd(A.makeAnd(A.makeAnd(AVal->formula(), BVal->formula()),
                                   CVal->formula()),
                         DVal->formula()));
        });
  }
}

TEST(TransferTest, AssignFromBoolNegation) {
  std::string Code = R"(
    void target() {
      bool Foo = true;
      bool Bar = !(Foo);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const auto *FooVal =
            dyn_cast_or_null<BoolValue>(Env.getValue(*FooDecl));
        ASSERT_THAT(FooVal, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const auto *BarVal =
            dyn_cast_or_null<BoolValue>(Env.getValue(*BarDecl));
        ASSERT_THAT(BarVal, NotNull());
        auto &A = Env.arena();
        EXPECT_EQ(&BarVal->formula(), &A.makeNot(FooVal->formula()));
      });
}

TEST(TransferTest, BuiltinExpect) {
  std::string Code = R"(
    void target(long Foo) {
      long Bar = __builtin_expect(Foo, true);
      /*[[p]]*/
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        EXPECT_EQ(Env.getValue(*FooDecl), Env.getValue(*BarDecl));
      });
}

// `__builtin_expect` takes and returns a `long` argument, so other types
// involve casts. This verifies that we identify the input and output in that
// case.
TEST(TransferTest, BuiltinExpectBoolArg) {
  std::string Code = R"(
    void target(bool Foo) {
      bool Bar = __builtin_expect(Foo, true);
      /*[[p]]*/
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        EXPECT_EQ(Env.getValue(*FooDecl), Env.getValue(*BarDecl));
      });
}

TEST(TransferTest, BuiltinUnreachable) {
  std::string Code = R"(
    void target(bool Foo) {
      bool Bar = false;
      if (Foo)
        Bar = Foo;
      else
        __builtin_unreachable();
      (void)0;
      /*[[p]]*/
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        // `__builtin_unreachable` promises that the code is
        // unreachable, so the compiler treats the "then" branch as the
        // only possible predecessor of this statement.
        EXPECT_EQ(Env.getValue(*FooDecl), Env.getValue(*BarDecl));
      });
}

TEST(TransferTest, BuiltinTrap) {
  std::string Code = R"(
    void target(bool Foo) {
      bool Bar = false;
      if (Foo)
        Bar = Foo;
      else
        __builtin_trap();
      (void)0;
      /*[[p]]*/
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        // `__builtin_trap` ensures program termination, so only the
        // "then" branch is a predecessor of this statement.
        EXPECT_EQ(Env.getValue(*FooDecl), Env.getValue(*BarDecl));
      });
}

TEST(TransferTest, BuiltinDebugTrap) {
  std::string Code = R"(
    void target(bool Foo) {
      bool Bar = false;
      if (Foo)
        Bar = Foo;
      else
        __builtin_debugtrap();
      (void)0;
      /*[[p]]*/
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        // `__builtin_debugtrap` doesn't ensure program termination.
        EXPECT_NE(Env.getValue(*FooDecl), Env.getValue(*BarDecl));
      });
}

TEST(TransferTest, StaticIntSingleVarDecl) {
  std::string Code = R"(
    void target() {
      static int Foo;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));

        const Value *FooVal = Env.getValue(*FooLoc);
        EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));
      });
}

TEST(TransferTest, StaticIntGroupVarDecl) {
  std::string Code = R"(
    void target() {
      static int Foo, Bar;
      (void)0;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));

        const StorageLocation *BarLoc = Env.getStorageLocation(*BarDecl);
        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(BarLoc));

        const Value *FooVal = Env.getValue(*FooLoc);
        EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));

        const Value *BarVal = Env.getValue(*BarLoc);
        EXPECT_TRUE(isa_and_nonnull<IntegerValue>(BarVal));

        EXPECT_NE(FooVal, BarVal);
      });
}

TEST(TransferTest, GlobalIntVarDecl) {
  std::string Code = R"(
    static int Foo;

    void target() {
      int Bar = Foo;
      int Baz = Foo;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        const Value *BarVal = cast<IntegerValue>(Env.getValue(*BarDecl));
        const Value *BazVal = cast<IntegerValue>(Env.getValue(*BazDecl));
        EXPECT_EQ(BarVal, BazVal);
      });
}

TEST(TransferTest, StaticMemberIntVarDecl) {
  std::string Code = R"(
    struct A {
      static int Foo;
    };

    void target(A a) {
      int Bar = a.Foo;
      int Baz = a.Foo;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        const Value *BarVal = cast<IntegerValue>(Env.getValue(*BarDecl));
        const Value *BazVal = cast<IntegerValue>(Env.getValue(*BazDecl));
        EXPECT_EQ(BarVal, BazVal);
      });
}

TEST(TransferTest, StaticMemberRefVarDecl) {
  std::string Code = R"(
    struct A {
      static int &Foo;
    };

    void target(A a) {
      int Bar = a.Foo;
      int Baz = a.Foo;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        const Value *BarVal = cast<IntegerValue>(Env.getValue(*BarDecl));
        const Value *BazVal = cast<IntegerValue>(Env.getValue(*BazDecl));
        EXPECT_EQ(BarVal, BazVal);
      });
}

TEST(TransferTest, AssignMemberBeforeCopy) {
  std::string Code = R"(
    struct A {
      int Foo;
    };

    void target() {
      A A1;
      A A2;
      int Bar;
      A1.Foo = Bar;
      A2 = A1;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const ValueDecl *A1Decl = findValueDecl(ASTCtx, "A1");
        ASSERT_THAT(A1Decl, NotNull());

        const ValueDecl *A2Decl = findValueDecl(ASTCtx, "A2");
        ASSERT_THAT(A2Decl, NotNull());

        const auto *BarVal = cast<IntegerValue>(Env.getValue(*BarDecl));

        const auto &A2Loc =
            *cast<RecordStorageLocation>(Env.getStorageLocation(*A2Decl));
        EXPECT_EQ(getFieldValue(&A2Loc, *FooDecl, Env), BarVal);
      });
}

TEST(TransferTest, BooleanEquality) {
  std::string Code = R"(
    void target(bool Bar) {
      bool Foo = true;
      if (Bar == Foo) {
        (void)0;
        /*[[p-then]]*/
      } else {
        (void)0;
        /*[[p-else]]*/
      }
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p-then", "p-else"));
        const Environment &EnvThen =
            getEnvironmentAtAnnotation(Results, "p-then");
        const Environment &EnvElse =
            getEnvironmentAtAnnotation(Results, "p-else");

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        auto &BarValThen = getFormula(*BarDecl, EnvThen);
        EXPECT_TRUE(EnvThen.proves(BarValThen));

        auto &BarValElse = getFormula(*BarDecl, EnvElse);
        EXPECT_TRUE(EnvElse.proves(EnvElse.arena().makeNot(BarValElse)));
      });
}

TEST(TransferTest, BooleanInequality) {
  std::string Code = R"(
    void target(bool Bar) {
      bool Foo = true;
      if (Bar != Foo) {
        (void)0;
        /*[[p-then]]*/
      } else {
        (void)0;
        /*[[p-else]]*/
      }
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p-then", "p-else"));
        const Environment &EnvThen =
            getEnvironmentAtAnnotation(Results, "p-then");
        const Environment &EnvElse =
            getEnvironmentAtAnnotation(Results, "p-else");

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        auto &BarValThen = getFormula(*BarDecl, EnvThen);
        EXPECT_TRUE(EnvThen.proves(EnvThen.arena().makeNot(BarValThen)));

        auto &BarValElse = getFormula(*BarDecl, EnvElse);
        EXPECT_TRUE(EnvElse.proves(BarValElse));
      });
}

TEST(TransferTest, PointerEquality) {
  std::string Code = R"cc(
    void target() {
      int i = 0;
      int i_other = 0;
      int *p1 = &i;
      int *p2 = &i;
      int *p_other = &i_other;
      int *null = nullptr;

      bool p1_eq_p1 = (p1 == p1);
      bool p1_eq_p2 = (p1 == p2);
      bool p1_eq_p_other = (p1 == p_other);

      bool p1_eq_null = (p1 == null);
      bool p1_eq_nullptr = (p1 == nullptr);
      bool null_eq_nullptr = (null == nullptr);
      bool nullptr_eq_nullptr = (nullptr == nullptr);

      // We won't duplicate all of the tests above with `!=`, as we know that
      // the implementation simply negates the result of the `==` comparison.
      // Instaed, just spot-check one case.
      bool p1_ne_p1 = (p1 != p1);

      (void)0; // [[p]]
    }
  )cc";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        // Check the we have indeed set things up so that `p1` and `p2` have
        // different pointer values.
        EXPECT_NE(&getValueForDecl<PointerValue>(ASTCtx, Env, "p1"),
                  &getValueForDecl<PointerValue>(ASTCtx, Env, "p2"));

        EXPECT_EQ(&getValueForDecl<BoolValue>(ASTCtx, Env, "p1_eq_p1"),
                  &Env.getBoolLiteralValue(true));
        EXPECT_EQ(&getValueForDecl<BoolValue>(ASTCtx, Env, "p1_eq_p2"),
                  &Env.getBoolLiteralValue(true));
        EXPECT_TRUE(isa<AtomicBoolValue>(
            getValueForDecl<BoolValue>(ASTCtx, Env, "p1_eq_p_other")));

        EXPECT_TRUE(isa<AtomicBoolValue>(
            getValueForDecl<BoolValue>(ASTCtx, Env, "p1_eq_null")));
        EXPECT_TRUE(isa<AtomicBoolValue>(
            getValueForDecl<BoolValue>(ASTCtx, Env, "p1_eq_nullptr")));
        EXPECT_EQ(&getValueForDecl<BoolValue>(ASTCtx, Env, "null_eq_nullptr"),
                  &Env.getBoolLiteralValue(true));
        EXPECT_EQ(
            &getValueForDecl<BoolValue>(ASTCtx, Env, "nullptr_eq_nullptr"),
            &Env.getBoolLiteralValue(true));

        EXPECT_EQ(&getValueForDecl<BoolValue>(ASTCtx, Env, "p1_ne_p1"),
                  &Env.getBoolLiteralValue(false));
      });
}

TEST(TransferTest, PointerEqualityUnionMembers) {
  std::string Code = R"cc(
    union U {
      int i1;
      int i2;
    };
    void target() {
      U u;
      bool i1_eq_i2 = (&u.i1 == &u.i2);

      (void)0; // [[p]]
    }
  )cc";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        // FIXME: By the standard, `u.i1` and `u.i2` should have the same
        // address, but we don't yet model this property of union members
        // correctly. The result is therefore weaker than it could be (just an
        // atom rather than a true literal), though not wrong.
        EXPECT_TRUE(isa<AtomicBoolValue>(
            getValueForDecl<BoolValue>(ASTCtx, Env, "i1_eq_i2")));
      });
}

TEST(TransferTest, IntegerLiteralEquality) {
  std::string Code = R"(
    void target() {
      bool equal = (42 == 42);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto &Equal =
            getValueForDecl<BoolValue>(ASTCtx, Env, "equal").formula();
        EXPECT_TRUE(Env.proves(Equal));
      });
}

TEST(TransferTest, CorrelatedBranches) {
  std::string Code = R"(
    void target(bool B, bool C) {
      if (B) {
        return;
      }
      (void)0;
      /*[[p0]]*/
      if (C) {
        B = true;
        /*[[p1]]*/
      }
      if (B) {
        (void)0;
        /*[[p2]]*/
      }
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p0", "p1", "p2"));

        const ValueDecl *CDecl = findValueDecl(ASTCtx, "C");
        ASSERT_THAT(CDecl, NotNull());

        {
          const Environment &Env = getEnvironmentAtAnnotation(Results, "p0");
          const ValueDecl *BDecl = findValueDecl(ASTCtx, "B");
          ASSERT_THAT(BDecl, NotNull());
          auto &BVal = getFormula(*BDecl, Env);

          EXPECT_TRUE(Env.proves(Env.arena().makeNot(BVal)));
        }

        {
          const Environment &Env = getEnvironmentAtAnnotation(Results, "p1");
          auto &CVal = getFormula(*CDecl, Env);
          EXPECT_TRUE(Env.proves(CVal));
        }

        {
          const Environment &Env = getEnvironmentAtAnnotation(Results, "p2");
          auto &CVal = getFormula(*CDecl, Env);
          EXPECT_TRUE(Env.proves(CVal));
        }
      });
}

TEST(TransferTest, LoopWithAssignmentConverges) {
  std::string Code = R"(
    bool foo();

    void target() {
       do {
        bool Bar = foo();
        if (Bar) break;
        (void)Bar;
        /*[[p]]*/
      } while (true);
    }
  )";
  // The key property that we are verifying is implicit in `runDataflow` --
  // namely, that the analysis succeeds, rather than hitting the maximum number
  // of iterations.
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        auto &BarVal = getFormula(*BarDecl, Env);
        EXPECT_TRUE(Env.proves(Env.arena().makeNot(BarVal)));
      });
}

TEST(TransferTest, LoopWithStagedAssignments) {
  std::string Code = R"(
    bool foo();

    void target() {
      bool Bar = false;
      bool Err = false;
      while (foo()) {
        if (Bar)
          Err = true;
        Bar = true;
        /*[[p]]*/
      }
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());
        const ValueDecl *ErrDecl = findValueDecl(ASTCtx, "Err");
        ASSERT_THAT(ErrDecl, NotNull());

        auto &BarVal = getFormula(*BarDecl, Env);
        auto &ErrVal = getFormula(*ErrDecl, Env);
        EXPECT_TRUE(Env.proves(BarVal));
        // An unsound analysis, for example only evaluating the loop once, can
        // conclude that `Err` is false. So, we test that this conclusion is not
        // reached.
        EXPECT_FALSE(Env.proves(Env.arena().makeNot(ErrVal)));
      });
}

TEST(TransferTest, LoopWithReferenceAssignmentConverges) {
  std::string Code = R"(
    bool &foo();

    void target() {
       do {
        bool& Bar = foo();
        if (Bar) break;
        (void)Bar;
        /*[[p]]*/
      } while (true);
    }
  )";
  // The key property that we are verifying is that the analysis succeeds,
  // rather than hitting the maximum number of iterations.
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        auto &BarVal = getFormula(*BarDecl, Env);
        EXPECT_TRUE(Env.proves(Env.arena().makeNot(BarVal)));
      });
}

TEST(TransferTest, LoopWithStructReferenceAssignmentConverges) {
  std::string Code = R"(
    struct Lookup {
      int x;
    };

    void target(Lookup val, bool b) {
      const Lookup* l = nullptr;
      while (b) {
        l = &val;
        /*[[p-inner]]*/
      }
      (void)0;
      /*[[p-outer]]*/
    }
  )";
  // The key property that we are verifying is implicit in `runDataflow` --
  // namely, that the analysis succeeds, rather than hitting the maximum number
  // of iterations.
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p-inner", "p-outer"));
        const Environment &InnerEnv =
            getEnvironmentAtAnnotation(Results, "p-inner");
        const Environment &OuterEnv =
            getEnvironmentAtAnnotation(Results, "p-outer");

        const ValueDecl *ValDecl = findValueDecl(ASTCtx, "val");
        ASSERT_THAT(ValDecl, NotNull());

        const ValueDecl *LDecl = findValueDecl(ASTCtx, "l");
        ASSERT_THAT(LDecl, NotNull());

        // Inner.
        auto *LVal = dyn_cast<PointerValue>(InnerEnv.getValue(*LDecl));
        ASSERT_THAT(LVal, NotNull());

        EXPECT_EQ(&LVal->getPointeeLoc(),
                  InnerEnv.getStorageLocation(*ValDecl));

        // Outer.
        LVal = dyn_cast<PointerValue>(OuterEnv.getValue(*LDecl));
        ASSERT_THAT(LVal, NotNull());

        // The loop body may not have been executed, so we should not conclude
        // that `l` points to `val`.
        EXPECT_NE(&LVal->getPointeeLoc(),
                  OuterEnv.getStorageLocation(*ValDecl));
      });
}

TEST(TransferTest, LoopDereferencingChangingPointerConverges) {
  std::string Code = R"cc(
    bool some_condition();

    void target(int i1, int i2) {
      int *p = &i1;
      while (true) {
        (void)*p;
        if (some_condition())
          p = &i1;
        else
          p = &i2;
      }
    }
  )cc";
  ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(Code), llvm::Succeeded());
}

TEST(TransferTest, LoopDereferencingChangingRecordPointerConverges) {
  std::string Code = R"cc(
    struct Lookup {
      int x;
    };

    bool some_condition();

    void target(Lookup l1, Lookup l2) {
      Lookup *l = &l1;
      while (true) {
        (void)l->x;
        if (some_condition())
          l = &l1;
        else
          l = &l2;
      }
    }
  )cc";
  ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(Code), llvm::Succeeded());
}

TEST(TransferTest, LoopWithShortCircuitedConditionConverges) {
  std::string Code = R"cc(
    bool foo();

    void target() {
      bool c = false;
      while (foo() || foo()) {
        c = true;
      }
    }
  )cc";
  ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(Code), llvm::Succeeded());
}

TEST(TransferTest, LoopCanProveInvariantForBoolean) {
  // Check that we can prove `b` is always false in the loop.
  // This test exercises the logic in `widenDistinctValues()` that preserves
  // information if the boolean can be proved to be either true or false in both
  // the previous and current iteration.
  std::string Code = R"cc(
    int return_int();
    void target() {
      bool b = return_int() == 0;
      if (b) return;
      while (true) {
        b;
        // [[p]]
        b = return_int() == 0;
        if (b) return;
      }
    }
  )cc";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
        auto &BVal = getValueForDecl<BoolValue>(ASTCtx, Env, "b");
        EXPECT_TRUE(Env.proves(Env.arena().makeNot(BVal.formula())));
      });
}

TEST(TransferTest, DoesNotCrashOnUnionThisExpr) {
  std::string Code = R"cc(
    union Union {
      int A;
      float B;
    };

    void foo() {
      Union A;
      Union B;
      A = B;
    }
  )cc";
  // This is a crash regression test when calling the transfer function on a
  // `CXXThisExpr` that refers to a union.
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
         ASTContext &) {},
      LangStandard::lang_cxx17, /*ApplyBuiltinTransfer=*/true, "operator=");
}

TEST(TransferTest, DoesNotCrashOnNullChildren) {
  std::string Code = (CoroutineLibrary + R"cc(
    task target() noexcept {
      co_return;
    }
  )cc")
                         .str();
  // This is a crash regression test when calling `AdornedCFG::build` on a
  // statement (in this case, the `CoroutineBodyStmt`) with null children.
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
         ASTContext &) {},
      LangStandard::lang_cxx20, /*ApplyBuiltinTransfer=*/true);
}

TEST(TransferTest, StructuredBindingAssignFromStructIntMembersToRefs) {
  std::string Code = R"(
    struct A {
      int Foo;
      int Bar;
    };

    void target() {
      int Qux;
      A Baz;
      Baz.Foo = Qux;
      auto &FooRef = Baz.Foo;
      auto &BarRef = Baz.Bar;
      auto &[BoundFooRef, BoundBarRef] = Baz;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooRefDecl = findValueDecl(ASTCtx, "FooRef");
        ASSERT_THAT(FooRefDecl, NotNull());

        const ValueDecl *BarRefDecl = findValueDecl(ASTCtx, "BarRef");
        ASSERT_THAT(BarRefDecl, NotNull());

        const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux");
        ASSERT_THAT(QuxDecl, NotNull());

        const ValueDecl *BoundFooRefDecl = findValueDecl(ASTCtx, "BoundFooRef");
        ASSERT_THAT(BoundFooRefDecl, NotNull());

        const ValueDecl *BoundBarRefDecl = findValueDecl(ASTCtx, "BoundBarRef");
        ASSERT_THAT(BoundBarRefDecl, NotNull());

        const StorageLocation *FooRefLoc = Env.getStorageLocation(*FooRefDecl);
        ASSERT_THAT(FooRefLoc, NotNull());

        const StorageLocation *BarRefLoc = Env.getStorageLocation(*BarRefDecl);
        ASSERT_THAT(BarRefLoc, NotNull());

        const Value *QuxVal = Env.getValue(*QuxDecl);
        ASSERT_THAT(QuxVal, NotNull());

        const StorageLocation *BoundFooRefLoc =
            Env.getStorageLocation(*BoundFooRefDecl);
        EXPECT_EQ(BoundFooRefLoc, FooRefLoc);

        const StorageLocation *BoundBarRefLoc =
            Env.getStorageLocation(*BoundBarRefDecl);
        EXPECT_EQ(BoundBarRefLoc, BarRefLoc);

        EXPECT_EQ(Env.getValue(*BoundFooRefDecl), QuxVal);
      });
}

TEST(TransferTest, StructuredBindingAssignFromStructRefMembersToRefs) {
  std::string Code = R"(
    struct A {
      int &Foo;
      int &Bar;
    };

    void target(A Baz) {
      int Qux;
      Baz.Foo = Qux;
      auto &FooRef = Baz.Foo;
      auto &BarRef = Baz.Bar;
      auto &[BoundFooRef, BoundBarRef] = Baz;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooRefDecl = findValueDecl(ASTCtx, "FooRef");
        ASSERT_THAT(FooRefDecl, NotNull());

        const ValueDecl *BarRefDecl = findValueDecl(ASTCtx, "BarRef");
        ASSERT_THAT(BarRefDecl, NotNull());

        const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux");
        ASSERT_THAT(QuxDecl, NotNull());

        const ValueDecl *BoundFooRefDecl = findValueDecl(ASTCtx, "BoundFooRef");
        ASSERT_THAT(BoundFooRefDecl, NotNull());

        const ValueDecl *BoundBarRefDecl = findValueDecl(ASTCtx, "BoundBarRef");
        ASSERT_THAT(BoundBarRefDecl, NotNull());

        const StorageLocation *FooRefLoc = Env.getStorageLocation(*FooRefDecl);
        ASSERT_THAT(FooRefLoc, NotNull());

        const StorageLocation *BarRefLoc = Env.getStorageLocation(*BarRefDecl);
        ASSERT_THAT(BarRefLoc, NotNull());

        const Value *QuxVal = Env.getValue(*QuxDecl);
        ASSERT_THAT(QuxVal, NotNull());

        const StorageLocation *BoundFooRefLoc =
            Env.getStorageLocation(*BoundFooRefDecl);
        EXPECT_EQ(BoundFooRefLoc, FooRefLoc);

        const StorageLocation *BoundBarRefLoc =
            Env.getStorageLocation(*BoundBarRefDecl);
        EXPECT_EQ(BoundBarRefLoc, BarRefLoc);

        EXPECT_EQ(Env.getValue(*BoundFooRefDecl), QuxVal);
      });
}

TEST(TransferTest, StructuredBindingAssignFromStructIntMembersToInts) {
  std::string Code = R"(
    struct A {
      int Foo;
      int Bar;
    };

    void target() {
      int Qux;
      A Baz;
      Baz.Foo = Qux;
      auto &FooRef = Baz.Foo;
      auto &BarRef = Baz.Bar;
      auto [BoundFoo, BoundBar] = Baz;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooRefDecl = findValueDecl(ASTCtx, "FooRef");
        ASSERT_THAT(FooRefDecl, NotNull());

        const ValueDecl *BarRefDecl = findValueDecl(ASTCtx, "BarRef");
        ASSERT_THAT(BarRefDecl, NotNull());

        const ValueDecl *BoundFooDecl = findValueDecl(ASTCtx, "BoundFoo");
        ASSERT_THAT(BoundFooDecl, NotNull());

        const ValueDecl *BoundBarDecl = findValueDecl(ASTCtx, "BoundBar");
        ASSERT_THAT(BoundBarDecl, NotNull());

        const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux");
        ASSERT_THAT(QuxDecl, NotNull());

        const StorageLocation *FooRefLoc = Env.getStorageLocation(*FooRefDecl);
        ASSERT_THAT(FooRefLoc, NotNull());

        const StorageLocation *BarRefLoc = Env.getStorageLocation(*BarRefDecl);
        ASSERT_THAT(BarRefLoc, NotNull());

        const Value *QuxVal = Env.getValue(*QuxDecl);
        ASSERT_THAT(QuxVal, NotNull());

        const StorageLocation *BoundFooLoc =
            Env.getStorageLocation(*BoundFooDecl);
        EXPECT_NE(BoundFooLoc, FooRefLoc);

        const StorageLocation *BoundBarLoc =
            Env.getStorageLocation(*BoundBarDecl);
        EXPECT_NE(BoundBarLoc, BarRefLoc);

        EXPECT_EQ(Env.getValue(*BoundFooDecl), QuxVal);
      });
}

TEST(TransferTest, StructuredBindingAssignFromTupleLikeType) {
  std::string Code = R"(
    namespace std {
    using size_t = int;
    template <class> struct tuple_size;
    template <std::size_t, class> struct tuple_element;
    template <class...> class tuple;

    namespace {
    template <class T, T v>
    struct size_helper { static const T value = v; };
    } // namespace

    template <class... T>
    struct tuple_size<tuple<T...>> : size_helper<std::size_t, sizeof...(T)> {};

    template <std::size_t I, class... T>
    struct tuple_element<I, tuple<T...>> {
      using type =  __type_pack_element<I, T...>;
    };

    template <class...> class tuple {};

    template <std::size_t I, class... T>
    typename tuple_element<I, tuple<T...>>::type get(tuple<T...>);
    } // namespace std

    std::tuple<bool, int> makeTuple();

    void target(bool B) {
      auto [BoundFoo, BoundBar] = makeTuple();
      bool Baz;
      // Include if-then-else to test interaction of `BindingDecl` with join.
      if (B) {
        Baz = BoundFoo;
        (void)BoundBar;
        // [[p1]]
      } else {
        Baz = BoundFoo;
      }
      (void)0;
      // [[p2]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1", "p2"));
        const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");

        const ValueDecl *BoundFooDecl = findValueDecl(ASTCtx, "BoundFoo");
        ASSERT_THAT(BoundFooDecl, NotNull());

        const ValueDecl *BoundBarDecl = findValueDecl(ASTCtx, "BoundBar");
        ASSERT_THAT(BoundBarDecl, NotNull());

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        // BindingDecls always map to references -- either lvalue or rvalue, so
        // we still need to skip here.
        const Value *BoundFooValue = Env1.getValue(*BoundFooDecl);
        ASSERT_THAT(BoundFooValue, NotNull());
        EXPECT_TRUE(isa<BoolValue>(BoundFooValue));

        const Value *BoundBarValue = Env1.getValue(*BoundBarDecl);
        ASSERT_THAT(BoundBarValue, NotNull());
        EXPECT_TRUE(isa<IntegerValue>(BoundBarValue));

        // Test that a `DeclRefExpr` to a `BindingDecl` works as expected.
        EXPECT_EQ(Env1.getValue(*BazDecl), BoundFooValue);

        const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2");

        // Test that `BoundFooDecl` retains the value we expect, after the join.
        BoundFooValue = Env2.getValue(*BoundFooDecl);
        EXPECT_EQ(Env2.getValue(*BazDecl), BoundFooValue);
      });
}

TEST(TransferTest, StructuredBindingAssignRefFromTupleLikeType) {
  std::string Code = R"(
    namespace std {
    using size_t = int;
    template <class> struct tuple_size;
    template <std::size_t, class> struct tuple_element;
    template <class...> class tuple;

    namespace {
    template <class T, T v>
    struct size_helper { static const T value = v; };
    } // namespace

    template <class... T>
    struct tuple_size<tuple<T...>> : size_helper<std::size_t, sizeof...(T)> {};

    template <std::size_t I, class... T>
    struct tuple_element<I, tuple<T...>> {
      using type =  __type_pack_element<I, T...>;
    };

    template <class...> class tuple {};

    template <std::size_t I, class... T>
    typename tuple_element<I, tuple<T...>>::type get(tuple<T...>);
    } // namespace std

    std::tuple<bool, int> &getTuple();

    void target(bool B) {
      auto &[BoundFoo, BoundBar] = getTuple();
      bool Baz;
      // Include if-then-else to test interaction of `BindingDecl` with join.
      if (B) {
        Baz = BoundFoo;
        (void)BoundBar;
        // [[p1]]
      } else {
        Baz = BoundFoo;
      }
      (void)0;
      // [[p2]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1", "p2"));
        const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");

        const ValueDecl *BoundFooDecl = findValueDecl(ASTCtx, "BoundFoo");
        ASSERT_THAT(BoundFooDecl, NotNull());

        const ValueDecl *BoundBarDecl = findValueDecl(ASTCtx, "BoundBar");
        ASSERT_THAT(BoundBarDecl, NotNull());

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        const Value *BoundFooValue = Env1.getValue(*BoundFooDecl);
        ASSERT_THAT(BoundFooValue, NotNull());
        EXPECT_TRUE(isa<BoolValue>(BoundFooValue));

        const Value *BoundBarValue = Env1.getValue(*BoundBarDecl);
        ASSERT_THAT(BoundBarValue, NotNull());
        EXPECT_TRUE(isa<IntegerValue>(BoundBarValue));

        // Test that a `DeclRefExpr` to a `BindingDecl` (with reference type)
        // works as expected. We don't test aliasing properties of the
        // reference, because we don't model `std::get` and so have no way to
        // equate separate references into the tuple.
        EXPECT_EQ(Env1.getValue(*BazDecl), BoundFooValue);

        const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2");

        // Test that `BoundFooDecl` retains the value we expect, after the join.
        BoundFooValue = Env2.getValue(*BoundFooDecl);
        EXPECT_EQ(Env2.getValue(*BazDecl), BoundFooValue);
      });
}

TEST(TransferTest, BinaryOperatorComma) {
  std::string Code = R"(
    void target(int Foo, int Bar) {
      int &Baz = (Foo, Bar);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        const StorageLocation *BarLoc = Env.getStorageLocation(*BarDecl);
        ASSERT_THAT(BarLoc, NotNull());

        const StorageLocation *BazLoc = Env.getStorageLocation(*BazDecl);
        EXPECT_EQ(BazLoc, BarLoc);
      });
}

TEST(TransferTest, ConditionalOperatorValue) {
  std::string Code = R"(
    void target(bool Cond, bool B1, bool B2) {
      bool JoinSame = Cond ? B1 : B1;
      bool JoinDifferent = Cond ? B1 : B2;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        Environment Env = getEnvironmentAtAnnotation(Results, "p").fork();

        auto &B1 = getValueForDecl<BoolValue>(ASTCtx, Env, "B1");
        auto &B2 = getValueForDecl<BoolValue>(ASTCtx, Env, "B2");
        auto &JoinSame = getValueForDecl<BoolValue>(ASTCtx, Env, "JoinSame");
        auto &JoinDifferent =
            getValueForDecl<BoolValue>(ASTCtx, Env, "JoinDifferent");

        EXPECT_EQ(&JoinSame, &B1);

        const Formula &JoinDifferentEqB1 =
            Env.arena().makeEquals(JoinDifferent.formula(), B1.formula());
        EXPECT_TRUE(Env.allows(JoinDifferentEqB1));
        EXPECT_FALSE(Env.proves(JoinDifferentEqB1));

        const Formula &JoinDifferentEqB2 =
            Env.arena().makeEquals(JoinDifferent.formula(), B2.formula());
        EXPECT_TRUE(Env.allows(JoinDifferentEqB2));
        EXPECT_FALSE(Env.proves(JoinDifferentEqB1));
      });
}

TEST(TransferTest, ConditionalOperatorLocation) {
  std::string Code = R"(
    void target(bool Cond, int I1, int I2) {
      int &JoinSame = Cond ? I1 : I1;
      int &JoinDifferent = Cond ? I1 : I2;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        Environment Env = getEnvironmentAtAnnotation(Results, "p").fork();

        StorageLocation &I1 = getLocForDecl(ASTCtx, Env, "I1");
        StorageLocation &I2 = getLocForDecl(ASTCtx, Env, "I2");
        StorageLocation &JoinSame = getLocForDecl(ASTCtx, Env, "JoinSame");
        StorageLocation &JoinDifferent =
            getLocForDecl(ASTCtx, Env, "JoinDifferent");

        EXPECT_EQ(&JoinSame, &I1);

        EXPECT_NE(&JoinDifferent, &I1);
        EXPECT_NE(&JoinDifferent, &I2);
      });
}

TEST(TransferTest, ConditionalOperatorOnConstantExpr) {
  // This is a regression test: We used to crash when a `ConstantExpr` was used
  // in the branches of a conditional operator.
  std::string Code = R"cc(
    consteval bool identity(bool B) { return B; }
    void target(bool Cond) {
      bool JoinTrueTrue = Cond ? identity(true) : identity(true);
      bool JoinTrueFalse = Cond ? identity(true) : identity(false);
      // [[p]]
    }
  )cc";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        Environment Env = getEnvironmentAtAnnotation(Results, "p").fork();

        auto &JoinTrueTrue =
            getValueForDecl<BoolValue>(ASTCtx, Env, "JoinTrueTrue");
        // FIXME: This test documents the current behavior, namely that we
        // don't actually use the constant result of the `ConstantExpr` and
        // instead treat it like a normal function call.
        EXPECT_EQ(JoinTrueTrue.formula().kind(), Formula::Kind::AtomRef);
        // EXPECT_TRUE(JoinTrueTrue.formula().literal());

        auto &JoinTrueFalse =
            getValueForDecl<BoolValue>(ASTCtx, Env, "JoinTrueFalse");
        EXPECT_EQ(JoinTrueFalse.formula().kind(), Formula::Kind::AtomRef);
      },
      LangStandard::lang_cxx20);
}

TEST(TransferTest, IfStmtBranchExtendsFlowCondition) {
  std::string Code = R"(
    void target(bool Foo) {
      if (Foo) {
        (void)0;
        // [[if_then]]
      } else {
        (void)0;
        // [[if_else]]
      }
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("if_then", "if_else"));
        const Environment &ThenEnv =
            getEnvironmentAtAnnotation(Results, "if_then");
        const Environment &ElseEnv =
            getEnvironmentAtAnnotation(Results, "if_else");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &ThenFooVal= getFormula(*FooDecl, ThenEnv);
        EXPECT_TRUE(ThenEnv.proves(ThenFooVal));

        auto &ElseFooVal = getFormula(*FooDecl, ElseEnv);
        EXPECT_TRUE(ElseEnv.proves(ElseEnv.arena().makeNot(ElseFooVal)));
      });
}

TEST(TransferTest, WhileStmtBranchExtendsFlowCondition) {
  std::string Code = R"(
    void target(bool Foo) {
      while (Foo) {
        (void)0;
        // [[loop_body]]
      }
      (void)0;
      // [[after_loop]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(),
                    UnorderedElementsAre("loop_body", "after_loop"));
        const Environment &LoopBodyEnv =
            getEnvironmentAtAnnotation(Results, "loop_body");
        const Environment &AfterLoopEnv =
            getEnvironmentAtAnnotation(Results, "after_loop");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &LoopBodyFooVal = getFormula(*FooDecl, LoopBodyEnv);
        EXPECT_TRUE(LoopBodyEnv.proves(LoopBodyFooVal));

        auto &AfterLoopFooVal = getFormula(*FooDecl, AfterLoopEnv);
        EXPECT_TRUE(
            AfterLoopEnv.proves(AfterLoopEnv.arena().makeNot(AfterLoopFooVal)));
      });
}

TEST(TransferTest, DoWhileStmtBranchExtendsFlowCondition) {
  std::string Code = R"(
    void target(bool Foo) {
      bool Bar = true;
      do {
        (void)0;
        // [[loop_body]]
        Bar = false;
      } while (Foo);
      (void)0;
      // [[after_loop]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(),
                    UnorderedElementsAre("loop_body", "after_loop"));
        const Environment &LoopBodyEnv =
            getEnvironmentAtAnnotation(Results, "loop_body");
        const Environment &AfterLoopEnv =
            getEnvironmentAtAnnotation(Results, "after_loop");
        auto &A = AfterLoopEnv.arena();

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        auto &LoopBodyFooVal= getFormula(*FooDecl, LoopBodyEnv);
        auto &LoopBodyBarVal = getFormula(*BarDecl, LoopBodyEnv);
        EXPECT_TRUE(
            LoopBodyEnv.proves(A.makeOr(LoopBodyBarVal, LoopBodyFooVal)));

        auto &AfterLoopFooVal = getFormula(*FooDecl, AfterLoopEnv);
        auto &AfterLoopBarVal = getFormula(*BarDecl, AfterLoopEnv);
        EXPECT_TRUE(AfterLoopEnv.proves(A.makeNot(AfterLoopFooVal)));
        EXPECT_TRUE(AfterLoopEnv.proves(A.makeNot(AfterLoopBarVal)));
      });
}

TEST(TransferTest, ForStmtBranchExtendsFlowCondition) {
  std::string Code = R"(
    void target(bool Foo) {
      for (; Foo;) {
        (void)0;
        // [[loop_body]]
      }
      (void)0;
      // [[after_loop]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(),
                    UnorderedElementsAre("loop_body", "after_loop"));
        const Environment &LoopBodyEnv =
            getEnvironmentAtAnnotation(Results, "loop_body");
        const Environment &AfterLoopEnv =
            getEnvironmentAtAnnotation(Results, "after_loop");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &LoopBodyFooVal= getFormula(*FooDecl, LoopBodyEnv);
        EXPECT_TRUE(LoopBodyEnv.proves(LoopBodyFooVal));

        auto &AfterLoopFooVal = getFormula(*FooDecl, AfterLoopEnv);
        EXPECT_TRUE(
            AfterLoopEnv.proves(AfterLoopEnv.arena().makeNot(AfterLoopFooVal)));
      });
}

TEST(TransferTest, ForStmtBranchWithoutConditionDoesNotExtendFlowCondition) {
  std::string Code = R"(
    void target(bool Foo) {
      for (;;) {
        (void)0;
        // [[loop_body]]
      }
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("loop_body"));
        const Environment &LoopBodyEnv =
            getEnvironmentAtAnnotation(Results, "loop_body");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &LoopBodyFooVal= getFormula(*FooDecl, LoopBodyEnv);
        EXPECT_FALSE(LoopBodyEnv.proves(LoopBodyFooVal));
      });
}

TEST(TransferTest, ContextSensitiveOptionDisabled) {
  std::string Code = R"(
    bool GiveBool();
    void SetBool(bool &Var) { Var = true; }

    void target() {
      bool Foo = GiveBool();
      SetBool(Foo);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_FALSE(Env.proves(FooVal));
        EXPECT_FALSE(Env.proves(Env.arena().makeNot(FooVal)));
      },
      {BuiltinOptions{/*.ContextSensitiveOpts=*/std::nullopt}});
}

TEST(TransferTest, ContextSensitiveReturnReference) {
  std::string Code = R"(
    class S {};
    S& target(bool b, S &s) {
      return s;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *SDecl = findValueDecl(ASTCtx, "s");
        ASSERT_THAT(SDecl, NotNull());

        auto *SLoc = Env.getStorageLocation(*SDecl);
        ASSERT_THAT(SLoc, NotNull());

        ASSERT_THAT(Env.getReturnStorageLocation(), Eq(SLoc));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

// This test is a regression test, based on a real crash.
TEST(TransferTest, ContextSensitiveReturnReferenceWithConditionalOperator) {
  std::string Code = R"(
    class S {};
    S& target(bool b, S &s) {
      return b ? s : s;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *SDecl = findValueDecl(ASTCtx, "s");
        ASSERT_THAT(SDecl, NotNull());

        auto *SLoc = Env.getStorageLocation(*SDecl);
        EXPECT_THAT(SLoc, NotNull());

        auto *Loc = Env.getReturnStorageLocation();
        EXPECT_THAT(Loc, NotNull());

        EXPECT_EQ(Loc, SLoc);
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveReturnOneOfTwoReferences) {
  std::string Code = R"(
    class S {};
    S &callee(bool b, S &s1_parm, S &s2_parm) {
      if (b)
        return s1_parm;
      else
        return s2_parm;
    }
    void target(bool b) {
      S s1;
      S s2;
      S &return_s1 = s1;
      S &return_s2 = s2;
      S &return_dont_know = callee(b, s1, s2);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *S1 = findValueDecl(ASTCtx, "s1");
        ASSERT_THAT(S1, NotNull());
        const ValueDecl *S2 = findValueDecl(ASTCtx, "s2");
        ASSERT_THAT(S2, NotNull());
        const ValueDecl *ReturnS1 = findValueDecl(ASTCtx, "return_s1");
        ASSERT_THAT(ReturnS1, NotNull());
        const ValueDecl *ReturnS2 = findValueDecl(ASTCtx, "return_s2");
        ASSERT_THAT(ReturnS2, NotNull());
        const ValueDecl *ReturnDontKnow =
            findValueDecl(ASTCtx, "return_dont_know");
        ASSERT_THAT(ReturnDontKnow, NotNull());

        StorageLocation *S1Loc = Env.getStorageLocation(*S1);
        StorageLocation *S2Loc = Env.getStorageLocation(*S2);

        EXPECT_THAT(Env.getStorageLocation(*ReturnS1), Eq(S1Loc));
        EXPECT_THAT(Env.getStorageLocation(*ReturnS2), Eq(S2Loc));

        // In the case where we don't have a consistent storage location for
        // the return value, the framework creates a new storage location, which
        // should be different from the storage locations of `s1` and `s2`.
        EXPECT_THAT(Env.getStorageLocation(*ReturnDontKnow), Ne(S1Loc));
        EXPECT_THAT(Env.getStorageLocation(*ReturnDontKnow), Ne(S2Loc));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveDepthZero) {
  std::string Code = R"(
    bool GiveBool();
    void SetBool(bool &Var) { Var = true; }

    void target() {
      bool Foo = GiveBool();
      SetBool(Foo);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_FALSE(Env.proves(FooVal));
        EXPECT_FALSE(Env.proves(Env.arena().makeNot(FooVal)));
      },
      {BuiltinOptions{ContextSensitiveOptions{/*.Depth=*/0}}});
}

TEST(TransferTest, ContextSensitiveSetTrue) {
  std::string Code = R"(
    bool GiveBool();
    void SetBool(bool &Var) { Var = true; }

    void target() {
      bool Foo = GiveBool();
      SetBool(Foo);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_TRUE(Env.proves(FooVal));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveSetFalse) {
  std::string Code = R"(
    bool GiveBool();
    void SetBool(bool &Var) { Var = false; }

    void target() {
      bool Foo = GiveBool();
      SetBool(Foo);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_TRUE(Env.proves(Env.arena().makeNot(FooVal)));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveSetBothTrueAndFalse) {
  std::string Code = R"(
    bool GiveBool();
    void SetBool(bool &Var, bool Val) { Var = Val; }

    void target() {
      bool Foo = GiveBool();
      bool Bar = GiveBool();
      SetBool(Foo, true);
      SetBool(Bar, false);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
        auto &A = Env.arena();

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_TRUE(Env.proves(FooVal));
        EXPECT_FALSE(Env.proves(A.makeNot(FooVal)));

        auto &BarVal = getFormula(*BarDecl, Env);
        EXPECT_FALSE(Env.proves(BarVal));
        EXPECT_TRUE(Env.proves(A.makeNot(BarVal)));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveSetTwoLayersDepthOne) {
  std::string Code = R"(
    bool GiveBool();
    void SetBool1(bool &Var) { Var = true; }
    void SetBool2(bool &Var) { SetBool1(Var); }

    void target() {
      bool Foo = GiveBool();
      SetBool2(Foo);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_FALSE(Env.proves(FooVal));
        EXPECT_FALSE(Env.proves(Env.arena().makeNot(FooVal)));
      },
      {BuiltinOptions{ContextSensitiveOptions{/*.Depth=*/1}}});
}

TEST(TransferTest, ContextSensitiveSetTwoLayersDepthTwo) {
  std::string Code = R"(
    bool GiveBool();
    void SetBool1(bool &Var) { Var = true; }
    void SetBool2(bool &Var) { SetBool1(Var); }

    void target() {
      bool Foo = GiveBool();
      SetBool2(Foo);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_TRUE(Env.proves(FooVal));
      },
      {BuiltinOptions{ContextSensitiveOptions{/*.Depth=*/2}}});
}

TEST(TransferTest, ContextSensitiveSetThreeLayersDepthTwo) {
  std::string Code = R"(
    bool GiveBool();
    void SetBool1(bool &Var) { Var = true; }
    void SetBool2(bool &Var) { SetBool1(Var); }
    void SetBool3(bool &Var) { SetBool2(Var); }

    void target() {
      bool Foo = GiveBool();
      SetBool3(Foo);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_FALSE(Env.proves(FooVal));
        EXPECT_FALSE(Env.proves(Env.arena().makeNot(FooVal)));
      },
      {BuiltinOptions{ContextSensitiveOptions{/*.Depth=*/2}}});
}

TEST(TransferTest, ContextSensitiveSetThreeLayersDepthThree) {
  std::string Code = R"(
    bool GiveBool();
    void SetBool1(bool &Var) { Var = true; }
    void SetBool2(bool &Var) { SetBool1(Var); }
    void SetBool3(bool &Var) { SetBool2(Var); }

    void target() {
      bool Foo = GiveBool();
      SetBool3(Foo);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_TRUE(Env.proves(FooVal));
      },
      {BuiltinOptions{ContextSensitiveOptions{/*.Depth=*/3}}});
}

TEST(TransferTest, ContextSensitiveMutualRecursion) {
  std::string Code = R"(
    bool Pong(bool X, bool Y);

    bool Ping(bool X, bool Y) {
      if (X) {
        return Y;
      } else {
        return Pong(!X, Y);
      }
    }

    bool Pong(bool X, bool Y) {
      if (Y) {
        return X;
      } else {
        return Ping(X, !Y);
      }
    }

    void target() {
      bool Foo = Ping(false, false);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        // The analysis doesn't crash...
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        // ... but it also can't prove anything here.
        EXPECT_FALSE(Env.proves(FooVal));
        EXPECT_FALSE(Env.proves(Env.arena().makeNot(FooVal)));
      },
      {BuiltinOptions{ContextSensitiveOptions{/*.Depth=*/4}}});
}

TEST(TransferTest, ContextSensitiveSetMultipleLines) {
  std::string Code = R"(
    void SetBools(bool &Var1, bool &Var2) {
      Var1 = true;
      Var2 = false;
    }

    void target() {
      bool Foo = false;
      bool Bar = true;
      SetBools(Foo, Bar);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_TRUE(Env.proves(FooVal));
        EXPECT_FALSE(Env.proves(Env.arena().makeNot(FooVal)));

        auto &BarVal = getFormula(*BarDecl, Env);
        EXPECT_FALSE(Env.proves(BarVal));
        EXPECT_TRUE(Env.proves(Env.arena().makeNot(BarVal)));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveSetMultipleBlocks) {
  std::string Code = R"(
    void IfCond(bool Cond, bool &Then, bool &Else) {
      if (Cond) {
        Then = true;
      } else {
        Else = true;
      }
    }

    void target() {
      bool Foo = false;
      bool Bar = false;
      bool Baz = false;
      IfCond(Foo, Bar, Baz);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        auto &BarVal = getFormula(*BarDecl, Env);
        EXPECT_FALSE(Env.proves(BarVal));
        EXPECT_TRUE(Env.proves(Env.arena().makeNot(BarVal)));

        auto &BazVal = getFormula(*BazDecl, Env);
        EXPECT_TRUE(Env.proves(BazVal));
        EXPECT_FALSE(Env.proves(Env.arena().makeNot(BazVal)));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveReturnVoid) {
  std::string Code = R"(
    void Noop() { return; }

    void target() {
      Noop();
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        // This just tests that the analysis doesn't crash.
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveReturnTrue) {
  std::string Code = R"(
    bool GiveBool() { return true; }

    void target() {
      bool Foo = GiveBool();
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_TRUE(Env.proves(FooVal));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveReturnFalse) {
  std::string Code = R"(
    bool GiveBool() { return false; }

    void target() {
      bool Foo = GiveBool();
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_TRUE(Env.proves(Env.arena().makeNot(FooVal)));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveReturnArg) {
  std::string Code = R"(
    bool GiveBool();
    bool GiveBack(bool Arg) { return Arg; }

    void target() {
      bool Foo = GiveBool();
      bool Bar = GiveBack(Foo);
      bool Baz = Foo == Bar;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
        ASSERT_THAT(BazDecl, NotNull());

        auto &BazVal = getFormula(*BazDecl, Env);
        EXPECT_TRUE(Env.proves(BazVal));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveReturnInt) {
  std::string Code = R"(
    int identity(int x) { return x; }

    void target() {
      int y = identity(42);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        // This just tests that the analysis doesn't crash.
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveReturnRecord) {
  std::string Code = R"(
    struct S {
      bool B;
    };

    S makeS(bool BVal) { return {BVal}; }

    void target() {
      S FalseS = makeS(false);
      S TrueS = makeS(true);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto &FalseSLoc =
            getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "FalseS");
        auto &TrueSLoc =
            getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "TrueS");

        EXPECT_EQ(getFieldValue(&FalseSLoc, "B", ASTCtx, Env),
                  &Env.getBoolLiteralValue(false));
        EXPECT_EQ(getFieldValue(&TrueSLoc, "B", ASTCtx, Env),
                  &Env.getBoolLiteralValue(true));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveReturnSelfReferentialRecord) {
  std::string Code = R"(
    struct S {
      S() { self = this; }
      S *self;
    };

    S makeS() {
      // RVO guarantees that this will be constructed directly into `MyS`.
      return S();
    }

    void target() {
      S MyS = makeS();
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto &MySLoc = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "MyS");

        auto *SelfVal =
            cast<PointerValue>(getFieldValue(&MySLoc, "self", ASTCtx, Env));
        EXPECT_EQ(&SelfVal->getPointeeLoc(), &MySLoc);
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveMethodLiteral) {
  std::string Code = R"(
    class MyClass {
    public:
      bool giveBool() { return true; }
    };

    void target() {
      MyClass MyObj;
      bool Foo = MyObj.giveBool();
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_TRUE(Env.proves(FooVal));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveMethodGetter) {
  std::string Code = R"(
    class MyClass {
    public:
      bool getField() { return Field; }

      bool Field;
    };

    void target() {
      MyClass MyObj;
      MyObj.Field = true;
      bool Foo = MyObj.getField();
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_TRUE(Env.proves(FooVal));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveMethodSetter) {
  std::string Code = R"(
    class MyClass {
    public:
      void setField(bool Val) { Field = Val; }

      bool Field;
    };

    void target() {
      MyClass MyObj;
      MyObj.setField(true);
      bool Foo = MyObj.Field;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_TRUE(Env.proves(FooVal));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveMethodGetterAndSetter) {
  std::string Code = R"(
    class MyClass {
    public:
      bool getField() { return Field; }
      void setField(bool Val) { Field = Val; }

    private:
      bool Field;
    };

    void target() {
      MyClass MyObj;
      MyObj.setField(true);
      bool Foo = MyObj.getField();
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_TRUE(Env.proves(FooVal));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}


TEST(TransferTest, ContextSensitiveMethodTwoLayersVoid) {
  std::string Code = R"(
    class MyClass {
    public:
      void Inner() { MyField = true; }
      void Outer() { Inner(); }

      bool MyField;
    };

    void target() {
      MyClass MyObj;
      MyObj.Outer();
      bool Foo = MyObj.MyField;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        ;
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_TRUE(Env.proves(FooVal));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveMethodTwoLayersReturn) {
  std::string Code = R"(
    class MyClass {
    public:
      bool Inner() { return MyField; }
      bool Outer() { return Inner(); }

      bool MyField;
    };

    void target() {
      MyClass MyObj;
      MyObj.MyField = true;
      bool Foo = MyObj.Outer();
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        ;
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_TRUE(Env.proves(FooVal));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveConstructorBody) {
  std::string Code = R"(
    class MyClass {
    public:
      MyClass() { MyField = true; }

      bool MyField;
    };

    void target() {
      MyClass MyObj;
      bool Foo = MyObj.MyField;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_TRUE(Env.proves(FooVal));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveConstructorInitializer) {
  std::string Code = R"(
    class MyClass {
    public:
      MyClass() : MyField(true) {}

      bool MyField;
    };

    void target() {
      MyClass MyObj;
      bool Foo = MyObj.MyField;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_TRUE(Env.proves(FooVal));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveConstructorDefault) {
  std::string Code = R"(
    class MyClass {
    public:
      MyClass() = default;

      bool MyField = true;
    };

    void target() {
      MyClass MyObj;
      bool Foo = MyObj.MyField;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        auto &FooVal = getFormula(*FooDecl, Env);
        EXPECT_TRUE(Env.proves(FooVal));
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, ContextSensitiveSelfReferentialClass) {
  // Test that the `this` pointer seen in the constructor has the same value
  // as the address of the variable the object is constructed into.
  std::string Code = R"(
    class MyClass {
    public:
      MyClass() : Self(this) {}
      MyClass *Self;
    };

    void target() {
      MyClass MyObj;
      MyClass *SelfPtr = MyObj.Self;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));

        const ValueDecl *MyObjDecl = findValueDecl(ASTCtx, "MyObj");
        ASSERT_THAT(MyObjDecl, NotNull());

        const ValueDecl *SelfDecl = findValueDecl(ASTCtx, "SelfPtr");
        ASSERT_THAT(SelfDecl, NotNull());

        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
        auto &SelfVal = *cast<PointerValue>(Env.getValue(*SelfDecl));
        EXPECT_EQ(Env.getStorageLocation(*MyObjDecl), &SelfVal.getPointeeLoc());
      },
      {BuiltinOptions{ContextSensitiveOptions{}}});
}

TEST(TransferTest, UnnamedBitfieldInitializer) {
  std::string Code = R"(
    struct B {};
    struct A {
      unsigned a;
      unsigned : 4;
      unsigned c;
      B b;
    };
    void target() {
      A a = {};
      A test = a;
      (void)test.c;
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        // This doesn't need a body because this test was crashing the framework
        // before handling correctly Unnamed bitfields in `InitListExpr`.
      });
}

// Repro for a crash that used to occur with chained short-circuiting logical
// operators.
TEST(TransferTest, ChainedLogicalOps) {
  std::string Code = R"(
    bool target() {
      bool b = true || false || false || false;
      // [[p]]
      return b;
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
        auto &B = getValueForDecl<BoolValue>(ASTCtx, Env, "b").formula();
        EXPECT_TRUE(Env.proves(B));
      });
}

// Repro for a crash that used to occur when we call a `noreturn` function
// within one of the operands of a `&&` or `||` operator.
TEST(TransferTest, NoReturnFunctionInsideShortCircuitedBooleanOp) {
  std::string Code = R"(
    __attribute__((noreturn)) int doesnt_return();
    bool some_condition();
    void target(bool b1, bool b2) {
      // Neither of these should crash. In addition, if we don't terminate the
      // program, we know that the operators need to trigger the short-circuit
      // logic, so `NoreturnOnRhsOfAnd` will be false and `NoreturnOnRhsOfOr`
      // will be true.
      bool NoreturnOnRhsOfAnd = b1 && doesnt_return() > 0;
      bool NoreturnOnRhsOfOr = b2 || doesnt_return() > 0;

      // Calling a `noreturn` function on the LHS of an `&&` or `||` makes the
      // entire expression unreachable. So we know that in both of the following
      // cases, if `target()` terminates, the `else` branch was taken.
      bool NoreturnOnLhsMakesAndUnreachable = false;
      if (some_condition())
         doesnt_return() > 0 && some_condition();
      else
         NoreturnOnLhsMakesAndUnreachable = true;

      bool NoreturnOnLhsMakesOrUnreachable = false;
      if (some_condition())
         doesnt_return() > 0 || some_condition();
      else
         NoreturnOnLhsMakesOrUnreachable = true;

      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
        auto &A = Env.arena();

        // Check that [[p]] is reachable with a non-false flow condition.
        EXPECT_FALSE(Env.proves(A.makeLiteral(false)));

        auto &B1 = getValueForDecl<BoolValue>(ASTCtx, Env, "b1").formula();
        EXPECT_TRUE(Env.proves(A.makeNot(B1)));

        auto &NoreturnOnRhsOfAnd =
            getValueForDecl<BoolValue>(ASTCtx, Env, "NoreturnOnRhsOfAnd").formula();
        EXPECT_TRUE(Env.proves(A.makeNot(NoreturnOnRhsOfAnd)));

        auto &B2 = getValueForDecl<BoolValue>(ASTCtx, Env, "b2").formula();
        EXPECT_TRUE(Env.proves(B2));

        auto &NoreturnOnRhsOfOr =
            getValueForDecl<BoolValue>(ASTCtx, Env, "NoreturnOnRhsOfOr")
                .formula();
        EXPECT_TRUE(Env.proves(NoreturnOnRhsOfOr));

        auto &NoreturnOnLhsMakesAndUnreachable = getValueForDecl<BoolValue>(
            ASTCtx, Env, "NoreturnOnLhsMakesAndUnreachable").formula();
        EXPECT_TRUE(Env.proves(NoreturnOnLhsMakesAndUnreachable));

        auto &NoreturnOnLhsMakesOrUnreachable = getValueForDecl<BoolValue>(
            ASTCtx, Env, "NoreturnOnLhsMakesOrUnreachable").formula();
        EXPECT_TRUE(Env.proves(NoreturnOnLhsMakesOrUnreachable));
      });
}

TEST(TransferTest, NewExpressions) {
  std::string Code = R"(
    void target() {
      int *p = new int(42);
      // [[after_new]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env =
            getEnvironmentAtAnnotation(Results, "after_new");

        auto &P = getValueForDecl<PointerValue>(ASTCtx, Env, "p");

        EXPECT_THAT(Env.getValue(P.getPointeeLoc()), NotNull());
      });
}

TEST(TransferTest, NewExpressions_Structs) {
  std::string Code = R"(
    struct Inner {
      int InnerField;
    };

    struct Outer {
      Inner OuterField;
    };

    void target() {
      Outer *p = new Outer;
      // Access the fields to make sure the analysis actually generates children
      // for them in the `RecordStorageLocation`.
      p->OuterField.InnerField;
      // [[after_new]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env =
            getEnvironmentAtAnnotation(Results, "after_new");

        const ValueDecl *OuterField = findValueDecl(ASTCtx, "OuterField");
        const ValueDecl *InnerField = findValueDecl(ASTCtx, "InnerField");

        auto &P = getValueForDecl<PointerValue>(ASTCtx, Env, "p");

        auto &OuterLoc = cast<RecordStorageLocation>(P.getPointeeLoc());
        auto &OuterFieldLoc =
            *cast<RecordStorageLocation>(OuterLoc.getChild(*OuterField));
        auto &InnerFieldLoc = *OuterFieldLoc.getChild(*InnerField);

        EXPECT_THAT(Env.getValue(InnerFieldLoc), NotNull());
      });
}

TEST(TransferTest, FunctionToPointerDecayHasValue) {
  std::string Code = R"(
    struct A { static void static_member_func(); };
    void target() {
      // To check that we're treating function-to-pointer decay correctly,
      // create two pointers, then verify they refer to the same storage
      // location.
      // We need to do the test this way because even if an initializer (in this
      // case, the function-to-pointer decay) does not create a value, we still
      // create a value for the variable.
      void (*non_member_p1)() = target;
      void (*non_member_p2)() = target;

      // Do the same thing but for a static member function.
      void (*member_p1)() = A::static_member_func;
      void (*member_p2)() = A::static_member_func;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto &NonMemberP1 =
            getValueForDecl<PointerValue>(ASTCtx, Env, "non_member_p1");
        auto &NonMemberP2 =
            getValueForDecl<PointerValue>(ASTCtx, Env, "non_member_p2");
        EXPECT_EQ(&NonMemberP1.getPointeeLoc(), &NonMemberP2.getPointeeLoc());

        auto &MemberP1 =
            getValueForDecl<PointerValue>(ASTCtx, Env, "member_p1");
        auto &MemberP2 =
            getValueForDecl<PointerValue>(ASTCtx, Env, "member_p2");
        EXPECT_EQ(&MemberP1.getPointeeLoc(), &MemberP2.getPointeeLoc());
      });
}

// Check that a builtin function is not associated with a value. (It's only
// possible to call builtin functions directly, not take their address.)
TEST(TransferTest, BuiltinFunctionModeled) {
  std::string Code = R"(
    void target() {
      __builtin_expect(0, 0);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        using ast_matchers::selectFirst;
        using ast_matchers::match;
        using ast_matchers::traverse;
        using ast_matchers::implicitCastExpr;
        using ast_matchers::hasCastKind;

        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto *ImplicitCast = selectFirst<ImplicitCastExpr>(
            "implicit_cast",
            match(traverse(TK_AsIs,
                           implicitCastExpr(hasCastKind(CK_BuiltinFnToFnPtr))
                               .bind("implicit_cast")),
                  ASTCtx));

        ASSERT_THAT(ImplicitCast, NotNull());
        EXPECT_THAT(Env.getValue(*ImplicitCast), IsNull());
      });
}

// Check that a callee of a member operator call is modeled as a `PointerValue`.
// Member operator calls are unusual in that their callee is a pointer that
// stems from a `FunctionToPointerDecay`. In calls to non-operator non-static
// member functions, the callee is a `MemberExpr` (which does not have pointer
// type).
// We want to make sure that we produce a pointer value for the callee in this
// specific scenario and that its storage location is durable (for convergence).
TEST(TransferTest, MemberOperatorCallModelsPointerForCallee) {
  std::string Code = R"(
    struct S {
      bool operator!=(S s);
    };
    void target() {
      S s;
      (void)(s != s);
      (void)(s != s);
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        using ast_matchers::selectFirst;
        using ast_matchers::match;
        using ast_matchers::traverse;
        using ast_matchers::cxxOperatorCallExpr;

        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        auto Matches = match(
            traverse(TK_AsIs, cxxOperatorCallExpr().bind("call")), ASTCtx);

        ASSERT_EQ(Matches.size(), 2UL);

        auto *Call1 = Matches[0].getNodeAs<CXXOperatorCallExpr>("call");
        auto *Call2 = Matches[1].getNodeAs<CXXOperatorCallExpr>("call");

        ASSERT_THAT(Call1, NotNull());
        ASSERT_THAT(Call2, NotNull());

        EXPECT_EQ(cast<ImplicitCastExpr>(Call1->getCallee())->getCastKind(),
                  CK_FunctionToPointerDecay);
        EXPECT_EQ(cast<ImplicitCastExpr>(Call2->getCallee())->getCastKind(),
                  CK_FunctionToPointerDecay);

        auto *Ptr1 = cast<PointerValue>(Env.getValue(*Call1->getCallee()));
        auto *Ptr2 = cast<PointerValue>(Env.getValue(*Call2->getCallee()));

        ASSERT_EQ(&Ptr1->getPointeeLoc(), &Ptr2->getPointeeLoc());
      });
}

// Check that fields of anonymous records are modeled.
TEST(TransferTest, AnonymousStruct) {
  std::string Code = R"(
    struct S {
      struct {
        bool b;
      };
    };
    void target() {
      S s;
      s.b = true;
      // [[p]]
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
        const ValueDecl *SDecl = findValueDecl(ASTCtx, "s");
        const ValueDecl *BDecl = findValueDecl(ASTCtx, "b");
        const IndirectFieldDecl *IndirectField =
            findIndirectFieldDecl(ASTCtx, "b");

        auto *S = cast<RecordStorageLocation>(Env.getStorageLocation(*SDecl));
        auto &AnonStruct = *cast<RecordStorageLocation>(
            S->getChild(*cast<ValueDecl>(IndirectField->chain().front())));

        auto *B = cast<BoolValue>(getFieldValue(&AnonStruct, *BDecl, Env));
        ASSERT_TRUE(Env.proves(B->formula()));
      });
}

TEST(TransferTest, AnonymousStructWithInitializer) {
  std::string Code = R"(
    struct target {
      target() {
        (void)0;
        // [[p]]
      }
      struct {
        bool b = true;
      };
    };
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
        const ValueDecl *BDecl = findValueDecl(ASTCtx, "b");
        const IndirectFieldDecl *IndirectField =
            findIndirectFieldDecl(ASTCtx, "b");

        auto *ThisLoc =
            cast<RecordStorageLocation>(Env.getThisPointeeStorageLocation());
        auto &AnonStruct = *cast<RecordStorageLocation>(ThisLoc->getChild(
            *cast<ValueDecl>(IndirectField->chain().front())));

        auto *B = cast<BoolValue>(getFieldValue(&AnonStruct, *BDecl, Env));
        ASSERT_TRUE(Env.proves(B->formula()));
      });
}

TEST(TransferTest, AnonymousStructWithReferenceField) {
  std::string Code = R"(
    int global_i = 0;
    struct target {
      target() {
        (void)0;
        // [[p]]
      }
      struct {
        int &i = global_i;
      };
    };
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
        const ValueDecl *GlobalIDecl = findValueDecl(ASTCtx, "global_i");
        const ValueDecl *IDecl = findValueDecl(ASTCtx, "i");
        const IndirectFieldDecl *IndirectField =
            findIndirectFieldDecl(ASTCtx, "i");

        auto *ThisLoc =
            cast<RecordStorageLocation>(Env.getThisPointeeStorageLocation());
        auto &AnonStruct = *cast<RecordStorageLocation>(ThisLoc->getChild(
            *cast<ValueDecl>(IndirectField->chain().front())));

        ASSERT_EQ(AnonStruct.getChild(*IDecl),
                  Env.getStorageLocation(*GlobalIDecl));
      });
}

TEST(TransferTest, EvaluateBlockWithUnreachablePreds) {
  // This is a crash repro.
  // `false` block may not have been processed when we try to evaluate the `||`
  // after visiting `true`, because it is not necessary (and therefore the edge
  // is marked unreachable). Trying to get the analysis state via
  // `getEnvironment` for the subexpression still should not crash.
  std::string Code = R"(
    int target(int i) {
      if ((i < 0 && true) || false) {
        return 0;
      }
      return 0;
    }
  )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {});
}

TEST(TransferTest, LambdaCaptureByCopy) {
  std::string Code = R"(
    void target(int Foo, int Bar) {
      [Foo]() {
        (void)0;
        // [[p]]
      }();
    }
  )";
  runDataflowOnLambda(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));

        const Value *FooVal = Env.getValue(*FooLoc);
        EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const StorageLocation *BarLoc = Env.getStorageLocation(*BarDecl);
        EXPECT_THAT(BarLoc, IsNull());
      });
}

TEST(TransferTest, LambdaCaptureByReference) {
  std::string Code = R"(
    void target(int Foo, int Bar) {
      [&Foo]() {
        (void)0;
        // [[p]]
      }();
    }
  )";
  runDataflowOnLambda(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));

        const Value *FooVal = Env.getValue(*FooLoc);
        EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const StorageLocation *BarLoc = Env.getStorageLocation(*BarDecl);
        EXPECT_THAT(BarLoc, IsNull());
      });
}

TEST(TransferTest, LambdaCaptureWithInitializer) {
  std::string Code = R"(
    void target(int Bar) {
      [Foo=Bar]() {
        (void)0;
        // [[p]]
      }();
    }
  )";
  runDataflowOnLambda(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));

        const Value *FooVal = Env.getValue(*FooLoc);
        EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        const StorageLocation *BarLoc = Env.getStorageLocation(*BarDecl);
        EXPECT_THAT(BarLoc, IsNull());
      });
}

TEST(TransferTest, LambdaCaptureByCopyImplicit) {
  std::string Code = R"(
    void target(int Foo, int Bar) {
      [=]() {
        Foo;
        // [[p]]
      }();
    }
  )";
  runDataflowOnLambda(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));

        const Value *FooVal = Env.getValue(*FooLoc);
        EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        // There is no storage location for `Bar` because it isn't used in the
        // body of the lambda.
        const StorageLocation *BarLoc = Env.getStorageLocation(*BarDecl);
        EXPECT_THAT(BarLoc, IsNull());
      });
}

TEST(TransferTest, LambdaCaptureByReferenceImplicit) {
  std::string Code = R"(
    void target(int Foo, int Bar) {
      [&]() {
        Foo;
        // [[p]]
      }();
    }
  )";
  runDataflowOnLambda(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));

        const Value *FooVal = Env.getValue(*FooLoc);
        EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));

        const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
        ASSERT_THAT(BarDecl, NotNull());

        // There is no storage location for `Bar` because it isn't used in the
        // body of the lambda.
        const StorageLocation *BarLoc = Env.getStorageLocation(*BarDecl);
        EXPECT_THAT(BarLoc, IsNull());
      });
}

TEST(TransferTest, LambdaCaptureThis) {
  std::string Code = R"(
    struct Bar {
      int Foo;

      void target() {
        [this]() {
          Foo;
          // [[p]]
        }();
      }
    };
  )";
  runDataflowOnLambda(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

        const RecordStorageLocation *ThisPointeeLoc =
            Env.getThisPointeeStorageLocation();
        ASSERT_THAT(ThisPointeeLoc, NotNull());

        const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
        ASSERT_THAT(FooDecl, NotNull());

        const StorageLocation *FooLoc = ThisPointeeLoc->getChild(*FooDecl);
        ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));

        const Value *FooVal = Env.getValue(*FooLoc);
        EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));
      });
}

// This test verifies correct modeling of a relational dependency that goes
// through unmodeled functions (the simple `cond()` in this case).
TEST(TransferTest, ConditionalRelation) {
  std::string Code = R"(
    bool cond();
    void target() {
       bool a = true;
       bool b = true;
       if (cond()) {
         a = false;
         if (cond()) {
           b = false;
         }
       }
       (void)0;
       // [[p]]
    }
 )";
  runDataflow(
      Code,
      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
         ASTContext &ASTCtx) {
        const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
        auto &A = Env.arena();
        auto &VarA = getValueForDecl<BoolValue>(ASTCtx, Env, "a").formula();
        auto &VarB = getValueForDecl<BoolValue>(ASTCtx, Env, "b").formula();

        EXPECT_FALSE(Env.allows(A.makeAnd(VarA, A.makeNot(VarB))));
      });
}

// This is a crash repro.
// We used to crash while transferring `S().i` because Clang contained a bug
// causing the AST to be malformed.
TEST(TransferTest, AnonymousUnionMemberExprInTemplate) {
  using ast_matchers::functionDecl;
  using ast_matchers::hasName;
  using ast_matchers::unless;

  std::string Code = R"cc(
    struct S {
      struct {
        int i;
      };
    };

    template <class>
    void target() {
        S().i;
    }

    template void target<int>();
  )cc";
  auto Matcher = functionDecl(hasName("target"), unless(isTemplated()));
  ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(Code, Matcher),
                    llvm::Succeeded());
}

} // namespace
