[nullability] Model pointer arithmetic in shared analysis.
This introduces two false positives in the pointer_arithmetic_diagnosis test
that will be resolved by a followup patch (that adds diagnosis requiring the
operands of increment and decrement operators to be nonnull).
PiperOrigin-RevId: 670848600
Change-Id: I04e8076c8098b439cda4495b3b962e58d47c2d43
diff --git a/nullability/pointer_nullability_analysis.cc b/nullability/pointer_nullability_analysis.cc
index 4c042cf..efd6aae 100644
--- a/nullability/pointer_nullability_analysis.cc
+++ b/nullability/pointer_nullability_analysis.cc
@@ -382,6 +382,37 @@
}
}
+void transferValue_PointerIncOrDec(
+ absl::Nonnull<const UnaryOperator *> UnaryOp,
+ const MatchFinder::MatchResult &,
+ TransferState<PointerNullabilityLattice> &State) {
+ // The framework propagates the subexpression's value (in the case of post-
+ // increment) or storage location (in the case of pre-increment). We just
+ // need to create a new nonnull value.
+ if (StorageLocation *Loc =
+ State.Env.getStorageLocation(*UnaryOp->getSubExpr())) {
+ auto *Val = cast<PointerValue>(State.Env.createValue(Loc->getType()));
+ initPointerNullState(*Val, State.Env.getDataflowAnalysisContext(),
+ NullabilityKind::NonNull);
+ State.Env.setValue(*Loc, *Val);
+ }
+}
+
+void transferValue_PointerAddOrSubAssign(
+ absl::Nonnull<const BinaryOperator *> BinaryOp,
+ const MatchFinder::MatchResult &,
+ TransferState<PointerNullabilityLattice> &State) {
+ // The framework propagates the storage location of the LHS, so we just need
+ // to create a new nonnull value.
+ if (StorageLocation *Loc =
+ State.Env.getStorageLocation(*BinaryOp->getLHS())) {
+ auto *Val = cast<PointerValue>(State.Env.createValue(Loc->getType()));
+ initPointerNullState(*Val, State.Env.getDataflowAnalysisContext(),
+ NullabilityKind::NonNull);
+ State.Env.setValue(*Loc, *Val);
+ }
+}
+
void transferValue_NotNullPointer(
absl::Nonnull<const Expr *> NotNullPointer,
const MatchFinder::MatchResult &,
@@ -1479,10 +1510,19 @@
.drop_front()
.vec();
+ case UO_PreInc:
+ case UO_PreDec: {
+ TypeNullability SubNullability =
+ getNullabilityForChild(UO->getSubExpr(), State);
+ if (!isSupportedRawPointerType(UO->getSubExpr()->getType()))
+ return SubNullability;
+ assert(!SubNullability.empty());
+ SubNullability[0] = NullabilityKind::NonNull;
+ return SubNullability;
+ }
+
case UO_PostInc:
case UO_PostDec:
- case UO_PreInc:
- case UO_PreDec:
case UO_Plus:
case UO_Minus:
case UO_Not:
@@ -1512,6 +1552,23 @@
case BO_Assign:
case BO_Comma:
return getNullabilityForChild(BO->getRHS(), State);
+ case BO_Add:
+ case BO_Sub:
+ // The `+=` and `-=` operators will always take the "LHS" branch below but
+ // can otherwise be handled using the same code as `+` and `-`, so we do.
+ case BO_AddAssign:
+ case BO_SubAssign: {
+ TypeNullability PtrNullability;
+ if (isSupportedRawPointerType(BO->getLHS()->getType()))
+ PtrNullability = getNullabilityForChild(BO->getLHS(), State);
+ else if (isSupportedRawPointerType(BO->getRHS()->getType()))
+ PtrNullability = getNullabilityForChild(BO->getRHS(), State);
+ else
+ return unspecifiedNullability(BO);
+ assert(!PtrNullability.empty());
+ PtrNullability[0] = NullabilityKind::NonNull;
+ return PtrNullability;
+ }
default:
// No other built-in binary operators can be pointer-valued
return unspecifiedNullability(BO);
@@ -1605,6 +1662,10 @@
.CaseOfCFGStmt<Expr>(isNullPointerLiteral(), transferValue_NullPointer)
.CaseOfCFGStmt<CXXScalarValueInitExpr>(isRawPointerValueInit(),
transferValue_NullPointer)
+ .CaseOfCFGStmt<UnaryOperator>(isPointerIncOrDec(),
+ transferValue_PointerIncOrDec)
+ .CaseOfCFGStmt<BinaryOperator>(isPointerAddOrSubAssign(),
+ transferValue_PointerAddOrSubAssign)
.CaseOfCFGStmt<CXXConstructExpr>(isSmartPointerConstructor(),
transferValue_SmartPointerConstructor)
.CaseOfCFGStmt<CXXOperatorCallExpr>(isSmartPointerOperatorCall("="),
diff --git a/nullability/pointer_nullability_matchers.cc b/nullability/pointer_nullability_matchers.cc
index 493799a..01ea2ce 100644
--- a/nullability/pointer_nullability_matchers.cc
+++ b/nullability/pointer_nullability_matchers.cc
@@ -55,6 +55,7 @@
using ast_matchers::isArrow;
using ast_matchers::isConst;
using ast_matchers::isInStdNamespace;
+using ast_matchers::isInteger;
using ast_matchers::isMemberInitializer;
using ast_matchers::memberExpr;
using ast_matchers::ofClass;
@@ -85,6 +86,14 @@
return binaryOperator(hasAnyOperatorName("!=", "=="),
hasOperands(isPointerExpr(), isPointerExpr()));
}
+Matcher<Stmt> isPointerIncOrDec() {
+ return unaryOperator(hasAnyOperatorName("++", "--"),
+ hasUnaryOperand(isPointerExpr()));
+}
+Matcher<Stmt> isPointerAddOrSubAssign() {
+ return binaryOperator(hasAnyOperatorName("+=", "-="),
+ hasOperands(isPointerExpr(), hasType(isInteger())));
+}
Matcher<Stmt> isImplicitCastPointerToBool() {
return implicitCastExpr(hasCastKind(CK_PointerToBoolean));
}
diff --git a/nullability/pointer_nullability_matchers.h b/nullability/pointer_nullability_matchers.h
index 2969faf..396c93f 100644
--- a/nullability/pointer_nullability_matchers.h
+++ b/nullability/pointer_nullability_matchers.h
@@ -44,6 +44,8 @@
ast_matchers::internal::Matcher<Stmt> isPointerDereference();
ast_matchers::internal::Matcher<Stmt> isPointerSubscript();
ast_matchers::internal::Matcher<Stmt> isPointerCheckBinOp();
+ast_matchers::internal::Matcher<Stmt> isPointerIncOrDec();
+ast_matchers::internal::Matcher<Stmt> isPointerAddOrSubAssign();
ast_matchers::internal::Matcher<Stmt> isImplicitCastPointerToBool();
ast_matchers::internal::Matcher<Stmt> isPointerReturn();
ast_matchers::internal::Matcher<CXXCtorInitializer> isCtorMemberInitializer();
diff --git a/nullability/test/BUILD b/nullability/test/BUILD
index 9585290..4dbc297 100644
--- a/nullability/test/BUILD
+++ b/nullability/test/BUILD
@@ -268,6 +268,11 @@
],
)
+nullability_test(
+ name = "pointer_arithmetic",
+ srcs = ["pointer_arithmetic.cc"],
+)
+
cc_test(
name = "pointer_arithmetic_diagnosis",
srcs = ["pointer_arithmetic_diagnosis.cc"],
diff --git a/nullability/test/pointer_arithmetic.cc b/nullability/test/pointer_arithmetic.cc
new file mode 100644
index 0000000..3780c10
--- /dev/null
+++ b/nullability/test/pointer_arithmetic.cc
@@ -0,0 +1,131 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for pointer arithmetic.
+
+// Note that, for example, the increment and decrement operators do not cause
+// their operand to become nonnull, even though we know that they may only be
+// applied to nonnull pointers. This is consistent with our treatment of other
+// operators, such as `*` and `->`; these also do not cause their operand to
+// become nonnull.
+
+#include "nullability_test.h"
+
+// This test intentionally contains violations of `-Wunsafe-buffer-usage`, so
+// turn it off.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
+
+TEST void preIncrementType(Nonnull<int *> NonnullParam,
+ Nullable<int *> NullableParam, int *UnknownParam) {
+ type<Nonnull<int *>>(++NonnullParam);
+ type<Nonnull<int *>>(++NullableParam);
+ type<Nonnull<int *>>(++UnknownParam);
+}
+
+TEST void preIncrementValue(Nonnull<int *> NonnullParam,
+ Nullable<int *> NullableParam, int *UnknownParam) {
+ int *NewVal = ++NonnullParam;
+ provable(NonnullParam == NewVal);
+ nonnull(NonnullParam);
+
+ NewVal = ++NullableParam;
+ provable(NullableParam == NewVal);
+ nonnull(NullableParam);
+
+ NewVal = ++UnknownParam;
+ provable(UnknownParam == NewVal);
+ nonnull(UnknownParam);
+}
+
+TEST void postIncrementType(Nonnull<int *> NonnullParam,
+ Nullable<int *> NullableParam, int *UnknownParam) {
+ type<Nonnull<int *>>(NonnullParam++);
+ type<Nullable<int *>>(NullableParam++);
+ type<int *>(UnknownParam++);
+}
+
+TEST void postIncrementValue(Nonnull<int *> NonnullParam,
+ Nullable<int *> NullableParam, int *UnknownParam) {
+ int *OldVal = NonnullParam;
+ provable(NonnullParam++ == OldVal);
+ nonnull(NonnullParam);
+
+ OldVal = NullableParam;
+ provable(NullableParam++ == OldVal);
+ nonnull(NullableParam);
+
+ OldVal = UnknownParam;
+ provable(UnknownParam++ == OldVal);
+ nonnull(UnknownParam);
+}
+
+TEST void add(Nonnull<int *> NonnullParam, Nullable<int *> NullableParam,
+ int *UnknownParam, int I) {
+ type<Nonnull<int *>>(NonnullParam + I);
+ type<Nonnull<int *>>(I + NonnullParam);
+
+ type<Nonnull<int *>>(NullableParam + I);
+ type<Nonnull<int *>>(I + NullableParam);
+
+ type<Nonnull<int *>>(UnknownParam + I);
+ type<Nonnull<int *>>(I + UnknownParam);
+}
+
+TEST void subtract(Nonnull<int *> NonnullParam, Nullable<int *> NullableParam,
+ int *UnknownParam, int I) {
+ type<Nonnull<int *>>(NonnullParam - I);
+ type<Nonnull<int *>>(NullableParam - I);
+ type<Nonnull<int *>>(UnknownParam - I);
+}
+
+TEST void addAssignType(Nonnull<int *> NonnullParam,
+ Nullable<int *> NullableParam, int *UnknownParam,
+ int I) {
+ type<Nonnull<int *>>(NonnullParam += I);
+ type<Nonnull<int *>>(NullableParam += I);
+ type<Nonnull<int *>>(UnknownParam += I);
+}
+
+TEST void addAssignValue(Nonnull<int *> NonnullParam,
+ Nullable<int *> NullableParam, int *UnknownParam,
+ int I) {
+ int *NewVal = (NonnullParam += I);
+ provable(NonnullParam == NewVal);
+ nonnull(NonnullParam);
+
+ NewVal = (NullableParam += I);
+ provable(NullableParam == NewVal);
+ nonnull(NullableParam);
+
+ NewVal = (UnknownParam += I);
+ provable(UnknownParam == NewVal);
+ nonnull(UnknownParam);
+}
+
+TEST void subtractAssignType(Nonnull<int *> NonnullParam,
+ Nullable<int *> NullableParam, int *UnknownParam,
+ int I) {
+ type<Nonnull<int *>>(NonnullParam -= I);
+ type<Nonnull<int *>>(NullableParam -= I);
+ type<Nonnull<int *>>(UnknownParam -= I);
+}
+
+TEST void subtractAssignValue(Nonnull<int *> NonnullParam,
+ Nullable<int *> NullableParam, int *UnknownParam,
+ int I) {
+ int *NewVal = (NonnullParam -= I);
+ provable(NonnullParam == NewVal);
+ nonnull(NonnullParam);
+
+ NewVal = (NullableParam -= I);
+ provable(NullableParam == NewVal);
+ nonnull(NullableParam);
+
+ NewVal = (UnknownParam -= I);
+ provable(UnknownParam == NewVal);
+ nonnull(UnknownParam);
+}
+
+#pragma clang diagnostic pop
diff --git a/nullability/test/pointer_arithmetic_diagnosis.cc b/nullability/test/pointer_arithmetic_diagnosis.cc
index 1fa81a4..86131d0 100644
--- a/nullability/test/pointer_arithmetic_diagnosis.cc
+++ b/nullability/test/pointer_arithmetic_diagnosis.cc
@@ -25,13 +25,19 @@
*nullable++; // [[unsafe]]
nullable = orig;
- *++nullable; // [[unsafe]]
+ // TODO(mboehme): False negative. The increment operation is creating a
+ // new nonnull value, so the dereference isn't considered unsafe, but the
+ // increment of a nullable pointer should itself be considered unsafe.
+ *++nullable;
nullable = orig;
*nullable--; // [[unsafe]]
nullable = orig;
- *--nullable; // [[unsafe]]
+ // TODO(mboehme): False negative. The decrement operation is creating a
+ // new nonnull value, so the dereference isn't considered unsafe, but the
+ // decrement of a nullable pointer should itself be considered unsafe.
+ *--nullable;
nullable = orig;
// On a nullable pointer, the pointer arithmetic itself should already be