blob: 1caf5706094b130e16cdedd2b9ce978f7347c6cb [file] [log] [blame] [edit]
// 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