Update pointer nullability when initialized in function call
PiperOrigin-RevId: 563260055
Change-Id: I91858156dfa3f7daa14f4c1ff7e36478e0715e86
diff --git a/nullability/pointer_nullability_analysis.cc b/nullability/pointer_nullability_analysis.cc
index 58fdb04..25fc9ea 100644
--- a/nullability/pointer_nullability_analysis.cc
+++ b/nullability/pointer_nullability_analysis.cc
@@ -388,6 +388,49 @@
State.Env.setValue(*CastExpr, State.Env.makeNot(PointerNull));
}
+void initializeOutputParameter(const Expr *Arg, dataflow::Environment &Env,
+ QualType ParamTy) {
+ // When a function has an "output parameter" - a non-const pointer or
+ // reference to a pointer of unknown nullability - assume that the function
+ // may set the pointer to non-null.
+ //
+ // For example, in the following code sequence we assume that the function may
+ // modify the pointer in a way that makes a subsequent dereference safe:
+ //
+ // void maybeModify(int ** _Nonnull);
+ //
+ // int *p = nullptr;
+ // initializePointer(&p);
+ // *p; // safe
+
+ if (ParamTy.isNull()) return;
+ if (ParamTy->getPointeeType().isNull()) return;
+ if (!isSupportedPointerType(ParamTy->getPointeeType())) return;
+ if (ParamTy->getPointeeType().isConstQualified()) return;
+
+ // TODO(b/298200521): This should extend support to annotations that suggest
+ // different in/out state
+ TypeNullability InnerNullability =
+ getNullabilityAnnotationsFromType(ParamTy->getPointeeType());
+ if (InnerNullability.front().concrete() != NullabilityKind::Unspecified)
+ return;
+
+ StorageLocation *Loc = nullptr;
+ if (ParamTy->isPointerType()) {
+ if (PointerValue *OuterPointer = getPointerValueFromExpr(Arg, Env))
+ Loc = &OuterPointer->getPointeeLoc();
+ } else if (ParamTy->isReferenceType()) {
+ Loc = Env.getStorageLocation(*Arg);
+ }
+ if (Loc == nullptr) return;
+
+ auto *InnerPointer =
+ cast<PointerValue>(Env.createValue(ParamTy->getPointeeType()));
+ initUnknownPointer(*InnerPointer, Env);
+
+ Env.setValue(*Loc, *InnerPointer);
+}
+
void transferFlowSensitiveCallExpr(
const CallExpr *CallExpr, const MatchFinder::MatchResult &Result,
TransferState<PointerNullabilityLattice> &State) {
@@ -426,6 +469,20 @@
// be a prvalue.
State.Env.setValue(*CallExpr, *PointerVal);
}
+
+ // Make output parameters (with unknown nullability) initialized to unknown.
+ const auto *FuncDecl = CallExpr->getDirectCallee();
+ if (!FuncDecl) return;
+ if (FuncDecl->getNumParams() != CallExpr->getNumArgs()) return;
+ if (auto *II = FuncDecl->getDeclName().getAsIdentifierInfo();
+ II && II->isStr("__assert_nullability")) {
+ return;
+ }
+ for (unsigned i = 0; i < CallExpr->getNumArgs(); ++i) {
+ const auto *Arg = CallExpr->getArg(i);
+ initializeOutputParameter(Arg, State.Env,
+ FuncDecl->getParamDecl(i)->getType());
+ }
}
// If nullability for the decl D has been overridden, patch N to reflect it.
diff --git a/nullability/test/function_calls.cc b/nullability/test/function_calls.cc
index 51b0b76..9935cfb 100644
--- a/nullability/test/function_calls.cc
+++ b/nullability/test/function_calls.cc
@@ -104,6 +104,150 @@
)cc"));
}
+TEST(PointerNullabilityTest, OutputParameterBasic) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void maybeModifyPtr(int** p);
+ void target() {
+ int* p = nullptr;
+ maybeModifyPtr(&p);
+ *p;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, OutputParameterReference) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void maybeModifyPtr(int*& r);
+ void target() {
+ int* p = nullptr;
+ maybeModifyPtr(p);
+ *p;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, OutputParameterReferenceConst) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void pointerNotModified(int* const& r);
+ void target() {
+ int* p = nullptr;
+ pointerNotModified(p);
+ *p; // [[unsafe]]
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, OutputParameterReferencePointerToPointer) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void maybeModifyPtr(int**& r);
+ void target() {
+ int** pp = nullptr;
+ maybeModifyPtr(pp);
+ *pp;
+ **pp;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, OutputParameterConst) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void pointerNotModified(int* const* p);
+ void target(int* _Nullable p) {
+ pointerNotModified(&p);
+ *p; // [[unsafe]]
+ }
+ )cc"));
+
+ // The only const qualifier that should be considered is on the inner
+ // pointer, otherwise this pattern should be considered safe.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void maybeModifyPtr(const int** const p);
+ void target() {
+ const int* p = nullptr;
+ maybeModifyPtr(&p);
+ *p;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, OutputParameterNonnull) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void pointerNotModified(int* _Nonnull* p);
+ void target(int* _Nonnull p) {
+ pointerNotModified(&p);
+ *p;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, OutputParameterCheckedNullable) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void maybeModify(int* _Nullable* p);
+ void target(int* _Nullable p) {
+ if (!p) return;
+ maybeModify(&p);
+ *p; // false negative: this dereference is actually unsafe!
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, OutputParameterNullable) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void maybeModifyPtr(int* _Nullable* p);
+ void target() {
+ int* p = nullptr;
+ maybeModifyPtr(&p);
+ *p; // [[unsafe]]
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, OutputParameterConditional) {
+ // This tests that flow sensitivity is preserved, to catch for example if the
+ // underlying pointer was always set to Nonnull once it's passed as an
+ // output parameter.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void maybeModifyPtr(int** p);
+ void target(int* _Nullable j, bool b) {
+ if (b) {
+ maybeModifyPtr(&j);
+ }
+ if (b) {
+ *j;
+ }
+ if (!b) {
+ *j; // [[unsafe]]
+ }
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, OutputParameterWithoutAmpersandOperator) {
+ // This tests that the call to maybeModifyPtr works as expected if the param
+ // passed in doesn't directly use the & operator
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void maybeModifyPtr(int** p);
+ void target(int* _Nullable p) {
+ auto pp = &p;
+ maybeModifyPtr(pp);
+ *p;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, OutputParameterTemplate) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T>
+ struct S {
+ void maybeModify(T& ref);
+ };
+ void target(S<int*> s, int* _Nullable p) {
+ s.maybeModify(p);
+ *p;
+ }
+ )cc"));
+}
+
TEST(PointerNullabilityTest, CallExprParamAssignment) {
// free function with single param
EXPECT_TRUE(checkDiagnostics(R"cc(
@@ -233,7 +377,7 @@
*ptr_nullable; // [[unsafe]]
takeUnannotatedRef(ptr_nullable);
- *ptr_nullable; // [[unsafe]]
+ *ptr_nullable;
}
)cc"));