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?";
+ }
});
}