Simplify tests, drop analyze_target_for_test

analyze_target_for_test forces the AST-consuming code into a callback, and doesn't simplify much.

infer_nullability_constraints_test: use clang::TestAST and findValueDecl directly
Also fix use-after-free: pointers returned by inferAnnotationsForTargetFunction dangled.
while here, Target => target.

safety_constraint_generator_test: use dataflow::test::checkDataflow.
Avoid difficult-to-debug matchers-containing-callbacks by expressing the constraints as strings, assigning names to the variables we care about.
While here, Target => target.

safety_constraint_generator: pass Lattice/Env separately instead of in a pair type, to avoid ugly conversions (and copies!) between different pair types in tests
PiperOrigin-RevId: 544127985
diff --git a/nullability/inference/BUILD b/nullability/inference/BUILD
index 5071479..74f274c 100644
--- a/nullability/inference/BUILD
+++ b/nullability/inference/BUILD
@@ -28,14 +28,15 @@
     name = "infer_nullability_constraints_test",
     srcs = ["infer_nullability_constraints_test.cc"],
     deps = [
-        ":analyze_target_for_test",
         ":infer_nullability_constraints",
         ":inference_cc_proto",
         "//nullability:proto_matchers",
         "@absl//absl/log:check",
         "@llvm-project//clang:analysis",
         "@llvm-project//clang:ast",
-        "@llvm-project//clang:ast_matchers",
+        "@llvm-project//clang:basic",
+        "@llvm-project//clang:testing",
+        "@llvm-project//clang/unittests:dataflow_testing_support",
         "@llvm-project//llvm:Support",
         "@llvm-project//third-party/unittest:gmock",
         "@llvm-project//third-party/unittest:gtest",
@@ -70,20 +71,6 @@
     ],
 )
 
-cc_library(
-    name = "analyze_target_for_test",
-    testonly = True,
-    srcs = ["analyze_target_for_test.cc"],
-    hdrs = ["analyze_target_for_test.h"],
-    deps = [
-        "@absl//absl/log:check",
-        "@llvm-project//clang:ast",
-        "@llvm-project//clang:ast_matchers",
-        "@llvm-project//clang:testing",
-        "@llvm-project//llvm:Support",
-    ],
-)
-
 cc_test(
     name = "resolve_constraints_test",
     srcs = ["resolve_constraints_test.cc"],
@@ -105,7 +92,6 @@
     name = "safety_constraint_generator_test",
     srcs = ["safety_constraint_generator_test.cc"],
     deps = [
-        ":analyze_target_for_test",
         ":safety_constraint_generator",
         "//nullability:pointer_nullability",
         "//nullability:pointer_nullability_analysis",
@@ -115,6 +101,8 @@
         "@llvm-project//clang:ast",
         "@llvm-project//clang:ast_matchers",
         "@llvm-project//clang:basic",
+        "@llvm-project//clang:testing",
+        "@llvm-project//clang/unittests:dataflow_testing_support",
         "@llvm-project//llvm:Support",
         "@llvm-project//third-party/unittest:gmock",
         "@llvm-project//third-party/unittest:gtest",
diff --git a/nullability/inference/analyze_target_for_test.cc b/nullability/inference/analyze_target_for_test.cc
deleted file mode 100644
index ecfff1b..0000000
--- a/nullability/inference/analyze_target_for_test.cc
+++ /dev/null
@@ -1,73 +0,0 @@
-// Part of the Crubit project, under the Apache License v2.0 with LLVM
-// Exceptions. See /LICENSE for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-
-#include "nullability/inference/analyze_target_for_test.h"
-
-#include <functional>
-#include <utility>
-
-#include "absl/log/check.h"
-#include "clang/AST/Decl.h"
-#include "clang/ASTMatchers/ASTMatchFinder.h"
-#include "clang/ASTMatchers/ASTMatchers.h"
-#include "clang/Testing/TestAST.h"
-#include "llvm/ADT/STLFunctionalExtras.h"
-#include "llvm/ADT/StringRef.h"
-
-namespace clang::tidy::nullability {
-namespace {
-using ::clang::ast_matchers::compoundStmt;
-using ::clang::ast_matchers::functionDecl;
-using ::clang::ast_matchers::hasBody;
-using ::clang::ast_matchers::hasName;
-using ::clang::ast_matchers::MatchFinder;
-
-// Simplifies creation of MatchCallbacks.
-class CallbackAdapter : public clang::ast_matchers::MatchFinder::MatchCallback {
- public:
-  explicit CallbackAdapter(
-      std::function<void(const clang::ast_matchers::MatchFinder::MatchResult &)>
-          Callback)
-      : StoredCallback(std::move(Callback)) {
-    CHECK(StoredCallback);
-  }
-
-  void run(
-      const clang::ast_matchers::MatchFinder::MatchResult &result) override {
-    StoredCallback(result);
-  };
-
- private:
-  std::function<void(const clang::ast_matchers::MatchFinder::MatchResult &)>
-      StoredCallback;
-};
-}  // namespace
-
-void analyzeTargetForTest(
-    llvm::StringRef Source,
-    llvm::function_ref<void(const clang::FunctionDecl &,
-                            const MatchFinder::MatchResult &)>
-        AnalysisCallback) {
-  int MatchesForFunctionDeclarationsNamedTarget = 0;
-  CallbackAdapter adapter([&, AnalysisCallback](
-                              const MatchFinder::MatchResult &result) mutable {
-    ++MatchesForFunctionDeclarationsNamedTarget;
-    const auto *MaybeFunc = result.Nodes.getNodeAs<clang::FunctionDecl>("func");
-    CHECK(MaybeFunc);
-    AnalysisCallback(*MaybeFunc, result);
-  });
-  MatchFinder Finder;
-  Finder.addMatcher(
-      functionDecl(hasName("Target"), hasBody(compoundStmt())).bind("func"),
-      &adapter);
-  clang::TestInputs Inputs(Source);
-  Inputs.FileName = "main.cc";
-  clang::TestAST Ast(Inputs);
-  Finder.matchAST(Ast.context());
-  QCHECK_EQ(MatchesForFunctionDeclarationsNamedTarget, 1)
-      << "Source must match exactly 1 function named 'Target' that has a "
-         "definition";
-}
-
-}  // namespace clang::tidy::nullability
diff --git a/nullability/inference/analyze_target_for_test.h b/nullability/inference/analyze_target_for_test.h
deleted file mode 100644
index 8c2de84..0000000
--- a/nullability/inference/analyze_target_for_test.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Part of the Crubit project, under the Apache License v2.0 with LLVM
-// Exceptions. See /LICENSE for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-
-#ifndef CRUBIT_NULLABILITY_INFERENCE_ANALYZE_TARGET_FOR_TEST_H_
-#define CRUBIT_NULLABILITY_INFERENCE_ANALYZE_TARGET_FOR_TEST_H_
-
-#include "clang/AST/Decl.h"
-#include "clang/ASTMatchers/ASTMatchFinder.h"
-#include "llvm/ADT/STLFunctionalExtras.h"
-#include "llvm/ADT/StringRef.h"
-
-namespace clang::tidy::nullability {
-
-void analyzeTargetForTest(
-    llvm::StringRef Source,
-    llvm::function_ref<
-        void(const clang::FunctionDecl &,
-             const clang::ast_matchers::MatchFinder::MatchResult &)>
-        AnalysisCallback);
-
-}  // namespace clang::tidy::nullability
-
-#endif  // CRUBIT_NULLABILITY_INFERENCE_ANALYZE_TARGET_FOR_TEST_H_
diff --git a/nullability/inference/infer_nullability_constraints.cc b/nullability/inference/infer_nullability_constraints.cc
index 9d8c0c1..afc368c 100644
--- a/nullability/inference/infer_nullability_constraints.cc
+++ b/nullability/inference/infer_nullability_constraints.cc
@@ -74,7 +74,7 @@
   return false;
 }
 
-llvm::Expected<llvm::DenseMap<const clang::Decl *, NullabilityConstraint>>
+llvm::Expected<llvm::DenseMap<const clang::NamedDecl *, NullabilityConstraint>>
 inferNullabilityConstraints(const clang::FunctionDecl &Func,
                             clang::ASTContext &Context) {
   // We want to make sure we use the declaration that the body comes from,
@@ -87,7 +87,7 @@
   }
   CHECK(DeclWithBody);
 
-  llvm::DenseMap<const clang::Decl *, NullabilityConstraint> Results;
+  llvm::DenseMap<const clang::NamedDecl *, NullabilityConstraint> Results;
   llvm::Expected<clang::dataflow::ControlFlowContext> ControlFlowContext =
       clang::dataflow::ControlFlowContext::build(*DeclWithBody);
   if (!ControlFlowContext) return Results;
@@ -106,8 +106,8 @@
               const clang::CFGElement &Element,
               const clang::dataflow::DataflowAnalysisState<
                   PointerNullabilityLattice> &State) {
-            SafetyConstraintGenerator.collectConstraints(Element, State,
-                                                         Context);
+            SafetyConstraintGenerator.collectConstraints(Element, State.Lattice,
+                                                         State.Env, Context);
           });
   if (!BlockToOutputStateOrError) {
     return Results;
diff --git a/nullability/inference/infer_nullability_constraints.h b/nullability/inference/infer_nullability_constraints.h
index 64b9d0e..7556215 100644
--- a/nullability/inference/infer_nullability_constraints.h
+++ b/nullability/inference/infer_nullability_constraints.h
@@ -17,7 +17,7 @@
 // Collects constraints on nullability annotations that could be added to the
 // types of Func's parameters based on the function's behavior and our
 // definition of null-safety.
-llvm::Expected<llvm::DenseMap<const clang::Decl *, NullabilityConstraint>>
+llvm::Expected<llvm::DenseMap<const clang::NamedDecl *, NullabilityConstraint>>
 inferNullabilityConstraints(const clang::FunctionDecl &Func,
                             clang::ASTContext &Context);
 
diff --git a/nullability/inference/infer_nullability_constraints_test.cc b/nullability/inference/infer_nullability_constraints_test.cc
index ce0543b..70b4090 100644
--- a/nullability/inference/infer_nullability_constraints_test.cc
+++ b/nullability/inference/infer_nullability_constraints_test.cc
@@ -6,149 +6,131 @@
 
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "absl/log/check.h"
-#include "nullability/inference/analyze_target_for_test.h"
 #include "nullability/inference/inference.proto.h"
 #include "nullability/proto_matchers.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/Expr.h"
-#include "clang/ASTMatchers/ASTMatchFinder.h"
 #include "clang/Analysis/CFG.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Testing/TestAST.h"
+#include "third_party/llvm/llvm-project/clang/unittests/Analysis/FlowSensitive/TestingSupport.h"
 #include "llvm/ADT/DenseMap.h"
-#include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
 #include "third_party/llvm/llvm-project/third-party/unittest/googlemock/include/gmock/gmock.h"
 #include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
 
 namespace clang::tidy::nullability {
 namespace {
 
-using ::clang::ast_matchers::MatchFinder;
 using ::testing::IsEmpty;
 using ::testing::Pair;
 using ::testing::UnorderedElementsAre;
 
 using InferredNullabilityConstraints =
-    llvm::DenseMap<const clang::Decl *, NullabilityConstraint>;
+    std::vector<std::pair<std::string, NullabilityConstraint>>;
 
 InferredNullabilityConstraints inferAnnotationsForTargetFunction(
     llvm::StringRef Source) {
-  InferredNullabilityConstraints Constraints;
-  analyzeTargetForTest(
-      Source, [&Constraints](const clang::FunctionDecl &Func,
-                             const MatchFinder::MatchResult &Result) mutable {
-        auto Results = inferNullabilityConstraints(Func, *Result.Context);
-        CHECK(Results);
-        Constraints = std::move(*Results);
-      });
-  return Constraints;
-}
-
-MATCHER_P(Parameter, Name, std::string("is a parameter named ") + Name) {
-  const auto *Param = clang::dyn_cast_or_null<clang::ParmVarDecl>(arg);
-  if (!Param) {
-    return false;
-  }
-
-  const auto *Identifier = Param->getIdentifier();
-  if (!Identifier) {
-    return false;
-  }
-
-  return testing::ExplainMatchResult(Name, Identifier->getName(),
-                                     result_listener);
+  clang::TestAST AST(Source);
+  auto DeclResults = inferNullabilityConstraints(
+      cast<FunctionDecl>(
+          *dataflow::test::findValueDecl(AST.context(), "target")),
+      AST.context());
+  CHECK(DeclResults) << toString(DeclResults.takeError());
+  InferredNullabilityConstraints StringResults;
+  for (const auto &Entry : *DeclResults)
+    StringResults.emplace_back(Entry.first->getName(), Entry.second);
+  return StringResults;
 }
 
 TEST(InferAnnotationsTest, NoParams) {
   static constexpr llvm::StringRef Src = R"cc(
-    void Target() {}
+    void target() {}
   )cc";
   EXPECT_THAT(inferAnnotationsForTargetFunction(Src), IsEmpty());
 }
 
 TEST(InferAnnotationsTest, OneParamUnused) {
   static constexpr llvm::StringRef Src = R"cc(
-    void Target(int *p0) {}
+    void target(int *p0) {}
   )cc";
   EXPECT_THAT(inferAnnotationsForTargetFunction(Src), IsEmpty());
 }
 
 TEST(InferAnnotationsTest, OneParamUsedWithoutRestriction) {
   static constexpr llvm::StringRef Src = R"cc(
-    void TakesUnknown(int *unknown) {}
+    void takesUnknown(int *unknown) {}
 
-    void Target(int *p0) { TakesUnknown(p0); }
+    void target(int *p0) { takesUnknown(p0); }
   )cc";
-  EXPECT_THAT(
-      inferAnnotationsForTargetFunction(Src),
-      UnorderedElementsAre(
-          Pair(Parameter("p0"), EqualsProto(R"pb(must_be_nonnull: false)pb"))));
+  EXPECT_THAT(inferAnnotationsForTargetFunction(Src),
+              UnorderedElementsAre(
+                  Pair("p0", EqualsProto(R"pb(must_be_nonnull: false)pb"))));
 }
 
 TEST(InferAnnotationsTest, Deref) {
   static constexpr llvm::StringRef Src = R"cc(
-    void Target(int *p0, int *p1) {
+    void target(int *p0, int *p1) {
       int a = *p0;
       if (p1 != nullptr) {
         int b = *p1;
       }
     }
   )cc";
-  EXPECT_THAT(
-      inferAnnotationsForTargetFunction(Src),
-      UnorderedElementsAre(
-          Pair(Parameter("p0"), EqualsProto(R"pb(must_be_nonnull: true)pb")),
-          Pair(Parameter("p1"), EqualsProto(R"pb(must_be_nonnull: false)pb"))));
+  EXPECT_THAT(inferAnnotationsForTargetFunction(Src),
+              UnorderedElementsAre(
+                  Pair("p0", EqualsProto(R"pb(must_be_nonnull: true)pb")),
+                  Pair("p1", EqualsProto(R"pb(must_be_nonnull: false)pb"))));
 }
 
 TEST(InferAnnotationsTest, DereferenceBeforeAssignment) {
   static constexpr llvm::StringRef Src = R"cc(
-    void Target(int *p) {
+    void target(int *p) {
       *p;
       int i = 1;
       p = &i;
     }
   )cc";
-  EXPECT_THAT(
-      inferAnnotationsForTargetFunction(Src),
-      UnorderedElementsAre(
-          Pair(Parameter("p"), EqualsProto(R"pb(must_be_nonnull: true)pb"))));
+  EXPECT_THAT(inferAnnotationsForTargetFunction(Src),
+              UnorderedElementsAre(Pair("p", EqualsProto(R"pb(must_be_nonnull:
+                                                                  true)pb"))));
 }
 
 TEST(InferAnnotationsTest, DereferenceAfterAssignment) {
   static constexpr llvm::StringRef Src = R"cc(
-    void Target(int *p) {
+    void target(int *p) {
       int i = 1;
       p = &i;
       *p;
     }
   )cc";
-  EXPECT_THAT(
-      inferAnnotationsForTargetFunction(Src),
-      UnorderedElementsAre(
-          Pair(Parameter("p"), EqualsProto(R"pb(must_be_nonnull: false)pb"))));
+  EXPECT_THAT(inferAnnotationsForTargetFunction(Src),
+              UnorderedElementsAre(
+                  Pair("p", EqualsProto(R"pb(must_be_nonnull: false)pb"))));
 }
 
 TEST(InferAnnotationsTest, DerefOfPtrRef) {
   static constexpr llvm::StringRef Src = R"cc(
-    void Target(int *&p0, int *&p1) {
+    void target(int *&p0, int *&p1) {
       int a = *p0;
       if (p1 != nullptr) {
         int b = *p1;
       }
     }
   )cc";
-  EXPECT_THAT(
-      inferAnnotationsForTargetFunction(Src),
-      UnorderedElementsAre(
-          Pair(Parameter("p0"), EqualsProto(R"pb(must_be_nonnull: true)pb")),
-          Pair(Parameter("p1"), EqualsProto(R"pb(must_be_nonnull: false)pb"))));
+  EXPECT_THAT(inferAnnotationsForTargetFunction(Src),
+              UnorderedElementsAre(
+                  Pair("p0", EqualsProto(R"pb(must_be_nonnull: true)pb")),
+                  Pair("p1", EqualsProto(R"pb(must_be_nonnull: false)pb"))));
 }
 
 TEST(InferAnnotationsTest, UnrelatedCondition) {
   static constexpr llvm::StringRef Src = R"cc(
-    void Target(int *p0, int *p1, int *p2, bool b) {
+    void target(int *p0, int *p1, int *p2, bool b) {
       if (b) {
         int a = *p0;
         int b = *p1;
@@ -158,17 +140,16 @@
       }
     }
   )cc";
-  EXPECT_THAT(
-      inferAnnotationsForTargetFunction(Src),
-      UnorderedElementsAre(
-          Pair(Parameter("p0"), EqualsProto(R"pb(must_be_nonnull: true)pb")),
-          Pair(Parameter("p1"), EqualsProto(R"pb(must_be_nonnull: true)pb")),
-          Pair(Parameter("p2"), EqualsProto(R"pb(must_be_nonnull: true)pb"))));
+  EXPECT_THAT(inferAnnotationsForTargetFunction(Src),
+              UnorderedElementsAre(
+                  Pair("p0", EqualsProto(R"pb(must_be_nonnull: true)pb")),
+                  Pair("p1", EqualsProto(R"pb(must_be_nonnull: true)pb")),
+                  Pair("p2", EqualsProto(R"pb(must_be_nonnull: true)pb"))));
 }
 
 TEST(InferAnnotationsTest, LaterDeref) {
   static constexpr llvm::StringRef Src = R"cc(
-    void Target(int *p0) {
+    void target(int *p0) {
       if (p0 == nullptr) {
         (void)0;
       } else {
@@ -177,45 +158,42 @@
       int a = *p0;
     }
   )cc";
-  EXPECT_THAT(
-      inferAnnotationsForTargetFunction(Src),
-      UnorderedElementsAre(
-          Pair(Parameter("p0"), EqualsProto(R"pb(must_be_nonnull: true)pb"))));
+  EXPECT_THAT(inferAnnotationsForTargetFunction(Src),
+              UnorderedElementsAre(
+                  Pair("p0", EqualsProto(R"pb(must_be_nonnull: true)pb"))));
 }
 
 TEST(InferAnnotationsTest, DerefBeforeGuardedDeref) {
   static constexpr llvm::StringRef Src = R"cc(
-    void Target(int *p0) {
+    void target(int *p0) {
       int a = *p0;
       if (p0 != nullptr) {
         int b = *p0;
       }
     }
   )cc";
-  EXPECT_THAT(
-      inferAnnotationsForTargetFunction(Src),
-      UnorderedElementsAre(
-          Pair(Parameter("p0"), EqualsProto(R"pb(must_be_nonnull: true)pb"))));
+  EXPECT_THAT(inferAnnotationsForTargetFunction(Src),
+              UnorderedElementsAre(
+                  Pair("p0", EqualsProto(R"pb(must_be_nonnull: true)pb"))));
 }
 
 TEST(InferAnnotationsTest, EarlyReturn) {
   static constexpr llvm::StringRef Src = R"cc(
-    void Target(int *p0) {
+    void target(int *p0) {
       if (!p0) {
         return;
       }
       int a = *p0;
     }
   )cc";
-  EXPECT_THAT(
-      inferAnnotationsForTargetFunction(Src),
-      UnorderedElementsAre(
-          Pair(Parameter("p0"), EqualsProto(R"pb(must_be_nonnull: false)pb"))));
+  EXPECT_THAT(inferAnnotationsForTargetFunction(Src),
+              UnorderedElementsAre(
+                  Pair("p0", EqualsProto(R"pb(must_be_nonnull: false)pb"))));
 }
 
 TEST(InferAnnotationsTest, UnreachableCode) {
   static constexpr llvm::StringRef Src = R"cc(
-    void Target(int *p0, int *p1, int *p2, int *p3) {
+    void target(int *p0, int *p1, int *p2, int *p3) {
       if (true) {
         int a = *p0;
       } else {
@@ -230,30 +208,28 @@
       int a = *p3;
     }
   )cc";
-  EXPECT_THAT(
-      inferAnnotationsForTargetFunction(Src),
-      UnorderedElementsAre(
-          Pair(Parameter("p0"), EqualsProto(R"pb(must_be_nonnull: true)pb"))));
+  EXPECT_THAT(inferAnnotationsForTargetFunction(Src),
+              UnorderedElementsAre(
+                  Pair("p0", EqualsProto(R"pb(must_be_nonnull: true)pb"))));
 }
 
 TEST(InferAnnotationsTest,
      RequiresNonNullWhenAnnotatedWithClangNullabilityAttributeAtDefinition) {
   static constexpr llvm::StringRef Src = R"cc(
-    void Target(int *unannotated, int *_Nonnull non_null,
+    void target(int *unannotated, int *_Nonnull non_null,
                 int *_Nonnull *only_inner_layer_nonnull) {
       unannotated;
       non_null;
       only_inner_layer_nonnull;
     }
   )cc";
-  EXPECT_THAT(inferAnnotationsForTargetFunction(Src),
-              UnorderedElementsAre(
-                  Pair(Parameter("unannotated"),
-                       EqualsProto(R"pb(must_be_nonnull: false)pb")),
-                  Pair(Parameter("non_null"), EqualsProto(R"pb(must_be_nonnull:
-                                                                   true)pb")),
-                  Pair(Parameter("only_inner_layer_nonnull"),
-                       EqualsProto(R"pb(must_be_nonnull: false)pb"))));
+  EXPECT_THAT(
+      inferAnnotationsForTargetFunction(Src),
+      UnorderedElementsAre(
+          Pair("unannotated", EqualsProto(R"pb(must_be_nonnull: false)pb")),
+          Pair("non_null", EqualsProto(R"pb(must_be_nonnull: true)pb")),
+          Pair("only_inner_layer_nonnull", EqualsProto(R"pb(must_be_nonnull:
+                                                                false)pb"))));
 }
 
 TEST(InferAnnotationsTest,
@@ -264,21 +240,20 @@
     using NonNull [[clang::annotate("Nonnull")]] = T;
     }  // namespace custom
 
-    void Target(int *unannotated, custom::NonNull<int *> non_null,
+    void target(int *unannotated, custom::NonNull<int *> non_null,
                 custom::NonNull<int *> *only_inner_layer_nonnull) {
       unannotated;
       non_null;
       only_inner_layer_nonnull;
     }
   )cc";
-  EXPECT_THAT(inferAnnotationsForTargetFunction(Src),
-              UnorderedElementsAre(
-                  Pair(Parameter("unannotated"),
-                       EqualsProto(R"pb(must_be_nonnull: false)pb")),
-                  Pair(Parameter("non_null"), EqualsProto(R"pb(must_be_nonnull:
-                                                                   true)pb")),
-                  Pair(Parameter("only_inner_layer_nonnull"),
-                       EqualsProto(R"pb(must_be_nonnull: false)pb"))));
+  EXPECT_THAT(
+      inferAnnotationsForTargetFunction(Src),
+      UnorderedElementsAre(
+          Pair("unannotated", EqualsProto(R"pb(must_be_nonnull: false)pb")),
+          Pair("non_null", EqualsProto(R"pb(must_be_nonnull: true)pb")),
+          Pair("only_inner_layer_nonnull", EqualsProto(R"pb(must_be_nonnull:
+                                                                false)pb"))));
 }
 
 }  // namespace
diff --git a/nullability/inference/safety_constraint_generator.cc b/nullability/inference/safety_constraint_generator.cc
index 231f5a0..e87d6d2 100644
--- a/nullability/inference/safety_constraint_generator.cc
+++ b/nullability/inference/safety_constraint_generator.cc
@@ -11,7 +11,7 @@
 #include "clang/ASTMatchers/ASTMatchFinder.h"
 #include "clang/Analysis/CFG.h"
 #include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h"
-#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
 #include "clang/Analysis/FlowSensitive/MatchSwitch.h"
 #include "clang/Analysis/FlowSensitive/Value.h"
 #include "llvm/ADT/DenseSet.h"
@@ -65,11 +65,10 @@
     : ConstraintCollector(buildConstraintCollector()) {}
 
 void SafetyConstraintGenerator::collectConstraints(
-    const clang::CFGElement &Element,
-    const clang::dataflow::DataflowAnalysisState<LatticeType> &State,
-    clang::ASTContext &Context) {
+    const clang::CFGElement &Element, const LatticeType &Lattice,
+    const clang::dataflow::Environment &Env, clang::ASTContext &Context) {
   if (auto *Constraint =
-          ConstraintCollector(Element, Context, {State.Lattice, State.Env})) {
+          ConstraintCollector(Element, Context, {Lattice, Env})) {
     Constraints.insert(Constraint);
   }
 }
diff --git a/nullability/inference/safety_constraint_generator.h b/nullability/inference/safety_constraint_generator.h
index a866c38..2789b2f 100644
--- a/nullability/inference/safety_constraint_generator.h
+++ b/nullability/inference/safety_constraint_generator.h
@@ -38,10 +38,10 @@
   // Intended for use as a PostVisitCFG after running
   // PointerNullabilityAnalysis. Assumes that `State` includes pointer
   // nullability state as set by PointerNullabilityAnalysis.
-  void collectConstraints(
-      const clang::CFGElement &Element,
-      const clang::dataflow::DataflowAnalysisState<LatticeType> &State,
-      clang::ASTContext &Context);
+  void collectConstraints(const clang::CFGElement &Element,
+                          const LatticeType &Lattice,
+                          const dataflow::Environment &Env,
+                          clang::ASTContext &Context);
 
   // Retrieves constraints gathered thus far. Until all analyzed CFGElements
   // have been processed by `collectConstraints`, the return value will not
diff --git a/nullability/inference/safety_constraint_generator_test.cc b/nullability/inference/safety_constraint_generator_test.cc
index 4011296..11ec6af 100644
--- a/nullability/inference/safety_constraint_generator_test.cc
+++ b/nullability/inference/safety_constraint_generator_test.cc
@@ -6,23 +6,24 @@
 
 #include <memory>
 #include <optional>
+#include <string>
+#include <utility>
 #include <vector>
 
 #include "absl/log/check.h"
-#include "nullability/inference/analyze_target_for_test.h"
 #include "nullability/pointer_nullability.h"
 #include "nullability/pointer_nullability_analysis.h"
-#include "nullability/pointer_nullability_lattice.h"
 #include "clang/AST/Decl.h"
-#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
 #include "clang/Analysis/CFG.h"
-#include "clang/Analysis/FlowSensitive/ControlFlowContext.h"
-#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
 #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
 #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+#include "clang/Analysis/FlowSensitive/DebugSupport.h"
 #include "clang/Analysis/FlowSensitive/Value.h"
-#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
 #include "clang/Basic/LLVM.h"
+#include "third_party/llvm/llvm-project/clang/unittests/Analysis/FlowSensitive/TestingSupport.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Error.h"
 #include "third_party/llvm/llvm-project/third-party/unittest/googlemock/include/gmock/gmock.h"
@@ -31,149 +32,115 @@
 namespace clang::tidy::nullability {
 namespace {
 
-using ::testing::AllOf;
+using ::std::replace;
+using ::testing::ElementsAre;
 using ::testing::IsEmpty;
 using ::testing::Not;
-using ::testing::SizeIs;
 using ::testing::UnorderedElementsAre;
 
-// Analyzes the to-be-matched code snippet with PointerNullabilityAnalysis, and
-// then matches against the constraints produced by SafetyConstraintGenerator
-// for the snippet.
-//
-// `ConstraintsMatcherProducer` should be a function taking the Environment
-// and a vector of PointerValue* representing the Target function's parameters
-// and returning a matcher for the generated constraints. This allows for
-// matching of the constraints against individual parameter null state
-// properties.
-MATCHER_P(ProducesSafetyConstraints, ConstraintsMatcherProducer, "") {
-  bool Success = false;
-  auto MatcherProducer = ConstraintsMatcherProducer;
-  analyzeTargetForTest(
-      arg, [&Success, &MatcherProducer, &result_listener](
-               const clang::FunctionDecl& Func,
-               const clang::ast_matchers::MatchFinder::MatchResult& Result) {
-        QCHECK(Func.hasBody()) << "Matched function has no body.";
-        QCHECK(Result.Context) << "Missing ASTContext from match result.";
-        llvm::Expected<clang::dataflow::ControlFlowContext> ControlFlowContext =
-            clang::dataflow::ControlFlowContext::build(Func);
-        clang::dataflow::DataflowAnalysisContext AnalysisContext(
-            std::make_unique<clang::dataflow::WatchedLiteralsSolver>());
-        clang::dataflow::Environment Environment(AnalysisContext, Func);
-        PointerNullabilityAnalysis Analysis(*Result.Context);
-        SafetyConstraintGenerator Generator;
-        llvm::Expected<std::vector<std::optional<
-            clang::dataflow::DataflowAnalysisState<PointerNullabilityLattice>>>>
-            BlockToOutputStateOrError = clang::dataflow::runDataflowAnalysis(
-                *ControlFlowContext, Analysis, Environment,
-                [&Generator, &Result](
-                    const clang::CFGElement& Element,
-                    const clang::dataflow::DataflowAnalysisState<
-                        PointerNullabilityLattice>& State) {
-                  Generator.collectConstraints(Element, State, *Result.Context);
-                });
+// Returns names for nullability atoms of a function's params.
+// Given a function `void foo(int *x)`, returns: {
+//   Env.getPointerNullState(x).first => "x.is_known",
+//   Env.getPointerNullState(x).second => "x.is_null",
+// }
+llvm::DenseMap<const dataflow::AtomicBoolValue *, std::string>
+getNullabilityVariableNames(const FunctionDecl &Func,
+                            const dataflow::Environment &Env) {
+  llvm::DenseMap<const dataflow::AtomicBoolValue *, std::string> Result;
+  for (unsigned I = 0; I < Func.param_size(); ++I) {
+    auto &Param = *Func.getParamDecl(I);
+    if (auto *Val = dyn_cast_or_null<clang::dataflow::PointerValue>(
+            Env.getValue(Param));
+        Val && hasPointerNullState(*Val)) {
+      std::string Name = Param.getName().str();
 
-        QCHECK(BlockToOutputStateOrError)
-            << "No output state from dataflow analysis.";
+      auto [Known, Null] = getPointerNullState(*Val);
+      Result[&Known] = Name + ".is_known";
+      Result[&Null] = Name + ".is_null";
+    }
+  }
+  return Result;
+}
 
-        // TODO(b/268440048) When we can retrieve the improved atoms
-        // representing annotations, stop using the Environment to retrieve the
-        // initial Environment PointerValues, which won't work for local
-        // variables down the road.
-        std::vector<clang::dataflow::PointerValue*> ParamDeclPointerValues;
-        for (const auto* P : Func.parameters()) {
-          CHECK(P != nullptr);
-          auto* Val = clang::dyn_cast_or_null<clang::dataflow::PointerValue>(
-              Environment.getValue(*P));
-          if (Val) {
-            ParamDeclPointerValues.push_back(Val);
-          }
+// Analyzes the "target" function in the code, and returns safety constraints.
+// These are expressed as strings (AtomicBoolValue* won't live long enough).
+std::vector<std::string> getSafetyConstraints(llvm::StringRef Code) {
+  using namespace ast_matchers;
+  std::vector<std::string> Result;
+  SafetyConstraintGenerator Generator;
+  auto Inputs =
+      dataflow::test::AnalysisInputs<PointerNullabilityAnalysis>(
+          Code,
+          functionDecl(hasName("target"), hasBody(compoundStmt()))
+              .bind("target"),
+          [&](ASTContext &Ctx, const dataflow::Environment &E) {
+            return PointerNullabilityAnalysis(Ctx);
+          })
+          .withPostVisitCFG([&](ASTContext &AST, const CFGElement &Elt,
+                                auto &&State) {
+            Generator.collectConstraints(Elt, State.Lattice, State.Env, AST);
+          });
+  auto Err = dataflow::test::checkDataflow(
+      std::move(Inputs), [&](const dataflow::test::AnalysisOutputs &Out) {
+        auto Names = getNullabilityVariableNames(*Out.Target, Out.InitEnv);
+        for (const auto *Constraint : Generator.constraints()) {
+          Result.push_back(dataflow::debugString(*Constraint, Names));
+          // Debug representation is ugly, drop newlines and excess spaces.
+          replace(Result.back().begin(), Result.back().end(), '\n', ' ');
+          llvm::erase_if(Result.back(),
+                         [](char &C) { return C == ' ' && *(&C + 1) == ' '; });
         }
-        Success = ExplainMatchResult(
-            MatcherProducer(Environment, ParamDeclPointerValues),
-            Generator.constraints(), result_listener);
       });
-  return Success;
+  CHECK(!Err) << toString(std::move(Err));
+  return Result;
 }
 
 TEST(SafetyConstraintGenerator, GeneratesNoConstraintsForEmptyFunctionDefn) {
-  static constexpr llvm::StringRef Src = R"cc(
-    void Target() {}
-  )cc";
-  EXPECT_THAT(Src, ProducesSafetyConstraints(
-                       [](const dataflow::Environment& Environment,
-                          auto ParamPointerValues) { return IsEmpty(); }));
+  EXPECT_THAT(getSafetyConstraints("void target() {}"), IsEmpty());
 }
 
 TEST(SafetyConstraintGenerator, GeneratesNoConstraintsForUnusedParam) {
-  static constexpr llvm::StringRef Src = R"cc(
-    void Target(int *p) {}
-  )cc";
-  EXPECT_THAT(Src, ProducesSafetyConstraints(
-                       [](const dataflow::Environment& Environment,
-                          auto ParamPointerValues) { return IsEmpty(); }));
+  EXPECT_THAT(getSafetyConstraints("void target(int *p) {}"), IsEmpty());
 }
 
 TEST(SafetyConstraintGenerator, GeneratesNotIsNullConstraintForDeref) {
   static constexpr llvm::StringRef Src = R"cc(
-    void Target(int *p) { *p; }
+    void target(int *p) { *p; }
   )cc";
-  EXPECT_THAT(
-      Src,
-      ProducesSafetyConstraints([](const dataflow::Environment& Environment,
-                                   auto ParamPointerValues) {
-        return UnorderedElementsAre(&Environment.makeNot(
-            getPointerNullState(*ParamPointerValues[0]).second));
-      }));
+  EXPECT_THAT(getSafetyConstraints(Src), ElementsAre("(not p.is_null)"));
 }
 
 TEST(SafetyConstraintGenerator,
      GeneratesNotIsNullConstraintForImproperlyGuardedDeref) {
   static constexpr llvm::StringRef Src = R"cc(
-    void Target(int *p) {
+    void target(int *p) {
       if (p == nullptr) *p;
     }
   )cc";
-  EXPECT_THAT(
-      Src,
-      ProducesSafetyConstraints([](const dataflow::Environment& Environment,
-                                   auto ParamPointerValues) {
-        return UnorderedElementsAre(&Environment.makeNot(
-            getPointerNullState(*ParamPointerValues[0]).second));
-      }));
+  EXPECT_THAT(getSafetyConstraints(Src), ElementsAre("(not p.is_null)"));
 }
 
 TEST(SafetyConstraintGenerator, GeneratesConstraintsForAllParams) {
   static constexpr llvm::StringRef Src = R"cc(
-    void Target(int *p, int *q, int *r) {
+    void target(int *p, int *q, int *r) {
       *p;
       *q;
       *r;
     }
   )cc";
-  EXPECT_THAT(Src, ProducesSafetyConstraints([](const dataflow::Environment&
-                                                    Environment,
-                                                auto ParamPointerValues) {
-                return UnorderedElementsAre(
-                    &Environment.makeNot(
-                        getPointerNullState(*ParamPointerValues[0]).second),
-                    &Environment.makeNot(
-                        getPointerNullState(*ParamPointerValues[1]).second),
-                    &Environment.makeNot(
-                        getPointerNullState(*ParamPointerValues[2]).second));
-              }));
+  EXPECT_THAT(getSafetyConstraints(Src),
+              UnorderedElementsAre("(not p.is_null)", "(not q.is_null)",
+                                   "(not r.is_null)"));
 }
 
 TEST(SafetyConstraintGenerator, DoesntGenerateConstraintForNullCheckedPtr) {
   static constexpr llvm::StringRef Src = R"cc(
-    void Target(int *p) {
+    void target(int *p) {
       if (p) *p;
       if (p != nullptr) *p;
     }
   )cc";
-  EXPECT_THAT(Src, ProducesSafetyConstraints(
-                       [](const dataflow::Environment& Environment,
-                          auto ParamPointerValues) { return IsEmpty(); }));
+  EXPECT_THAT(getSafetyConstraints(Src), IsEmpty());
 }
 
 TEST(SafetyConstraintGenerator,
@@ -181,18 +148,12 @@
   static constexpr llvm::StringRef Src = R"cc(
     int *getPtr();
 
-    void Target(int *p) {
+    void target(int *p) {
       *p;
       p = getPtr();
     }
   )cc";
-  EXPECT_THAT(
-      Src,
-      ProducesSafetyConstraints([](const dataflow::Environment& Environment,
-                                   auto ParamPointerValues) {
-        return UnorderedElementsAre(&Environment.makeNot(
-            getPointerNullState(*ParamPointerValues[0]).second));
-      }));
+  EXPECT_THAT(getSafetyConstraints(Src), ElementsAre("(not p.is_null)"));
 }
 
 TEST(SafetyConstraintGenerator,
@@ -200,21 +161,15 @@
   static constexpr llvm::StringRef Src = R"cc(
     int *getPtr();
 
-    void Target(int *p) {
+    void target(int *p) {
       p = getPtr();
       *p;
     }
   )cc";
-  EXPECT_THAT(
-      Src,
-      ProducesSafetyConstraints([](const dataflow::Environment& Environment,
-                                   auto ParamPointerValues) {
-        return AllOf(SizeIs(1),
-                     // TODO(b/268440048) Figure out how to access and assert
-                     // equality for the constraint that this is.
-                     Not(UnorderedElementsAre(&Environment.makeNot(
-                         getPointerNullState(*ParamPointerValues[0]).second))));
-      }));
+  // TODO(b/268440048) Figure out how to access and assert
+  // equality for the constraint that this is.
+  // (We require the value that models the getPtr() result to be non-null)
+  EXPECT_THAT(getSafetyConstraints(Src), ElementsAre(Not("(not p.is_null)")));
 }
 }  // namespace
 }  // namespace clang::tidy::nullability