Reland "Extend non-flow-sensitive transfer for CastExpr"
This used to crash when casting nullptr to a type alias, due to a violated invariant about the nullability vector size. This was fixed by "Handle aliases (and all sugar) in type => nullability vector computation".
Added a test for this case.
PiperOrigin-RevId: 524833947
diff --git a/nullability_verification/pointer_nullability_analysis.cc b/nullability_verification/pointer_nullability_analysis.cc
index 2d095a5..4bde0a2 100644
--- a/nullability_verification/pointer_nullability_analysis.cc
+++ b/nullability_verification/pointer_nullability_analysis.cc
@@ -355,11 +355,133 @@
void transferNonFlowSensitiveCastExpr(
const CastExpr* CE, const MatchFinder::MatchResult& MR,
TransferState<PointerNullabilityLattice>& State) {
- // TODO: Handle casts where the input and output types can have different
- // numbers of pointers, and therefore different nullability. For example, a
- // reinterpret_cast from `int *` to int.
- computeNullability(CE, State, [&]() {
- return getNullabilityForChild(CE->getSubExpr(), State).vec();
+ computeNullability(CE, State, [&]() -> std::vector<NullabilityKind> {
+ // Most casts that can convert ~unrelated types drop nullability in general.
+ // As a special case, preserve nullability of outer pointer types.
+ // For example, int* p; (void*)p; is a BitCast, but preserves nullability.
+ auto PreserveTopLevelPointers = [&](std::vector<NullabilityKind> V) {
+ auto ArgNullability = getNullabilityForChild(CE->getSubExpr(), State);
+ const PointerType* ArgType = dyn_cast<PointerType>(
+ CE->getSubExpr()->getType().getCanonicalType().getTypePtr());
+ const PointerType* CastType =
+ dyn_cast<PointerType>(CE->getType().getCanonicalType().getTypePtr());
+ for (int I = 0; ArgType && CastType; ++I) {
+ V[I] = ArgNullability[I];
+ ArgType = dyn_cast<PointerType>(ArgType->getPointeeType().getTypePtr());
+ CastType =
+ dyn_cast<PointerType>(CastType->getPointeeType().getTypePtr());
+ }
+ return V;
+ };
+
+ switch (CE->getCastKind()) {
+ // Casts between unrelated types: we can't say anything about nullability.
+ case CK_LValueBitCast:
+ case CK_BitCast:
+ case CK_LValueToRValueBitCast:
+ return PreserveTopLevelPointers(unspecifiedNullability(CE));
+
+ // Casts between equivalent types.
+ case CK_LValueToRValue:
+ case CK_NoOp:
+ case CK_AtomicToNonAtomic:
+ case CK_NonAtomicToAtomic:
+ case CK_AddressSpaceConversion:
+ return getNullabilityForChild(CE->getSubExpr(), State).vec();
+
+ // Controlled conversions between types
+ // TODO: these should be doable somehow
+ case CK_BaseToDerived:
+ case CK_DerivedToBase:
+ case CK_UncheckedDerivedToBase:
+ return PreserveTopLevelPointers(unspecifiedNullability(CE));
+ case CK_UserDefinedConversion:
+ case CK_ConstructorConversion:
+ return unspecifiedNullability(CE);
+
+ case CK_Dynamic: {
+ auto Result = unspecifiedNullability(CE);
+ // A dynamic_cast to pointer is null if the runtime check fails.
+ if (isa<PointerType>(CE->getType().getCanonicalType()))
+ Result.front() = NullabilityKind::Nullable;
+ return Result;
+ }
+
+ // Primitive values have no nullability.
+ case CK_ToVoid:
+ case CK_MemberPointerToBoolean:
+ case CK_PointerToBoolean:
+ case CK_PointerToIntegral:
+ case CK_IntegralCast:
+ case CK_IntegralToBoolean:
+ case CK_IntegralToFloating:
+ case CK_FloatingToFixedPoint:
+ case CK_FixedPointToFloating:
+ case CK_FixedPointCast:
+ case CK_FixedPointToIntegral:
+ case CK_IntegralToFixedPoint:
+ case CK_FixedPointToBoolean:
+ case CK_FloatingToIntegral:
+ case CK_FloatingToBoolean:
+ case CK_BooleanToSignedIntegral:
+ case CK_FloatingCast:
+ case CK_FloatingRealToComplex:
+ case CK_FloatingComplexToReal:
+ case CK_FloatingComplexToBoolean:
+ case CK_FloatingComplexCast:
+ case CK_FloatingComplexToIntegralComplex:
+ case CK_IntegralRealToComplex:
+ case CK_IntegralComplexToReal:
+ case CK_IntegralComplexToBoolean:
+ case CK_IntegralComplexCast:
+ case CK_IntegralComplexToFloatingComplex:
+ return {};
+
+ // This can definitely be null!
+ case CK_NullToPointer: {
+ auto Nullability = getNullabilityAnnotationsFromType(CE->getType());
+ Nullability.front() = NullabilityKind::Nullable;
+ return Nullability;
+ }
+
+ // Pointers out of thin air, who knows?
+ case CK_IntegralToPointer:
+ return unspecifiedNullability(CE);
+
+ // Decayed objects are never null.
+ case CK_ArrayToPointerDecay:
+ case CK_FunctionToPointerDecay:
+ case CK_BuiltinFnToFnPtr:
+ return prepend(NullabilityKind::NonNull,
+ getNullabilityForChild(CE->getSubExpr(), State));
+
+ // TODO: what is our model of member pointers?
+ case CK_BaseToDerivedMemberPointer:
+ case CK_DerivedToBaseMemberPointer:
+ case CK_NullToMemberPointer:
+ case CK_ReinterpretMemberPointer:
+ case CK_ToUnion: // and unions?
+ return unspecifiedNullability(CE);
+
+ // TODO: Non-C/C++ constructs, do we care about these?
+ case CK_CPointerToObjCPointerCast:
+ case CK_ObjCObjectLValueCast:
+ case CK_MatrixCast:
+ case CK_VectorSplat:
+ case CK_BlockPointerToObjCPointerCast:
+ case CK_AnyPointerToBlockPointerCast:
+ case CK_ARCProduceObject:
+ case CK_ARCConsumeObject:
+ case CK_ARCReclaimReturnedObject:
+ case CK_ARCExtendBlockObject:
+ case CK_CopyAndAutoreleaseBlockObject:
+ case CK_ZeroToOCLOpaqueType:
+ case CK_IntToOCLSampler:
+ return unspecifiedNullability(CE);
+
+ case CK_Dependent:
+ CHECK(false) << "Shouldn't see dependent casts here?";
+ }
});
}
diff --git a/nullability_verification/test/casts.cc b/nullability_verification/test/casts.cc
index cd2b77f..4358fee 100644
--- a/nullability_verification/test/casts.cc
+++ b/nullability_verification/test/casts.cc
@@ -101,6 +101,138 @@
)cc"));
}
+// CK_Bitcast: Bitcasts preserve outer nullability
+TEST(PointerNullabilityTest, Bitcast) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <class X>
+ struct vector {};
+
+ void target() {
+ // Bitcasts preserve nullability.
+ __assert_nullability<NK_nullable>((void*)value<int* _Nullable>());
+ __assert_nullability<NK_nonnull>((void*)value<int* _Nonnull>());
+ __assert_nullability<NK_unspecified>((void*)value<int*>());
+ // Nullability of further outer pointer types is preserved in bitcasts.
+ __assert_nullability<NK_nullable, NK_nullable>(
+ (void**)value<int* _Nullable* _Nullable>());
+ __assert_nullability<NK_nonnull, NK_nonnull>(
+ (void**)value<int* _Nonnull* _Nonnull>());
+ __assert_nullability<NK_unspecified, NK_unspecified>((void**)value<int**>());
+ // But nullability of other inner types is dropped.
+ __assert_nullability<NK_nullable, NK_unspecified>(
+ (void**)value<vector<int* _Nullable>* _Nullable>());
+ __assert_nullability<NK_nonnull, NK_unspecified>(
+ (void**)value<vector<int* _Nonnull>* _Nonnull>());
+
+ __assert_nullability<NK_nonnull, NK_unspecified>(
+ (void**)value<int* _Nonnull>);
+ __assert_nullability<NK_nonnull>((void*)value<int* _Nonnull* _Nonnull>());
+ }
+ )cc"));
+}
+
+// CK_NoOp: No-op casts preserve deep nullability
+// TODO: fix false-positives from treating untracked values as unsafe.
+TEST(PointerNullabilityTest, NoOp) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <class X>
+ struct vector {};
+
+ void target() {
+ // No-op casts preserve deep nullability.
+ __assert_nullability // [[unsafe]] TODO: fix false positive
+ <NK_nullable, NK_nullable>(const_cast<vector<int>*>(
+ (vector<int>* const)value<vector<int* _Nullable>* _Nullable>()));
+ }
+ )cc"));
+}
+
+// Casts between types with inheritance - only simple cases handled.
+// TODO: fix false-positives from treating untracked values as unsafe.
+TEST(PointerNullabilityTest, Inheritance) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <class X>
+ struct base {
+ virtual void ensure_polymorphic();
+ };
+ template <class X>
+ struct derived : base<X> {};
+
+ void target() {
+ // CK_BaseToDerived: preserves outer nullability only.
+ // TODO: determine that derived's type param is actually nullable here.
+ __assert_nullability<NK_nullable, NK_unspecified>(
+ (derived<int *> *)value<base<int *_Nullable> *_Nullable>());
+ // CK_Dynamic: dynamic_cast returns a nullable pointer.
+ auto b = value<base<int *_Nonnull> *_Nonnull>();
+ __assert_nullability // [[unsafe]] TODO: fix false positive
+ <NK_nullable, NK_unspecified>(dynamic_cast<derived<int> *>(b));
+ // ... only if casting to a pointer!
+ auto c = value<base<int *>>();
+ __assert_nullability<NK_unspecified>(dynamic_cast<derived<int *> &>(c));
+ }
+ )cc"));
+}
+
+// User-defined conversions could do anything, use declared type.
+TEST(PointerNullabilityTest, UserDefinedConversions) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <class X>
+ struct BuildFromPointer {
+ BuildFromPointer(int*);
+ };
+
+ void target() {
+ // User-defined conversions could do anything.
+ // CK_ConstructorConversion
+ __assert_nullability<NK_unspecified>(
+ (BuildFromPointer<double*>)value<int* _Nonnull>());
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CastToNonPointer) {
+ // Casting to non-pointer types destroyes nullability.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ using I = __INTPTR_TYPE__;
+
+ // TODO: fix false-positives from treating untracked values as unsafe.
+ void target() {
+ // Casting away pointerness destroys nullability.
+ // CK_PointerToIntegral
+ __assert_nullability<>((I)value<int* _Nonnull>());
+ // CK_PointerToBoolean
+ __assert_nullability<>((bool)value<int* _Nonnull>());
+ // Casting them back does not recover it.
+ // CK_IntegralToPointer
+ __assert_nullability // [[unsafe]] TODO: fix false positive
+ <>((int*)(I)value<int* _Nonnull>());
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, TrivialNullability) {
+ // Casts with trivial nullability
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target() {
+ // Null is nullable!
+ __assert_nullability<NK_nullable>((int*)nullptr);
+
+ // Decayed objects are non-null.
+ int array[2];
+ __assert_nullability<NK_nonnull>((int*)array);
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CastNullToAlias) {
+ // This used to crash!
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ using P = int *;
+ P target() { return nullptr; }
+ )cc"));
+}
+
TEST(PointerNullabilityTest, CastExpression) {
// TODO: We currently do not warn on local variables
// whose annotations conflict with the initializer. Decide whether to do so,
diff --git a/nullability_verification/test/check_diagnostics.cc b/nullability_verification/test/check_diagnostics.cc
index 328e7ec..f387828 100644
--- a/nullability_verification/test/check_diagnostics.cc
+++ b/nullability_verification/test/check_diagnostics.cc
@@ -24,6 +24,9 @@
template <NullabilityKind... NK, typename T>
void __assert_nullability(const T&);
+
+ template <typename T>
+ T value();
)cc";
bool checkDiagnostics(llvm::StringRef SourceCode) {