[nullability] Allow nullability properties to be "top".
This is preparatory work for pointer widening; I'm submitting it for review
separately because:
* It separates this API change from the pointer widening itself.
* It shows that the API change is a no-op in terms of behavior (though note
that, so far, we aren't covering the cases where the nullability properties
are "top" -- this will come with the pointer widening).
PiperOrigin-RevId: 576854948
Change-Id: I3b13750193e682d831f930cb8fe030ec56b806a0
diff --git a/nullability/BUILD b/nullability/BUILD
index 3ba5bcf..b06dd32 100644
--- a/nullability/BUILD
+++ b/nullability/BUILD
@@ -117,6 +117,7 @@
],
deps = [
":type_nullability",
+ "@absl//absl/base:nullability",
"@llvm-project//clang:analysis",
"@llvm-project//clang:ast",
"@llvm-project//clang:basic",
@@ -133,6 +134,7 @@
"@llvm-project//clang:analysis",
"@llvm-project//clang:ast",
"@llvm-project//clang:basic",
+ "@llvm-project//third-party/unittest:gmock",
"@llvm-project//third-party/unittest:gtest",
"@llvm-project//third-party/unittest:gtest_main",
],
diff --git a/nullability/inference/collect_evidence.cc b/nullability/inference/collect_evidence.cc
index 5fdff82..6cf2abf 100644
--- a/nullability/inference/collect_evidence.cc
+++ b/nullability/inference/collect_evidence.cc
@@ -133,7 +133,10 @@
std::vector<std::pair<PointerTypeNullability, Slot>> &InferableSlots,
Evidence::Kind EvidenceKind, llvm::function_ref<EvidenceEmitter> Emit) {
auto &A = Env.getDataflowAnalysisContext().arena();
- auto &NotIsNull = A.makeNot(getPointerNullState(Value).IsNull);
+ auto *IsNull = getPointerNullState(Value).IsNull;
+ // If `IsNull` is top, we can't infer anything about it.
+ if (IsNull == nullptr) return;
+ auto &NotIsNull = A.makeNot(*getPointerNullState(Value).IsNull);
// If the flow conditions already imply that Value is not null, then we don't
// have any new evidence of a necessary annotation.
diff --git a/nullability/pointer_nullability.cc b/nullability/pointer_nullability.cc
index 54daf2b..8235860 100644
--- a/nullability/pointer_nullability.cc
+++ b/nullability/pointer_nullability.cc
@@ -25,6 +25,7 @@
using dataflow::Environment;
using dataflow::Formula;
using dataflow::PointerValue;
+using dataflow::TopBoolValue;
using dataflow::Value;
/// The nullness information of a pointer is represented by two properties
@@ -47,17 +48,22 @@
}
PointerNullState getPointerNullState(const PointerValue &PointerVal) {
- auto &FromNullable = *cast<BoolValue>(PointerVal.getProperty(kFromNullable));
- auto &Null = *cast<BoolValue>(PointerVal.getProperty(kNull));
- return {FromNullable.formula(), Null.formula()};
+ Value *FromNullableProp = PointerVal.getProperty(kFromNullable);
+ Value *NullProp = PointerVal.getProperty(kNull);
+
+ return {
+ isa<TopBoolValue>(FromNullableProp)
+ ? nullptr
+ : &cast<BoolValue>(FromNullableProp)->formula(),
+ isa<TopBoolValue>(NullProp) ? nullptr
+ : &cast<BoolValue>(NullProp)->formula(),
+ };
}
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());
@@ -77,12 +83,23 @@
// 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)));
+ if (const Formula *IsNull = getPointerNullState(PointerVal).IsNull)
+ Ctx.addInvariant(
+ A.makeImplies(Source->isNonnull(A), A.makeNot(*IsNull)));
}
}
}
+void forgetFromNullable(dataflow::PointerValue &PointerVal,
+ DataflowAnalysisContext &Ctx) {
+ PointerVal.setProperty(kFromNullable, Ctx.arena().makeTopValue());
+}
+
+void forgetIsNull(dataflow::PointerValue &PointerVal,
+ DataflowAnalysisContext &Ctx) {
+ PointerVal.setProperty(kNull, Ctx.arena().makeTopValue());
+}
+
void initNullPointer(PointerValue &PointerVal, DataflowAnalysisContext &Ctx) {
tryCreatePointerNullState(PointerVal, Ctx.arena(),
/*FromNullable=*/&Ctx.arena().makeLiteral(true),
@@ -93,9 +110,18 @@
const dataflow::Formula *AdditionalConstraints) {
auto &A = Env.getDataflowAnalysisContext().arena();
auto [FromNullable, Null] = getPointerNullState(PointerVal);
- auto *ForseeablyNull = &A.makeAnd(FromNullable, Null);
+
+ // A value is nullable if two things can be simultaneously true:
+ // - We got it from a nullable source
+ // (values from unknown sources may be null, but are not nullable)
+ // - The value is actually null
+ // (if a value from a nullable source was checked, it's not nullable)
+ const Formula *ForseeablyNull = &A.makeLiteral(true);
+ if (FromNullable) ForseeablyNull = &A.makeAnd(*ForseeablyNull, *FromNullable);
+ if (Null) ForseeablyNull = &A.makeAnd(*ForseeablyNull, *Null);
if (AdditionalConstraints)
- ForseeablyNull = &A.makeAnd(*AdditionalConstraints, *ForseeablyNull);
+ ForseeablyNull = &A.makeAnd(*ForseeablyNull, *AdditionalConstraints);
+
return !Env.flowConditionImplies(A.makeNot(*ForseeablyNull));
}
@@ -103,10 +129,11 @@
const dataflow::Environment &Env,
const dataflow::Formula *AdditionalConstraints) {
auto &A = Env.getDataflowAnalysisContext().arena();
- auto *Null = &getPointerNullState(PointerVal).IsNull;
- if (AdditionalConstraints) Null = &A.makeAnd(*AdditionalConstraints, *Null);
- if (Env.flowConditionImplies(A.makeNot(*Null)))
- return NullabilityKind::NonNull;
+ if (auto *Null = getPointerNullState(PointerVal).IsNull) {
+ if (AdditionalConstraints) Null = &A.makeAnd(*AdditionalConstraints, *Null);
+ if (Env.flowConditionImplies(A.makeNot(*Null)))
+ return NullabilityKind::NonNull;
+ }
return isNullable(PointerVal, Env, AdditionalConstraints)
? NullabilityKind::Nullable
: NullabilityKind::Unspecified;
diff --git a/nullability/pointer_nullability.h b/nullability/pointer_nullability.h
index 8e156bb..a768650 100644
--- a/nullability/pointer_nullability.h
+++ b/nullability/pointer_nullability.h
@@ -15,6 +15,7 @@
#include <optional>
+#include "absl/base/nullability.h"
#include "nullability/type_nullability.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTDumper.h"
@@ -45,11 +46,14 @@
/// The properties representing nullness information for a pointer.
///
/// We attach these properties to every PointerValue taken by an expression.
+///
+/// A null pointer for `FromNullable` or `IsNull` represents "top", i.e. we have
+/// no information on this property.
struct PointerNullState {
/// Did the pointer come from a known-nullable source?
- const dataflow::Formula &FromNullable;
+ absl::Nullable<const dataflow::Formula *> FromNullable;
/// Is the pointer's value null?
- const dataflow::Formula &IsNull;
+ absl::Nullable<const dataflow::Formula *> IsNull;
// These are independent: sources with unknown nullability can yield nullptr!
};
@@ -79,6 +83,20 @@
dataflow::PointerValue &PointerVal, dataflow::DataflowAnalysisContext &Ctx,
std::optional<PointerTypeNullability> Source = std::nullopt);
+// Sets the `FromNullable` state of `PointerVal` to null (interpreted as "top").
+// Explicitly indicating that we don't know whether the source was nullable is a
+// form of widening that allows analysis to converge.
+// This mutates the `PointerValue`, so it should be freshly created and not have
+// been shared with other environments.
+void forgetFromNullable(dataflow::PointerValue &PointerVal,
+ dataflow::DataflowAnalysisContext &Ctx);
+
+// Sets the `IsNull` state of `PointerVal` to null (interpreted as "top").
+// This mutates the `PointerValue`, so it should be freshly created and not have
+// been shared with other environments.
+void forgetIsNull(dataflow::PointerValue &PointerVal,
+ dataflow::DataflowAnalysisContext &Ctx);
+
/// Variant of initPointerNullState, where the pointer is guaranteed null.
/// (This is flow-insensitive, but PointerTypeNullability can't represent it).
void initNullPointer(dataflow::PointerValue &PointerVal,
diff --git a/nullability/pointer_nullability_analysis.cc b/nullability/pointer_nullability_analysis.cc
index 337bffd..1e34a58 100644
--- a/nullability/pointer_nullability_analysis.cc
+++ b/nullability/pointer_nullability_analysis.cc
@@ -27,6 +27,7 @@
#include "clang/Analysis/FlowSensitive/Arena.h"
#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
+#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
#include "clang/Analysis/FlowSensitive/Value.h"
@@ -40,6 +41,7 @@
using ast_matchers::MatchFinder;
using dataflow::BoolValue;
using dataflow::CFGMatchSwitchBuilder;
+using dataflow::DataflowAnalysisContext;
using dataflow::Environment;
using dataflow::Formula;
using dataflow::PointerValue;
@@ -308,24 +310,32 @@
if (!LHS || !RHS) return;
if (!hasPointerNullState(*LHS) || !hasPointerNullState(*RHS)) return;
- auto &LHSNull = getPointerNullState(*LHS).IsNull;
- auto &RHSNull = getPointerNullState(*RHS).IsNull;
+ auto *LHSNull = getPointerNullState(*LHS).IsNull;
+ auto *RHSNull = getPointerNullState(*RHS).IsNull;
+
+ // If the null state of either pointer is "top", the result of the comparison
+ // is a top bool, and we don't have any knowledge we can add to the flow
+ // condition.
+ if (LHSNull == nullptr || RHSNull == nullptr) {
+ State.Env.setValue(*BinaryOp, A.makeTopValue());
+ return;
+ }
// 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 (LHSNull == &A.makeLiteral(true)) {
if (BinaryOp->getOpcode() == BO_EQ)
- State.Env.setValue(*BinaryOp, A.makeBoolValue(RHSNull));
+ State.Env.setValue(*BinaryOp, A.makeBoolValue(*RHSNull));
else
- State.Env.setValue(*BinaryOp, A.makeBoolValue(A.makeNot(RHSNull)));
+ State.Env.setValue(*BinaryOp, A.makeBoolValue(A.makeNot(*RHSNull)));
return;
}
- if (&RHSNull == &A.makeLiteral(true)) {
+ if (RHSNull == &A.makeLiteral(true)) {
if (BinaryOp->getOpcode() == BO_EQ)
- State.Env.setValue(*BinaryOp, A.makeBoolValue(LHSNull));
+ State.Env.setValue(*BinaryOp, A.makeBoolValue(*LHSNull));
else
- State.Env.setValue(*BinaryOp, A.makeBoolValue(A.makeNot(LHSNull)));
+ State.Env.setValue(*BinaryOp, A.makeBoolValue(A.makeNot(*LHSNull)));
return;
}
@@ -344,13 +354,13 @@
// nullptr == nullptr
State.Env.addToFlowCondition(
- A.makeImplies(A.makeAnd(LHSNull, RHSNull), PointerEQ));
+ A.makeImplies(A.makeAnd(*LHSNull, *RHSNull), PointerEQ));
// nullptr != notnull
State.Env.addToFlowCondition(
- A.makeImplies(A.makeAnd(LHSNull, A.makeNot(RHSNull)), PointerNE));
+ A.makeImplies(A.makeAnd(*LHSNull, A.makeNot(*RHSNull)), PointerNE));
// notnull != nullptr
State.Env.addToFlowCondition(
- A.makeImplies(A.makeAnd(A.makeNot(LHSNull), RHSNull), PointerNE));
+ A.makeImplies(A.makeAnd(A.makeNot(*LHSNull), *RHSNull), PointerNE));
}
void transferFlowSensitiveNullCheckImplicitCastPtrToBool(
@@ -362,7 +372,11 @@
if (!PointerVal) return;
auto Nullability = getPointerNullState(*PointerVal);
- State.Env.setValue(*CastExpr, A.makeBoolValue(A.makeNot(Nullability.IsNull)));
+ if (Nullability.IsNull != nullptr)
+ State.Env.setValue(*CastExpr,
+ A.makeBoolValue(A.makeNot(*Nullability.IsNull)));
+ else
+ State.Env.setValue(*CastExpr, A.makeTopValue());
}
void initializeOutputParameter(const Expr *Arg, dataflow::Environment &Env,
@@ -878,15 +892,17 @@
FlowSensitiveTransferer(Elt, getASTContext(), State);
}
-static const Formula &mergeFormulas(const Formula &Bool1,
+static const Formula *mergeFormulas(const Formula *Bool1,
const Environment &Env1,
- const Formula &Bool2,
+ const Formula *Bool2,
const Environment &Env2,
Environment &MergedEnv) {
- if (&Bool1 == &Bool2) {
+ if (Bool1 == Bool2) {
return Bool1;
}
+ if (Bool1 == nullptr || Bool2 == nullptr) return nullptr;
+
auto &A = MergedEnv.arena();
auto &MergedBool = A.makeAtomRef(A.makeAtom());
@@ -895,10 +911,10 @@
// path taken - this simplifies the flow condition tracked in `MergedEnv`.
// Otherwise, information about which path was taken is used to associate
// `MergedBool` with `Bool1` and `Bool2`.
- if (Env1.flowConditionImplies(Bool1) && Env2.flowConditionImplies(Bool2)) {
+ if (Env1.flowConditionImplies(*Bool1) && Env2.flowConditionImplies(*Bool2)) {
MergedEnv.addToFlowCondition(MergedBool);
- } else if (Env1.flowConditionImplies(A.makeNot(Bool1)) &&
- Env2.flowConditionImplies(A.makeNot(Bool2))) {
+ } else if (Env1.flowConditionImplies(A.makeNot(*Bool1)) &&
+ Env2.flowConditionImplies(A.makeNot(*Bool2))) {
MergedEnv.addToFlowCondition(A.makeNot(MergedBool));
} else {
// TODO(b/233582219): Flow conditions are not necessarily mutually
@@ -907,10 +923,10 @@
auto FC1 = Env1.getFlowConditionToken();
auto FC2 = Env2.getFlowConditionToken();
MergedEnv.addToFlowCondition(A.makeOr(
- A.makeAnd(A.makeAtomRef(FC1), A.makeEquals(MergedBool, Bool1)),
- A.makeAnd(A.makeAtomRef(FC2), A.makeEquals(MergedBool, Bool2))));
+ A.makeAnd(A.makeAtomRef(FC1), A.makeEquals(MergedBool, *Bool1)),
+ A.makeAnd(A.makeAtomRef(FC2), A.makeEquals(MergedBool, *Bool2))));
}
- return MergedBool;
+ return &MergedBool;
}
bool PointerNullabilityAnalysis::merge(QualType Type, const Value &Val1,
@@ -928,22 +944,35 @@
return false;
}
+ auto &MergedPointerVal = cast<PointerValue>(MergedVal);
+ DataflowAnalysisContext &Ctx = MergedEnv.getDataflowAnalysisContext();
+ auto &A = MergedEnv.arena();
+
auto Nullability1 = getPointerNullState(cast<PointerValue>(Val1));
auto Nullability2 = getPointerNullState(cast<PointerValue>(Val2));
- auto &FromNullable =
- mergeFormulas(Nullability1.FromNullable, Env1, Nullability2.FromNullable,
- Env2, MergedEnv);
- auto &Null = mergeFormulas(Nullability1.IsNull, Env1, Nullability2.IsNull,
- Env2, MergedEnv);
+ // Initialize `MergedPointerVal`'s nullability properties with atoms. These
+ // are potentially replaced with "top" below.
+ assert(!hasPointerNullState(MergedPointerVal));
+ initPointerNullState(MergedPointerVal, Ctx);
+ auto MergedNullability = getPointerNullState(MergedPointerVal);
+ assert(MergedNullability.FromNullable != nullptr);
+ assert(MergedNullability.IsNull != nullptr);
- initPointerNullState(cast<PointerValue>(MergedVal),
- MergedEnv.getDataflowAnalysisContext());
- auto MergedNullability = getPointerNullState(cast<PointerValue>(MergedVal));
- auto &A = MergedEnv.arena();
- MergedEnv.addToFlowCondition(
- A.makeEquals(MergedNullability.FromNullable, FromNullable));
- MergedEnv.addToFlowCondition(A.makeEquals(MergedNullability.IsNull, Null));
+ if (auto *FromNullable =
+ mergeFormulas(Nullability1.FromNullable, Env1,
+ Nullability2.FromNullable, Env2, MergedEnv))
+ MergedEnv.addToFlowCondition(
+ A.makeEquals(*MergedNullability.FromNullable, *FromNullable));
+ else
+ forgetFromNullable(MergedPointerVal, Ctx);
+
+ if (auto *Null = mergeFormulas(Nullability1.IsNull, Env1, Nullability2.IsNull,
+ Env2, MergedEnv))
+ MergedEnv.addToFlowCondition(
+ A.makeEquals(*MergedNullability.IsNull, *Null));
+ else
+ forgetIsNull(MergedPointerVal, Ctx);
return true;
}
diff --git a/nullability/pointer_nullability_analysis_test.cc b/nullability/pointer_nullability_analysis_test.cc
index 61eb15d..1bc46f8 100644
--- a/nullability/pointer_nullability_analysis_test.cc
+++ b/nullability/pointer_nullability_analysis_test.cc
@@ -20,7 +20,6 @@
#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
#include "clang/Basic/LLVM.h"
#include "clang/Testing/TestAST.h"
-#include "llvm/Support/Error.h"
#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
namespace clang::tidy::nullability {
@@ -72,18 +71,20 @@
dyn_cast_or_null<dataflow::PointerValue>(ExitState.Env.getReturnValue());
ASSERT_NE(Ret, nullptr);
auto State = getPointerNullState(*Ret);
+ ASSERT_NE(State.FromNullable, nullptr);
+ ASSERT_NE(State.IsNull, nullptr);
// The param nullability hasn't been fixed.
EXPECT_EQ(std::nullopt, evaluate(PN.isNonnull(A), ExitState.Env));
EXPECT_EQ(std::nullopt, evaluate(PN.isNullable(A), ExitState.Env));
// Nor has the the nullability of the returned pointer.
- EXPECT_EQ(std::nullopt, evaluate(State.FromNullable, ExitState.Env));
- EXPECT_EQ(std::nullopt, evaluate(State.IsNull, ExitState.Env));
+ EXPECT_EQ(std::nullopt, evaluate(*State.FromNullable, ExitState.Env));
+ EXPECT_EQ(std::nullopt, evaluate(*State.IsNull, ExitState.Env));
// However, the two are linked as expected.
EXPECT_EQ(true,
- evaluate(A.makeImplies(PN.isNonnull(A), A.makeNot(State.IsNull)),
+ evaluate(A.makeImplies(PN.isNonnull(A), A.makeNot(*State.IsNull)),
ExitState.Env));
- EXPECT_EQ(true, evaluate(A.makeEquals(PN.isNullable(A), State.FromNullable),
+ EXPECT_EQ(true, evaluate(A.makeEquals(PN.isNullable(A), *State.FromNullable),
ExitState.Env));
}
diff --git a/nullability/pointer_nullability_test.cc b/nullability/pointer_nullability_test.cc
index 3e54939..9060082 100644
--- a/nullability/pointer_nullability_test.cc
+++ b/nullability/pointer_nullability_test.cc
@@ -47,22 +47,26 @@
{
auto &NullableButNotNull = makePointer(NullabilityKind::Nullable);
EXPECT_TRUE(isNullable(NullableButNotNull, Env));
- Env.addToFlowCondition(
- A.makeNot(getPointerNullState(NullableButNotNull).IsNull));
+ auto *IsNull = getPointerNullState(NullableButNotNull).IsNull;
+ ASSERT_NE(IsNull, nullptr);
+ Env.addToFlowCondition(A.makeNot(*IsNull));
EXPECT_FALSE(isNullable(NullableButNotNull, Env));
}
{
auto &NullableAndNull = makePointer(NullabilityKind::Nullable);
- Env.addToFlowCondition(getPointerNullState(NullableAndNull).IsNull);
+ auto *IsNull = getPointerNullState(NullableAndNull).IsNull;
+ ASSERT_NE(IsNull, nullptr);
+ Env.addToFlowCondition(*IsNull);
EXPECT_TRUE(isNullable(NullableAndNull, Env));
}
{
auto &NonnullAndNotNull = makePointer(NullabilityKind::NonNull);
EXPECT_FALSE(isNullable(NonnullAndNotNull, Env));
- Env.addToFlowCondition(
- A.makeNot(getPointerNullState(NonnullAndNotNull).IsNull));
+ auto *IsNull = getPointerNullState(NonnullAndNotNull).IsNull;
+ ASSERT_NE(IsNull, nullptr);
+ Env.addToFlowCondition(A.makeNot(*IsNull));
EXPECT_FALSE(isNullable(NonnullAndNotNull, Env));
}
@@ -71,7 +75,9 @@
// but is dynamically discovered to be definitely null, we still don't
// consider it nullable.
auto &NonnullAndNull = makePointer(NullabilityKind::NonNull);
- Env.addToFlowCondition(getPointerNullState(NonnullAndNull).IsNull);
+ auto *IsNull = getPointerNullState(NonnullAndNull).IsNull;
+ ASSERT_NE(IsNull, nullptr);
+ Env.addToFlowCondition(*IsNull);
EXPECT_FALSE(isNullable(NonnullAndNull, Env));
}
}
@@ -79,16 +85,35 @@
TEST_F(NullabilityPropertiesTest, IsNullableAdditionalConstraints) {
auto &P = makePointer(NullabilityKind::Nullable);
EXPECT_TRUE(isNullable(P, Env));
- auto *NotNull = &DACtx.arena().makeNot(getPointerNullState(P).IsNull);
+ auto *IsNull = getPointerNullState(P).IsNull;
+ ASSERT_NE(IsNull, nullptr);
+ auto *NotNull = &DACtx.arena().makeNot(*IsNull);
EXPECT_FALSE(isNullable(P, Env, NotNull));
}
TEST_F(NullabilityPropertiesTest, GetNullabilityAdditionalConstraints) {
auto &P = makePointer(NullabilityKind::Nullable);
EXPECT_EQ(getNullability(P, Env), NullabilityKind::Nullable);
- auto *NotNull = &DACtx.arena().makeNot(getPointerNullState(P).IsNull);
+ auto *IsNull = getPointerNullState(P).IsNull;
+ ASSERT_NE(IsNull, nullptr);
+ auto *NotNull = &DACtx.arena().makeNot(*IsNull);
EXPECT_EQ(getNullability(P, Env, NotNull), NullabilityKind::NonNull);
}
+TEST_F(NullabilityPropertiesTest, InitNullabilityPropertiesWithTop) {
+ auto &P = Env.create<dataflow::PointerValue>(
+ DACtx.createStorageLocation(QualType()));
+
+ initPointerNullState(P, DACtx);
+ ASSERT_NE(getPointerNullState(P).FromNullable, nullptr);
+ ASSERT_NE(getPointerNullState(P).IsNull, nullptr);
+
+ forgetFromNullable(P, DACtx);
+ ASSERT_EQ(getPointerNullState(P).FromNullable, nullptr);
+
+ forgetIsNull(P, DACtx);
+ ASSERT_EQ(getPointerNullState(P).IsNull, nullptr);
+}
+
} // namespace
} // namespace clang::tidy::nullability