Check that assignment to parameters in function calls are null-safe.
For example, it is unsafe to pass a nullable value as a nonnull parameter.
```
void foo(int * _Nonnull x);
int * _Nullable x = ...;
foo(x); // Unsafe
```
PiperOrigin-RevId: 468782730
diff --git a/nullability_verification/BUILD b/nullability_verification/BUILD
index d98a9b1..9ff987c 100644
--- a/nullability_verification/BUILD
+++ b/nullability_verification/BUILD
@@ -36,6 +36,7 @@
"@llvm-project//clang:analysis",
"@llvm-project//clang:ast",
"@llvm-project//clang:ast_matchers",
+ "@llvm-project//clang:basic",
"@llvm-project//llvm:Support",
],
)
diff --git a/nullability_verification/pointer_nullability.cc b/nullability_verification/pointer_nullability.cc
index 09a0f1f..c53dfd9 100644
--- a/nullability_verification/pointer_nullability.cc
+++ b/nullability_verification/pointer_nullability.cc
@@ -52,6 +52,13 @@
initPointerBoolProperty(PointerVal, kNotNull, NotNullConstraint, Env);
}
+bool isNullable(const PointerValue& PointerVal, const Environment& Env) {
+ auto [PointerKnown, PointerNotNull] = getPointerNullState(PointerVal, Env);
+ auto& PointerNotKnownNull =
+ Env.makeNot(Env.makeAnd(PointerKnown, Env.makeNot(PointerNotNull)));
+ return !Env.flowConditionImplies(PointerNotKnownNull);
+}
+
} // namespace nullability
} // namespace tidy
} // namespace clang
diff --git a/nullability_verification/pointer_nullability.h b/nullability_verification/pointer_nullability.h
index c8d69e9..01c9c75 100644
--- a/nullability_verification/pointer_nullability.h
+++ b/nullability_verification/pointer_nullability.h
@@ -80,6 +80,10 @@
/*KnownConstraint=*/&Env.getBoolLiteralValue(false));
}
+/// Returns true if there is evidence that `PointerVal` may hold a nullptr.
+bool isNullable(const dataflow::PointerValue& PointerVal,
+ const dataflow::Environment& Env);
+
} // namespace nullability
} // namespace tidy
} // namespace clang
diff --git a/nullability_verification/pointer_nullability_diagnosis.cc b/nullability_verification/pointer_nullability_diagnosis.cc
index 82167f9..7387eef 100644
--- a/nullability_verification/pointer_nullability_diagnosis.cc
+++ b/nullability_verification/pointer_nullability_diagnosis.cc
@@ -8,6 +8,7 @@
#include "nullability_verification/pointer_nullability_matchers.h"
#include "clang/AST/Expr.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Basic/Specifiers.h"
namespace clang {
namespace tidy {
@@ -22,12 +23,7 @@
const Expr* PointerExpr,
const Environment& Env) {
auto* PointerVal = getPointerValueFromExpr(PointerExpr, Env);
- if (!PointerVal) return PointerAccessExpr;
-
- auto [PointerKnown, PointerNotNull] = getPointerNullState(*PointerVal, Env);
- auto& PointerNotKnownNull =
- Env.makeNot(Env.makeAnd(PointerKnown, Env.makeNot(PointerNotNull)));
- if (!Env.flowConditionImplies(PointerNotKnownNull)) {
+ if (!PointerVal || isNullable(*PointerVal, Env)) {
return PointerAccessExpr;
}
return llvm::None;
@@ -45,6 +41,38 @@
return diagnosePointerAccess(MemberExpr, MemberExpr->getBase(), Env);
}
+// TODO(b/233582219): Handle call expressions whose callee is not a decl (e.g.
+// a function returned from another function), or when the callee cannot be
+// interpreted as a function type (e.g. a pointer to a function pointer).
+llvm::Optional<const Stmt*> diagnoseCallExpr(
+ const CallExpr* CE, const MatchFinder::MatchResult& Result,
+ const Environment& Env) {
+ auto* Callee = CE->getCalleeDecl();
+ if (!Callee) return llvm::None;
+
+ auto* CalleeType = Callee->getFunctionType();
+ if (!CalleeType) return llvm::None;
+
+ auto ParamTypes = CalleeType->getAs<FunctionProtoType>()->getParamTypes();
+
+ for (unsigned int I = 0; I < ParamTypes.size(); ++I) {
+ auto ParamType = ParamTypes[I];
+ if (!ParamType->isAnyPointerType() ||
+ ParamType->getNullability(*Result.Context)
+ .value_or(NullabilityKind::Unspecified) !=
+ NullabilityKind::NonNull) {
+ continue;
+ }
+ auto* Arg = CE->getArg(I);
+ auto* PointerVal = getPointerValueFromExpr(Arg, Env);
+ if (!PointerVal || isNullable(*PointerVal, Env)) {
+ return llvm::Optional<const Stmt*>(CE);
+ }
+ }
+
+ return llvm::None;
+}
+
auto buildDiagnoser() {
return dataflow::MatchSwitchBuilder<const Environment,
llvm::Optional<const Stmt*>>()
@@ -52,6 +80,8 @@
.CaseOf<UnaryOperator>(isPointerDereference(), diagnoseDereference)
// (->)
.CaseOf<MemberExpr>(isPointerArrow(), diagnoseArrow)
+ // Check compatibility of parameter assignments
+ .CaseOf<CallExpr>(isCallExpr(), diagnoseCallExpr)
.Build();
}
diff --git a/nullability_verification/pointer_nullability_verification_test.cc b/nullability_verification/pointer_nullability_verification_test.cc
index c4504f0..c28c4b6 100644
--- a/nullability_verification/pointer_nullability_verification_test.cc
+++ b/nullability_verification/pointer_nullability_verification_test.cc
@@ -1389,6 +1389,97 @@
)");
}
+TEST(PointerNullabilityTest, CallExprParamAssignment) {
+ // free function with single param
+ checkDiagnostics(R"(
+ void takeNonnull(int * _Nonnull);
+ void takeNullable(int * _Nullable);
+ void takeUnannotated(int *);
+ void target(int * _Nonnull ptr_nonnull,
+ int * _Nullable ptr_nullable,
+ int *ptr_unannotated) {
+ takeNonnull(nullptr); // [[unsafe]]
+ takeNonnull(ptr_nonnull);
+ takeNonnull(ptr_nullable); // [[unsafe]]
+ takeNonnull(ptr_unannotated);
+
+ takeNullable(nullptr);
+ takeNullable(ptr_nonnull);
+ takeNullable(ptr_nullable);
+ takeNullable(ptr_unannotated);
+
+ takeUnannotated(nullptr);
+ takeUnannotated(ptr_nonnull);
+ takeUnannotated(ptr_nullable);
+ takeUnannotated(ptr_unannotated);
+ }
+ )");
+
+ // free function with multiple params of mixed nullability
+ checkDiagnostics(R"(
+ void takeMixed(int *, int * _Nullable, int * _Nonnull);
+ void target() {
+ takeMixed(nullptr, nullptr, nullptr); // [[unsafe]]
+ }
+ )");
+
+ // member function
+ checkDiagnostics(R"(
+ struct Foo {
+ void takeNonnull(int * _Nonnull);
+ void takeNullable(int * _Nullable);
+ void takeUnannotated(int *);
+ };
+ void target(Foo foo) {
+ foo.takeNonnull(nullptr); // [[unsafe]]
+ foo.takeNullable(nullptr);
+ foo.takeUnannotated(nullptr);
+ }
+ )");
+
+ // function pointer
+ checkDiagnostics(R"(
+ void target(void (*takeNonnull)(int * _Nonnull),
+ void (*takeNullable)(int * _Nullable),
+ void (*takeUnannotated)(int *)) {
+ takeNonnull(nullptr); // [[unsafe]]
+ takeNullable(nullptr);
+ takeUnannotated(nullptr);
+ }
+ )");
+
+ // pointer to function pointer
+ //
+ // TODO(b/233582219): Fix false negative. Implement support for retrieving
+ // parameter types from a pointer to function pointer.
+ checkDiagnostics(R"(
+ void target(void (**takeNonnull)(int * _Nonnull),
+ void (**takeNullable)(int * _Nullable),
+ void (**takeUnannotated)(int *)) {
+ (*takeNonnull)(nullptr); // false-negative
+ (*takeNullable)(nullptr);
+ (*takeUnannotated)(nullptr);
+ }
+ )");
+
+ // function returned from function
+ //
+ // TODO(b/233582219): Fix false negative. Implement support for retrieving
+ // parameter types for functions returned by another function.
+ checkDiagnostics(R"(
+ typedef void (*takeNonnullF)(int * _Nonnull);
+ typedef void (*takeNullableF)(int * _Nullable);
+ typedef void (*takeUnannotatedF)(int *);
+ void target(takeNonnullF (*takeNonnull)(),
+ takeNullableF (*takeNullable)(),
+ takeUnannotatedF (*takeUnannotated)()) {
+ (*takeNonnull)()(nullptr); // false-negative
+ (*takeNullable)()(nullptr);
+ (*takeUnannotated)()(nullptr);
+ }
+ )");
+}
+
} // namespace
} // namespace nullability
} // namespace tidy