Rework PointerNullState APIs

These now return Formulas instead of BoolValues, which drops the requirement
to be atomic bools, and opens the door to true/false becoming Formula primitives
rather than Atoms in the framework (and formula simplification).

The connection between nonnull annotations and the is_null property is now
expressed as a global invariant, so it works properly when is_null is neither
exactly true/false nor unconstrained - i.e. when it's restricted by a symbolic
nullability variable in inference.

PiperOrigin-RevId: 567282695
Change-Id: I2e8ac1c173c8bb1526a357d3eafc08935ec8a2da
diff --git a/nullability/pointer_nullability.cc b/nullability/pointer_nullability.cc
index 2d86787..495b72e 100644
--- a/nullability/pointer_nullability.cc
+++ b/nullability/pointer_nullability.cc
@@ -4,9 +4,12 @@
 
 #include "nullability/pointer_nullability.h"
 
-#include <utility>
+#include <cassert>
+#include <optional>
 
+#include "nullability/type_nullability.h"
 #include "clang/AST/Type.h"
+#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
 #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
 #include "clang/Analysis/FlowSensitive/Formula.h"
 #include "clang/Analysis/FlowSensitive/Value.h"
@@ -18,7 +21,9 @@
 
 using dataflow::AtomicBoolValue;
 using dataflow::BoolValue;
+using dataflow::DataflowAnalysisContext;
 using dataflow::Environment;
+using dataflow::Formula;
 using dataflow::PointerValue;
 using dataflow::Value;
 
@@ -41,41 +46,55 @@
          PointerVal.getProperty(kNull) != nullptr;
 }
 
-std::pair<AtomicBoolValue &, AtomicBoolValue &> getPointerNullState(
-    const PointerValue &PointerVal) {
+PointerNullState getPointerNullState(const PointerValue &PointerVal) {
   auto &FromNullable =
       *cast<AtomicBoolValue>(PointerVal.getProperty(kFromNullable));
   auto &Null = *cast<AtomicBoolValue>(PointerVal.getProperty(kNull));
-  return {FromNullable, Null};
+  return {FromNullable.formula(), Null.formula()};
 }
 
-void initPointerBoolProperty(PointerValue &PointerVal, llvm::StringRef Name,
-                             BoolValue *BoolVal, Environment &Env) {
-  if (PointerVal.getProperty(Name) != nullptr) return;
-  // The property must always be a non-null boolean atom.
-  if (!isa_and_nonnull<AtomicBoolValue>(BoolVal)) {
-    auto &Atom = Env.makeAtomicBoolValue();
-    if (BoolVal)
-      Env.addToFlowCondition(
-          Env.arena().makeEquals(Atom.formula(), BoolVal->formula()));
-    BoolVal = &Atom;
+static bool tryCreatePointerNullState(PointerValue &PointerVal,
+                                      dataflow::Arena &A,
+                                      const Formula *FromNullable = nullptr,
+                                      const Formula *IsNull = nullptr) {
+  // TODO: for now we assume that we have both nullability properties, or none.
+  // We'll need to relax this when properties can be independently widened away.
+  if (hasPointerNullState(PointerVal)) return false;
+  if (!FromNullable) FromNullable = &A.makeAtomRef(A.makeAtom());
+  if (!IsNull) IsNull = &A.makeAtomRef(A.makeAtom());
+  PointerVal.setProperty(kFromNullable, A.makeBoolValue(*FromNullable));
+  PointerVal.setProperty(kNull, A.makeBoolValue(*IsNull));
+  return true;
+}
+
+void initPointerNullState(PointerValue &PointerVal,
+                          DataflowAnalysisContext &Ctx,
+                          std::optional<PointerTypeNullability> Source) {
+  auto &A = Ctx.arena();
+  if (tryCreatePointerNullState(PointerVal, A,
+                                Source ? &Source->isNullable(A) : nullptr)) {
+    // The symbolic/nonnull check is not needed for correctness, but it avoids
+    // adding meaningless (false => !null) invariant clauses.
+    // TODO: remove this once such clauses are recognized and dropped.
+    if (Source &&
+        (Source->isSymbolic() || Source == NullabilityKind::NonNull)) {
+      const Formula &IsNull = getPointerNullState(PointerVal).IsNull;
+      Ctx.addInvariant(A.makeImplies(Source->isNonnull(A), A.makeNot(IsNull)));
+    }
   }
-  PointerVal.setProperty(Name, BoolVal ? *BoolVal : Env.makeAtomicBoolValue());
 }
 
-void initPointerNullState(PointerValue &PointerVal, Environment &Env,
-                          BoolValue *FromNullableConstraint,
-                          BoolValue *NullConstraint) {
-  initPointerBoolProperty(PointerVal, kFromNullable, FromNullableConstraint,
-                          Env);
-  initPointerBoolProperty(PointerVal, kNull, NullConstraint, Env);
+void initNullPointer(PointerValue &PointerVal, DataflowAnalysisContext &Ctx) {
+  tryCreatePointerNullState(PointerVal, Ctx.arena(),
+                            /*FromNullable=*/&Ctx.arena().makeLiteral(true),
+                            /*IsNull=*/&Ctx.arena().makeLiteral(true));
 }
 
 bool isNullable(const PointerValue &PointerVal, const Environment &Env,
                 const dataflow::Formula *AdditionalConstraints) {
   auto &A = Env.getDataflowAnalysisContext().arena();
   auto [FromNullable, Null] = getPointerNullState(PointerVal);
-  auto *ForseeablyNull = &A.makeAnd(FromNullable.formula(), Null.formula());
+  auto *ForseeablyNull = &A.makeAnd(FromNullable, Null);
   if (AdditionalConstraints)
     ForseeablyNull = &A.makeAnd(*AdditionalConstraints, *ForseeablyNull);
   return !Env.flowConditionImplies(A.makeNot(*ForseeablyNull));
@@ -85,7 +104,7 @@
                                const dataflow::Environment &Env,
                                const dataflow::Formula *AdditionalConstraints) {
   auto &A = Env.getDataflowAnalysisContext().arena();
-  auto *Null = &getPointerNullState(PointerVal).second.formula();
+  auto *Null = &getPointerNullState(PointerVal).IsNull;
   if (AdditionalConstraints) Null = &A.makeAnd(*AdditionalConstraints, *Null);
   if (Env.flowConditionImplies(A.makeNot(*Null)))
     return NullabilityKind::NonNull;