| // 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 { |
| |
| // TODO(b/233582219): Implement diagnosis of unreachable program points |
| 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")); |
| } |
| |
| // TODO(b/233582219): Fix false negatives. Casting the pointer to boolean is |
| // evidence of the author considering null a possibility, hence the unnannotated |
| // pointer should be considered nullable and emit warnings where it fails or is |
| // not null checked. |
| TEST(PointerNullabilityTest, UnknownPtrImplicitCastToBool) { |
| // x |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void target(int* x) { |
| *x; // false-negative |
| if (x) { |
| *x; |
| } else { |
| *x; // [[unsafe]] |
| } |
| *x; // false-negative |
| } |
| )cc")); |
| |
| // !x |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void target(int* x) { |
| *x; // false-negative |
| if (!x) { |
| *x; // [[unsafe]] |
| } else { |
| *x; |
| } |
| *x; // false-negative |
| } |
| )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")); |
| } |
| |
| // 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( |
| 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")); |
| } |
| |
| } // namespace |
| } // namespace clang::tidy::nullability |