[nullability] Simplify flow conditions produced when comparing against `nullptr`.

When comparing against `nullptr`, instead of adding three implications to the
flow condition, we can leave the flow condition unchanged and simply use the
nullstate of the operand that isn't `nullptr`, potentially with an added
negation.

Comparing against `nullptr` is the overwhelmingly common case, and simpler flow
conditions help in two ways:

- The SAT solver needs to do less work.

- Flow conditions are easier to interpret when debugging.

The tests in test/comparisons.cc already provide coverage for the new case
distinctions introduced here.

PiperOrigin-RevId: 559659135
Change-Id: I37ad7430654646cd31c3bcd4e69e97010496d6e5
diff --git a/nullability/pointer_nullability_analysis.cc b/nullability/pointer_nullability_analysis.cc
index 344df00..28dc665 100644
--- a/nullability/pointer_nullability_analysis.cc
+++ b/nullability/pointer_nullability_analysis.cc
@@ -323,6 +323,36 @@
     const BinaryOperator *BinaryOp, const MatchFinder::MatchResult &result,
     TransferState<PointerNullabilityLattice> &State) {
   auto &A = State.Env.arena();
+
+  auto *LHS = getPointerValueFromExpr(BinaryOp->getLHS(), State.Env);
+  auto *RHS = getPointerValueFromExpr(BinaryOp->getRHS(), State.Env);
+
+  if (!LHS || !RHS) return;
+  if (!hasPointerNullState(*LHS) || !hasPointerNullState(*RHS)) return;
+
+  BoolValue &LHSNullVal = getPointerNullState(*LHS).second;
+  BoolValue &RHSNullVal = getPointerNullState(*RHS).second;
+  auto &LHSNull = LHSNullVal.formula();
+  auto &RHSNull = RHSNullVal.formula();
+
+  // Special case: Are we comparing against `nullptr`?
+  // We can avoid modifying the flow condition in this case and simply propagate
+  // the nullability of the other operand (potentially with a negation).
+  if (&LHSNull == &A.makeLiteral(true)) {
+    if (BinaryOp->getOpcode() == BO_EQ)
+      State.Env.setValue(*BinaryOp, RHSNullVal);
+    else
+      State.Env.setValue(*BinaryOp, State.Env.makeNot(RHSNullVal));
+    return;
+  }
+  if (&RHSNull == &A.makeLiteral(true)) {
+    if (BinaryOp->getOpcode() == BO_EQ)
+      State.Env.setValue(*BinaryOp, LHSNullVal);
+    else
+      State.Env.setValue(*BinaryOp, State.Env.makeNot(LHSNullVal));
+    return;
+  }
+
   // Boolean representing the comparison between the two pointer values,
   // automatically created by the dataflow framework.
   auto &PointerComparison =
@@ -336,25 +366,15 @@
                         ? A.makeNot(PointerComparison)
                         : PointerComparison;
 
-  auto *LHS = getPointerValueFromExpr(BinaryOp->getLHS(), State.Env);
-  auto *RHS = getPointerValueFromExpr(BinaryOp->getRHS(), State.Env);
-
-  if (!LHS || !RHS) return;
-
-  auto &LHSNull = getPointerNullState(*LHS).second.formula();
-  auto &RHSNull = getPointerNullState(*RHS).second.formula();
-  auto &LHSNotNull = A.makeNot(LHSNull);
-  auto &RHSNotNull = A.makeNot(RHSNull);
-
   // nullptr == nullptr
   State.Env.addToFlowCondition(
       A.makeImplies(A.makeAnd(LHSNull, RHSNull), PointerEQ));
   // nullptr != notnull
   State.Env.addToFlowCondition(
-      A.makeImplies(A.makeAnd(LHSNull, RHSNotNull), PointerNE));
+      A.makeImplies(A.makeAnd(LHSNull, A.makeNot(RHSNull)), PointerNE));
   // notnull != nullptr
   State.Env.addToFlowCondition(
-      A.makeImplies(A.makeAnd(LHSNotNull, RHSNull), PointerNE));
+      A.makeImplies(A.makeAnd(A.makeNot(LHSNull), RHSNull), PointerNE));
 }
 
 void transferFlowSensitiveNullCheckImplicitCastPtrToBool(