Set up tooling for verifying C++ code complies with nullability annotations.

PiperOrigin-RevId: 452285783
diff --git a/nullability_verification/BUILD b/nullability_verification/BUILD
new file mode 100644
index 0000000..fb9700b
--- /dev/null
+++ b/nullability_verification/BUILD
@@ -0,0 +1,50 @@
+# Verification for null safety
+
+cc_library(
+    name = "pointer_nullability_lattice",
+    hdrs = ["pointer_nullability_lattice.h"],
+    deps = [
+        "@llvm-project//clang:analysis",
+        "@llvm-project//llvm:Support",
+    ],
+)
+
+cc_library(
+    name = "pointer_nullability_matchers",
+    srcs = ["pointer_nullability_matchers.cc"],
+    hdrs = ["pointer_nullability_matchers.h"],
+    deps = [
+        "@llvm-project//clang:ast_matchers",
+        "@llvm-project//llvm:Support",
+    ],
+)
+
+cc_library(
+    name = "pointer_nullability_analysis",
+    srcs = ["pointer_nullability_analysis.cc"],
+    hdrs = ["pointer_nullability_analysis.h"],
+    deps = [
+        ":pointer_nullability_lattice",
+        ":pointer_nullability_matchers",
+        "//common:check",
+        "@llvm-project//clang:analysis",
+        "@llvm-project//clang:ast",
+        "@llvm-project//clang:ast_matchers",
+        "@llvm-project//clang:basic",
+        "@llvm-project//clang:tooling",
+    ],
+)
+
+cc_test(
+    name = "pointer_nullability_analysis_test",
+    srcs = ["pointer_nullability_analysis_test.cc"],
+    deps = [
+        ":pointer_nullability_analysis",
+        "@llvm-project//clang/unittests:dataflow_testing_support",
+        "@llvm-project//llvm:Support",
+        "@llvm-project//llvm:TestingSupport",
+        "@llvm-project//llvm:gmock",
+        "@llvm-project//llvm:gtest",
+        "@llvm-project//llvm:gtest_main",
+    ],
+)
diff --git a/nullability_verification/pointer_nullability_analysis.cc b/nullability_verification/pointer_nullability_analysis.cc
new file mode 100644
index 0000000..5cde0c3
--- /dev/null
+++ b/nullability_verification/pointer_nullability_analysis.cc
@@ -0,0 +1,139 @@
+// 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_verification/pointer_nullability_analysis.h"
+
+#include <iostream>
+#include <string>
+
+#include "common/check.h"
+#include "nullability_verification/pointer_nullability_lattice.h"
+#include "nullability_verification/pointer_nullability_matchers.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+#include "clang/Analysis/FlowSensitive/MatchSwitch.h"
+#include "clang/Analysis/FlowSensitive/Value.h"
+#include "clang/Basic/LLVM.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+
+using ast_matchers::MatchFinder;
+using dataflow::BoolValue;
+using dataflow::Environment;
+using dataflow::MatchSwitchBuilder;
+using dataflow::PointerValue;
+using dataflow::SkipPast;
+using dataflow::TransferState;
+using dataflow::Value;
+
+namespace {
+
+void initialisePointerNullability(
+    const Expr* Expr, const MatchFinder::MatchResult&,
+    TransferState<PointerNullabilityLattice>& State) {
+  if (auto* PointerVal = cast_or_null<PointerValue>(
+          State.Env.getValue(*Expr, SkipPast::Reference))) {
+    if (!State.Lattice.hasPointerNullability(PointerVal)) {
+      State.Lattice.setPointerNullability(PointerVal,
+                                          &State.Env.makeAtomicBoolValue());
+    }
+  }
+}
+
+void transferDereference(const UnaryOperator* UnaryOp,
+                         const MatchFinder::MatchResult&,
+                         TransferState<PointerNullabilityLattice>& State) {
+  auto* PointerExpr = UnaryOp->getSubExpr();
+  if (auto* PointerVal = cast_or_null<PointerValue>(
+          State.Env.getValue(*PointerExpr, SkipPast::Reference))) {
+    auto PointerNullability = State.Lattice.getPointerNullability(PointerVal);
+    CHECK(PointerNullability != nullptr);
+    if (State.Env.flowConditionImplies(*PointerNullability)) {
+      return;
+    }
+  }
+  State.Lattice.addViolation(PointerExpr);
+}
+
+void transferNullCheckComparison(
+    const Expr* NullCheck, const Expr* PointerExpr,
+    TransferState<PointerNullabilityLattice>& State) {
+  if (auto* PointerVal = cast_or_null<PointerValue>(
+          State.Env.getValue(*PointerExpr, SkipPast::Reference))) {
+    auto* PointerNullability = State.Lattice.getPointerNullability(PointerVal);
+    CHECK(PointerNullability != nullptr);
+
+    // For binary operations, the dataflow framework automatically creates a
+    // corresponding BoolVal
+    auto* ExistingDFVal =
+        cast_or_null<BoolValue>(State.Env.getValue(*NullCheck, SkipPast::None));
+    CHECK(ExistingDFVal != nullptr);
+    State.Env.addToFlowCondition(
+        State.Env.makeIff(*ExistingDFVal, *PointerNullability));
+  }
+}
+
+void transferNullCheckImplicitCastPtrToBool(
+    const Expr* CastExpr, const MatchFinder::MatchResult&,
+    TransferState<PointerNullabilityLattice>& State) {
+  if (auto* PointerVal = cast_or_null<PointerValue>(State.Env.getValue(
+          *CastExpr->IgnoreImplicit(), SkipPast::Reference))) {
+    auto* PointerNullability = State.Lattice.getPointerNullability(PointerVal);
+    CHECK(PointerNullability != nullptr);
+
+    auto& CastExprLoc = State.Env.createStorageLocation(*CastExpr);
+    State.Env.setValue(CastExprLoc, *PointerNullability);
+    State.Env.setStorageLocation(*CastExpr, CastExprLoc);
+  }
+}
+
+auto buildTransferer() {
+  return MatchSwitchBuilder<TransferState<PointerNullabilityLattice>>()
+      // Initialise nullability state of pointers
+      .CaseOf<Expr>(isPointerExpr(), initialisePointerNullability)
+      // Pointer dereference
+      .CaseOf<UnaryOperator>(isPointerDereference(), transferDereference)
+      // Nullability check
+      .CaseOf<BinaryOperator>(
+          isNEQNullBinOp(/*BindID=*/"pointer"),
+          [](const BinaryOperator* binOp,
+             const MatchFinder::MatchResult& result,
+             TransferState<PointerNullabilityLattice>& State) {
+            transferNullCheckComparison(
+                binOp, result.Nodes.getNodeAs<Expr>("pointer"), State);
+          })
+      .CaseOf<Expr>(isImplicitCastPtrToBool(),
+                    transferNullCheckImplicitCastPtrToBool)
+      .Build();
+}
+}  // namespace
+
+PointerNullabilityAnalysis::PointerNullabilityAnalysis(ASTContext& Context)
+    : DataflowAnalysis<PointerNullabilityAnalysis, PointerNullabilityLattice>(
+          Context),
+      Transferer(buildTransferer()) {}
+
+void PointerNullabilityAnalysis::transfer(const Stmt* Stmt,
+                                          PointerNullabilityLattice& Lattice,
+                                          Environment& Env) {
+  TransferState<PointerNullabilityLattice> State(Lattice, Env);
+  Transferer(*Stmt, getASTContext(), State);
+}
+
+bool PointerNullabilityAnalysis::merge(QualType Type, const Value& Val1,
+                                       const Environment& Env1,
+                                       const Value& Val2,
+                                       const Environment& Env2,
+                                       Value& MergedVal,
+                                       Environment& MergedEnv) {
+  return false;
+}
+}  // namespace nullability
+}  // namespace tidy
+}  // namespace clang
diff --git a/nullability_verification/pointer_nullability_analysis.h b/nullability_verification/pointer_nullability_analysis.h
new file mode 100644
index 0000000..f15c358
--- /dev/null
+++ b/nullability_verification/pointer_nullability_analysis.h
@@ -0,0 +1,54 @@
+// 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_VERIFICATION_POINTER_NULLABILITY_ANALYSIS_H_
+#define CRUBIT_NULLABILITY_VERIFICATION_POINTER_NULLABILITY_ANALYSIS_H_
+
+#include "nullability_verification/pointer_nullability_lattice.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/Stmt.h"
+#include "clang/AST/Type.h"
+#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
+#include "clang/Analysis/FlowSensitive/MatchSwitch.h"
+#include "clang/Analysis/FlowSensitive/Value.h"
+#include "clang/Tooling/Tooling.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+
+// Current iteration marks lattice unsafe if there exists a pointer
+// dereference
+// TODO(b/233582219): Implement tracking of nullability to distinguish
+// safe/unsafe accesses
+
+class PointerNullabilityAnalysis
+    : public dataflow::DataflowAnalysis<PointerNullabilityAnalysis,
+                                        PointerNullabilityLattice> {
+ public:
+  explicit PointerNullabilityAnalysis(ASTContext& context);
+
+  static PointerNullabilityLattice initialElement() { return {}; }
+
+  void transfer(const Stmt* Stmt, PointerNullabilityLattice& Lattice,
+                dataflow::Environment& Env);
+
+  bool merge(QualType Type, const dataflow::Value& Val1,
+             const dataflow::Environment& Env1, const dataflow::Value& Val2,
+             const dataflow::Environment& Env2, dataflow::Value& MergedVal,
+             dataflow::Environment& MergedEnv) override;
+
+ private:
+  // Applies transfer functions on statements
+  dataflow::MatchSwitch<dataflow::TransferState<PointerNullabilityLattice>>
+      Transferer;
+};
+}  // namespace nullability
+}  // namespace tidy
+}  // namespace clang
+
+#endif  // CRUBIT_NULLABILITY_VERIFICATION_POINTER_NULLABILITY_ANALYSIS_H_
diff --git a/nullability_verification/pointer_nullability_analysis_test.cc b/nullability_verification/pointer_nullability_analysis_test.cc
new file mode 100644
index 0000000..a8ff6c1
--- /dev/null
+++ b/nullability_verification/pointer_nullability_analysis_test.cc
@@ -0,0 +1,151 @@
+// 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_verification/pointer_nullability_analysis.h"
+
+#include <string>
+
+#include "third_party/llvm/llvm-project/clang/unittests/Analysis/FlowSensitive/TestingSupport.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Testing/Support/Error.h"
+#include "third_party/llvm/llvm-project/llvm/utils/unittest/googlemock/include/gmock/gmock.h"
+#include "third_party/llvm/llvm-project/llvm/utils/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+using ::testing::Pair;
+using ::testing::Test;
+using ::testing::UnorderedElementsAre;
+
+using dataflow::DataflowAnalysisState;
+using dataflow::Environment;
+using dataflow::test::checkDataflow;
+
+MATCHER(IsSafe, "") { return arg.Lattice.isSafe(); }
+MATCHER(IsUnsafe, "") { return !arg.Lattice.isSafe(); }
+
+class PointerNullabilityTest : public Test {
+ protected:
+  template <typename Matcher>
+  void expectDataflow(llvm::StringRef Code, Matcher Expectations) {
+    ASSERT_THAT_ERROR(
+        checkDataflow<PointerNullabilityAnalysis>(
+            Code, "target",
+            [](ASTContext& ASTCtx, Environment&) {
+              return PointerNullabilityAnalysis(ASTCtx);
+            },
+            [&Expectations](
+                llvm::ArrayRef<
+                    std::pair<std::string,
+                              DataflowAnalysisState<PointerNullabilityLattice>>>
+                    Results,
+                ASTContext&) { EXPECT_THAT(Results, Expectations); },
+            {"-fsyntax-only", "-std=c++17"}),
+        llvm::Succeeded());
+  }
+};
+
+TEST_F(PointerNullabilityTest, SafeNoOp) {
+  std::string Code = R"(
+    void target(int* maybeNull) {
+      1 + 2;
+      /*[[check]]*/
+    }
+  )";
+  expectDataflow(Code, UnorderedElementsAre(Pair("check", IsSafe())));
+}
+
+TEST_F(PointerNullabilityTest, UnsafeUnchecked) {
+  std::string Code = R"(
+    void target(int* maybeNull) {
+      *maybeNull;
+      /*[[check]]*/
+    }
+  )";
+  expectDataflow(Code, UnorderedElementsAre(Pair("check", IsUnsafe())));
+}
+
+TEST_F(PointerNullabilityTest, SafeCheckNEQNull) {
+  std::string NullLiteralOnRight = R"(
+    void target(int* maybeNull) {
+      if (maybeNull != nullptr) {
+        *maybeNull;
+        /*[[check-safe]]*/
+      } else {
+        *maybeNull;
+        /*[[check-unsafe1]]*/
+      }
+      *maybeNull;
+      /*[[check-unsafe2]]*/
+    }
+  )";
+  expectDataflow(NullLiteralOnRight,
+                 UnorderedElementsAre(Pair("check-safe", IsSafe()),
+                                      Pair("check-unsafe1", IsUnsafe()),
+                                      Pair("check-unsafe2", IsUnsafe())));
+
+  std::string NullLiteralOnLeft = R"(
+    void target(int* maybeNull) {
+      if (nullptr != maybeNull) {
+        *maybeNull;
+        /*[[check-safe]]*/
+      } else {
+        *maybeNull;
+        /*[[check-unsafe1]]*/
+      }
+      *maybeNull;
+      /*[[check-unsafe2]]*/
+    }
+  )";
+  expectDataflow(NullLiteralOnLeft,
+                 UnorderedElementsAre(Pair("check-safe", IsSafe()),
+                                      Pair("check-unsafe1", IsUnsafe()),
+                                      Pair("check-unsafe2", IsUnsafe())));
+}
+
+TEST_F(PointerNullabilityTest, SafeCheckImplicitCastToBool) {
+  std::string Code = R"(
+    void target(int* maybeNull) {
+      if (maybeNull) {
+        *maybeNull;
+        /*[[check-safe]]*/
+      } else {
+        *maybeNull;
+        /*[[check-unsafe1]]*/
+      }
+      *maybeNull;
+      /*[[check-unsafe2]]*/
+    }
+  )";
+  expectDataflow(Code, UnorderedElementsAre(Pair("check-safe", IsSafe()),
+                                            Pair("check-unsafe1", IsUnsafe()),
+                                            Pair("check-unsafe2", IsUnsafe())));
+  std::string NegatedCondition = R"(
+    void target(int* maybeNull) {
+      if (!maybeNull) {
+        *maybeNull;
+        /*[[check-unsafe1]]*/
+      } else {
+        *maybeNull;
+        /*[[check-safe]]*/
+      }
+      *maybeNull;
+      /*[[check-unsafe2]]*/
+    }
+  )";
+  expectDataflow(NegatedCondition,
+                 UnorderedElementsAre(Pair("check-safe", IsSafe()),
+                                      Pair("check-unsafe1", IsUnsafe()),
+                                      Pair("check-unsafe2", IsUnsafe())));
+}
+
+}  // namespace
+}  // namespace nullability
+}  // namespace tidy
+}  // namespace clang
diff --git a/nullability_verification/pointer_nullability_lattice.h b/nullability_verification/pointer_nullability_lattice.h
new file mode 100644
index 0000000..917e075
--- /dev/null
+++ b/nullability_verification/pointer_nullability_lattice.h
@@ -0,0 +1,61 @@
+// 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_VERIFICATION_POINTER_NULLABILITY_LATTICE_H_
+#define CRUBIT_NULLABILITY_VERIFICATION_POINTER_NULLABILITY_LATTICE_H_
+
+#include <ostream>
+
+#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
+#include "clang/Analysis/FlowSensitive/Value.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+class PointerNullabilityLattice {
+ public:
+  PointerNullabilityLattice() = default;
+
+  bool operator==(const PointerNullabilityLattice& Other) const { return true; }
+
+  dataflow::LatticeJoinEffect join(const PointerNullabilityLattice& Other) {
+    return dataflow::LatticeJoinEffect::Unchanged;
+  }
+
+  bool hasPointerNullability(clang::dataflow::PointerValue* PointerVal) {
+    return pointer_nullabilities_.find(PointerVal) !=
+           pointer_nullabilities_.end();
+  }
+
+  dataflow::BoolValue* getPointerNullability(
+      dataflow::PointerValue* PointerVal) {
+    auto search = pointer_nullabilities_.find(PointerVal);
+    return search == pointer_nullabilities_.end() ? nullptr : search->second;
+  }
+
+  void setPointerNullability(dataflow::PointerValue* PointerVal,
+                             dataflow::BoolValue* BoolVal) {
+    pointer_nullabilities_[PointerVal] = BoolVal;
+  }
+
+  bool isSafe() const { return violations_.empty(); }
+  void addViolation(const Expr* Violation) { violations_.insert(Violation); }
+
+ private:
+  llvm::DenseSet<const Expr*> violations_;
+  llvm::DenseMap<dataflow::PointerValue*, dataflow::BoolValue*>
+      pointer_nullabilities_;
+};
+
+inline std::ostream& operator<<(std::ostream& OS,
+                                const PointerNullabilityLattice& L) {
+  return OS << "Pointer Nullability Lattice";
+}
+}  // namespace nullability
+}  // namespace tidy
+}  // namespace clang
+
+#endif  // CRUBIT_NULLABILITY_VERIFICATION_POINTER_NULLABILITY_LATTICE_H_
diff --git a/nullability_verification/pointer_nullability_matchers.cc b/nullability_verification/pointer_nullability_matchers.cc
new file mode 100644
index 0000000..37c4266
--- /dev/null
+++ b/nullability_verification/pointer_nullability_matchers.cc
@@ -0,0 +1,49 @@
+// 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_verification/pointer_nullability_matchers.h"
+
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/StringRef.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+
+using ast_matchers::binaryOperator;
+using ast_matchers::booleanType;
+using ast_matchers::expr;
+using ast_matchers::hasImplicitDestinationType;
+using ast_matchers::hasOperands;
+using ast_matchers::hasOperatorName;
+using ast_matchers::hasSourceExpression;
+using ast_matchers::hasType;
+using ast_matchers::hasUnaryOperand;
+using ast_matchers::ignoringImplicit;
+using ast_matchers::implicitCastExpr;
+using ast_matchers::isAnyPointer;
+using ast_matchers::nullPointerConstant;
+using ast_matchers::unaryOperator;
+using ast_matchers::internal::Matcher;
+
+Matcher<Stmt> isPointerExpr() { return expr(hasType(isAnyPointer())); }
+
+Matcher<Stmt> isPointerDereference() {
+  return unaryOperator(hasOperatorName("*"), hasUnaryOperand(isPointerExpr()));
+}
+
+Matcher<Stmt> isNEQNullBinOp(llvm::StringRef BindID) {
+  return binaryOperator(
+      hasOperatorName("!="),
+      hasOperands(ignoringImplicit(nullPointerConstant()),
+                  expr(hasType(isAnyPointer())).bind(BindID)));
+}
+
+Matcher<Stmt> isImplicitCastPtrToBool() {
+  return implicitCastExpr(hasSourceExpression(isPointerExpr()),
+                          hasImplicitDestinationType(booleanType()));
+}
+}  // namespace nullability
+}  // namespace tidy
+}  // namespace clang
diff --git a/nullability_verification/pointer_nullability_matchers.h b/nullability_verification/pointer_nullability_matchers.h
new file mode 100644
index 0000000..ed30a23
--- /dev/null
+++ b/nullability_verification/pointer_nullability_matchers.h
@@ -0,0 +1,23 @@
+// 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_VERIFICATION_POINTER_NULLABILITY_MATCHERS_H_
+#define CRUBIT_NULLABILITY_VERIFICATION_POINTER_NULLABILITY_MATCHERS_H_
+
+#include "clang/ASTMatchers/ASTMatchersInternal.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+
+ast_matchers::internal::Matcher<Stmt> isPointerExpr();
+ast_matchers::internal::Matcher<Stmt> isPointerDereference();
+ast_matchers::internal::Matcher<Stmt> isNEQNullBinOp(llvm::StringRef BindID);
+ast_matchers::internal::Matcher<Stmt> isImplicitCastPtrToBool();
+
+}  // namespace nullability
+}  // namespace tidy
+}  // namespace clang
+
+#endif  // CRUBIT_NULLABILITY_VERIFICATION_POINTER_NULLABILITY_MATCHERS_H_