| // 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 casts of types containing nullability annotations. |
| |
| #include "nullability/test/check_diagnostics.h" |
| #include "external/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h" |
| |
| namespace clang::tidy::nullability { |
| namespace { |
| |
| // The following examples involve unreachable code, in the eye of the analyzer, |
| // since `x` is `_Nonnull` (assumes no contract violations at runtime). |
| // NOTE: We don't emit diagnostics in unreachable code. The analyzer's |
| // environment will have unsatisfiable flow conditions, which allow the analyzer |
| // to prove anything, and is less reliable. |
| TEST(PointerNullabilityTest, NonNullPtrImplicitCastToBool) { |
| // x |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void target(int* _Nonnull x) { |
| *x; |
| if (x) { |
| *x; |
| } else { |
| *x; // unreachable |
| } |
| *x; |
| } |
| )cc")); |
| |
| // !x |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void target(int* _Nonnull x) { |
| *x; |
| if (!x) { |
| *x; // unreachable |
| } else { |
| *x; |
| } |
| *x; |
| } |
| )cc")); |
| } |
| |
| TEST(PointerNullabilityTest, NullablePtrImplicitCastToBool) { |
| // x |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void target(int* _Nullable x) { |
| *x; // [[unsafe]] |
| if (x) { |
| *x; |
| } else { |
| *x; // [[unsafe]] |
| } |
| *x; // [[unsafe]] |
| } |
| )cc")); |
| |
| // !x |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void target(int* _Nullable x) { |
| *x; // [[unsafe]] |
| if (!x) { |
| *x; // [[unsafe]] |
| } else { |
| *x; |
| } |
| *x; // [[unsafe]] |
| } |
| )cc")); |
| } |
| |
| // We don't promote an unknown pointer to nullable based on a null check. The |
| // pointer could still be non-null, but with a defensive/redundant check. Thus, |
| // we don't warn before the null check, or after the join point, in the |
| // following examples. |
| TEST(PointerNullabilityTest, UnknownPtrImplicitCastToBool) { |
| // x |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void target(int* x) { |
| *x; |
| if (x) { |
| *x; |
| } else { |
| *x; // [[unsafe]] |
| } |
| *x; |
| } |
| )cc")); |
| |
| // !x |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void target(int* x) { |
| *x; |
| if (!x) { |
| *x; // [[unsafe]] |
| } else { |
| *x; |
| } |
| *x; |
| } |
| )cc")); |
| } |
| |
| // This is a crash repro involving implicit casts to bool of nullptr_t values |
| // that have a PointerValue but no associated null state. |
| TEST(PointerNullabilityTest, NullptrTypeImplicitCastToBool) { |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| namespace std { |
| using nullptr_t = decltype(nullptr); |
| } |
| void target() { |
| std::nullptr_t A = nullptr; |
| // initialization of B must contain an implicit cast to pointer to trigger |
| // the crash condition. |
| if (std::nullptr_t B = A) { |
| // body doesn't matter; analysis of the condition used to crash. |
| } |
| } |
| )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")); |
| } |
| |
| // A pointer `reinterpret_cast` (CK_BitCast) is value-preserving on the pointer |
| // bits, so flow-sensitive null-state (e.g. an `if (p != nullptr)` narrowing) |
| // must survive the cast. |
| TEST(PointerNullabilityTest, ReinterpretCastPreservesFlowNullState) { |
| // A guarded dereference is safe: the null check on the operand carries across |
| // the reinterpret_cast. |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void target(int* _Nullable p) { |
| if (p != nullptr) { |
| *reinterpret_cast<long*>(p); |
| } |
| } |
| )cc")); |
| |
| // Without a guard, the dereference is still unsafe. |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void target(int* _Nullable p) { |
| *reinterpret_cast<long*>(p); // [[unsafe]] |
| } |
| )cc")); |
| |
| // Returning a reinterpret_cast'ed nonnull pointer from a _Nonnull function is |
| // safe. |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| long* _Nonnull target(int* _Nonnull p) { |
| return reinterpret_cast<long*>(p); |
| } |
| )cc")); |
| |
| // Loading a nonnull member pointer, reinterpret_cast'ing it, and returning it |
| // from a _Nonnull function is safe. |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| struct S { |
| int* _Nonnull member; |
| }; |
| long* _Nonnull target(S* _Nonnull s) { |
| return reinterpret_cast<long*>(s->member); |
| } |
| )cc")); |
| |
| // Regression for a crash: when the operand and result of a `reinterpret_cast` |
| // have different pointee types (here `void*` -> a class type), the operand's |
| // modeled pointee storage location has the wrong type for the result, so only |
| // its null-state -- not the whole value -- may be forwarded. This mirrors |
| // protobuf's `arena_destruct_object<T>` helper |
| // (`reinterpret_cast<T*>(object)->~T()` with a `void* object`), which used to |
| // crash the analyzer while modeling the member access on the result. |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| struct T { |
| int* _Nullable field; |
| }; |
| void target(void* _Nonnull object) { |
| reinterpret_cast<T*>(object)->field = nullptr; |
| } |
| )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(); |
| X getX(); |
| }; |
| template <class X> |
| struct derived : base<X> {}; |
| |
| void target() { |
| // CK_BaseToDerived: preserves only outer nullability for explicit casts |
| // and for implicit casts generated as part of an explicit cast. |
| __assert_nullability<NK_nullable, NK_unspecified>( |
| (derived<int*>*)value<base<int* _Nullable>* _Nullable>()); |
| // CK_DerivedToBase: resugars from the argument's template parameters for |
| // implicit casts |
| __assert_nullability<NK_nullable>(value<derived<int* _Nullable>>().getX()); |
| // 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 destroys 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, |
| // and then treat static casts in an equivalent manner. |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void target(int* _Nullable p) { |
| static_cast<int* _Nonnull>(p); // TODO: To warn, or not to warn, that is |
| // the question. |
| static_cast<int*>(p); |
| } |
| )cc")); |
| |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| template <int I0, typename T1, typename T2> |
| struct Struct3Arg { |
| T1 arg1; |
| T2 arg2; |
| }; |
| |
| void target(Struct3Arg<1, int* _Nullable, int*>& p) { |
| *static_cast<const Struct3Arg<1, int*, int*>&>(p).arg1; // [[unsafe]] |
| *static_cast<const Struct3Arg<1, int*, int*>&>(p).arg2; |
| *static_cast<int*>(p.arg1); // [[unsafe]] |
| *static_cast<int*>(p.arg2); |
| } |
| )cc")); |
| |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| struct Base {}; |
| struct Derived : public Base {}; |
| |
| void target(Derived* _Nullable x, Derived* _Nonnull y) { |
| *static_cast<Base*>(x); // [[unsafe]] |
| *static_cast<Base*>(y); |
| } |
| )cc")); |
| |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| struct Base {}; |
| struct Derived : public Base {}; |
| |
| void target(Derived* _Nullable x) { |
| Base* y = x; |
| *y; // [[unsafe]] |
| } |
| )cc")); |
| |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| template <int I0, typename T1, typename T2> |
| struct Struct3Arg { |
| T1 arg1; |
| T2 arg2; |
| }; |
| |
| void target(Struct3Arg<1, int* _Nullable, int*>& p) { |
| *((const Struct3Arg<1, int*, int*>&)p).arg1; // [[unsafe]] |
| *((const Struct3Arg<1, int*, int*>&)p).arg2; |
| *(int*)p.arg1; // [[unsafe]] |
| *(int*)p.arg2; |
| *(float*)p.arg1; // [[unsafe]] |
| *(char*)p.arg2; |
| } |
| )cc")); |
| |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| template <typename T0, typename T1> |
| struct Struct2Arg { |
| T0 arg0; |
| T1 arg1; |
| }; |
| |
| void target(Struct2Arg<const int*, const int* _Nullable>& p) { |
| *const_cast<int*>(p.arg0); |
| *const_cast<int*>(p.arg1); // [[unsafe]] |
| } |
| )cc")); |
| } |
| |
| TEST(PointerNullabilityTest, CastToNullptrT) { |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| namespace std { |
| using nullptr_t = decltype(nullptr); |
| } |
| void target(const std::nullptr_t null) { std::nullptr_t p = null; } |
| )cc")); |
| } |
| |
| TEST(PointerNullabilityTest, CastDerivedToBase) { |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| class Parent {}; |
| |
| class Child : public Parent { |
| public: |
| static const Child& Singleton(); |
| }; |
| |
| void target() { |
| const Parent* _Nullable ptr = nullptr; |
| ptr = &Child::Singleton(); |
| *ptr; |
| } |
| )cc")); |
| } |
| |
| TEST(PointerNullabilityTest, CastDerivedToBaseWithAliasing) { |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| struct Base { |
| int* _Nullable nullable_field; |
| }; |
| |
| struct Derived : public Base {}; |
| |
| int* _Nonnull getNonnull(); |
| |
| void target(Derived* _Nonnull x) { |
| Base* alias = x; |
| alias->nullable_field = getNonnull(); |
| *x->nullable_field; |
| } |
| )cc")); |
| } |
| |
| } // namespace |
| } // namespace clang::tidy::nullability |