blob: 24c9eb2b5893a9386b373eb4d5fe647026bce757 [file] [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 nullability annotations on fields.
#include "nullability/test/check_diagnostics.h"
#include "external/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
namespace clang::tidy::nullability {
namespace {
TEST(PointerNullabilityTest, NonNullFieldsOfPointerType) {
// dereference field of pointer type
EXPECT_TRUE(checkDiagnostics(R"cc(
struct Foo {
Foo *_Nonnull ptr;
};
void target(Foo foo) { *foo.ptr; }
)cc"));
// dereference field of pointer type in member function
EXPECT_TRUE(checkDiagnostics(R"cc(
struct Foo {
Foo *_Nonnull ptr;
void target() { *ptr; }
};
)cc"));
}
TEST(PointerNullabilityTest, NullableFieldsOfPointerType) {
// dereference field of pointer type
EXPECT_TRUE(checkDiagnostics(R"cc(
struct Foo {
Foo *_Nullable ptr;
};
void target(Foo foo) {
*foo.ptr; // [[unsafe]]
if (foo.ptr) {
*foo.ptr;
} else {
*foo.ptr; // [[unsafe]]
}
*foo.ptr; // [[unsafe]]
}
)cc"));
// dereference field of pointer type in member function
EXPECT_TRUE(checkDiagnostics(R"cc(
struct Foo {
Foo *_Nullable ptr;
void target() {
*ptr; // [[unsafe]]
if (ptr) {
*ptr;
} else {
*ptr; // [[unsafe]]
}
*ptr; // [[unsafe]]
}
};
)cc"));
}
TEST(PointerNullabilityTest, UnknownFieldsOfPointerType) {
// dereference field of pointer type
EXPECT_TRUE(checkDiagnostics(R"cc(
struct Foo {
Foo *ptr;
};
void target(Foo foo) { *foo.ptr; }
)cc"));
// dereference field of pointer type in member function
EXPECT_TRUE(checkDiagnostics(R"cc(
struct Foo {
Foo *ptr;
void target() { *ptr; }
};
)cc"));
}
TEST(PointerNullabilityTest, ChainedFieldDeref) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct S {
S *_Nonnull nonnull;
S *_Nullable nullable;
S *unknown;
};
void target(S &s) {
*(*s.nonnull).nonnull;
*(*s.nonnull).nullable; // [[unsafe]]
*(*s.nonnull).unknown;
s.nonnull->nonnull->nonnull;
s.nonnull->nonnull->nullable;
s.nonnull->nullable->nonnull; // [[unsafe]]
s.nonnull->unknown->nonnull;
*&s;
}
)cc"));
}
// This is a crash repro. It sets up a situation where we're merging pointers
// that don't have a null state to check that we don't crash in this case.
TEST(PointerNullabilityTest, MergePointersWithoutNullState) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct S {
void *p;
};
void target(bool cond) {
S src, dst;
if (cond) dst = src;
// `dst` has different values in the two branches that merge here, so we
// will attempt to merge the values of `dst.p` from the two branches.
// These lines are only here to ensure that `p` is modeled.
S unrelated;
unrelated.p;
}
)cc"));
}
TEST(PointerNullabilityTest, CreatesConsistentPointerValueForField) {
// This is a repro for a false positive.
// The call to `some_func()` clears the value of `p_`.
// Our logic for creating new pointer values used to work only on prvalues,
// so it would create two independent pointer values for the two accesses of
// `p_`, and hence we would not be able to conclude that `p_` was null in the
// `p_->target()` call.
EXPECT_TRUE(checkDiagnostics(R"cc(
struct S {
S* _Nullable const p_;
void target() {
some_func();
if (p_ != nullptr)
p_->target(); // p_ needs to be a member variable to repro.
}
void some_func();
};
)cc"));
}
TEST(PointerNullabilityTest,
NonnullRawPointerFieldNullableAtDestructorEntryViaRValueRefMethod) {
// An `&&`-qualified method may null out `some_resource_` before destruction,
// so it is modeled as nullable at destructor entry and dereferencing it in
// the destructor body is unsafe.
EXPECT_TRUE(checkDiagnostics(R"cc(
struct SomeResource {};
class target {
public:
void Finalize() && { some_resource_ = nullptr; }
~target() {
*some_resource_; // [[unsafe]]
}
private:
SomeResource* _Nonnull some_resource_;
};
)cc"));
}
TEST(PointerNullabilityTest, NonnullRawPointerFieldCheckedAtDestructorEntry) {
// The field is modeled as nullable at destructor entry, so a null check
// narrows it back to nonnull and the guarded dereference is safe.
EXPECT_TRUE(checkDiagnostics(R"cc(
struct SomeResource {};
class target {
public:
void Finalize() && { some_resource_ = nullptr; }
~target() {
if (some_resource_) *some_resource_;
}
private:
SomeResource* _Nonnull some_resource_;
};
)cc"));
}
TEST(PointerNullabilityTest,
UnknownRawPointerFieldNotNullableAtDestructorEntry) {
// A pointer field with unknown (unannotated) nullability is NOT modeled as
// nullable at destructor entry, even in a movable class. The user has not
// opted into nullability checking for such a field, so dereferencing it in
// the destructor -- without a null check -- must not be diagnosed as unsafe.
EXPECT_TRUE(checkDiagnostics(R"cc(
struct SomeResource {};
class target {
public:
void Finalize() && { some_resource_ = nullptr; }
~target() {
*some_resource_; // safe: unknown nullability, not downgraded
}
private:
SomeResource* some_resource_;
};
)cc"));
}
TEST(PointerNullabilityTest,
NonnullRawPointerFieldNullableAtDestructorEntryViaConsumingMethod) {
// A method taking an rvalue reference to the same class (`Consume(target&&)`)
// may move from its argument, nulling the argument's members before
// destruction -- even though this class declares no move constructor or move
// assignment operator (the user-declared destructor suppresses the implicit
// ones). So `some_resource_` may be null at destructor entry and
// dereferencing it is unsafe.
EXPECT_TRUE(checkDiagnostics(R"cc(
struct SomeResource {};
class target {
public:
void Consume(target&& other);
~target() {
*some_resource_; // [[unsafe]]
}
private:
SomeResource* _Nonnull some_resource_;
};
)cc"));
}
TEST(PointerNullabilityTest,
NonnullConstRawPointerFieldNonnullAtDestructorEntryInMovableClass) {
// A `const` pointer field is copied (not moved) by a defaulted move operation
// and cannot be reassigned by an `&&`-qualified method, so it is never
// moved-from and remains nonnull at destructor entry. Only the non-const
// sibling is downgraded and flagged.
EXPECT_TRUE(checkDiagnostics(R"cc(
struct SomeResource {};
class target {
public:
void Finalize() && { movable_resource_ = nullptr; }
~target() {
*const_resource_; // safe: const field, not downgraded
*movable_resource_; // [[unsafe]]
}
private:
SomeResource* _Nonnull const const_resource_;
SomeResource* _Nonnull movable_resource_;
};
)cc"));
}
} // namespace
} // namespace clang::tidy::nullability