blob: 3280f508517c183943211bc5bd0a85f09021332e [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 function calls.
#include "nullability/test/check_diagnostics.h"
#include "nullability/type_nullability.h"
#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
namespace clang::tidy::nullability {
namespace {
TEST(PointerNullabilityTest, CallExprWithPointerReturnTypeFreeFunction) {
EXPECT_TRUE(checkDiagnostics(R"cc(
int *_Nonnull makeNonnull();
int *_Nullable makeNullable();
int *makeUnannotated();
void target() {
*makeNonnull();
*makeNullable(); // [[unsafe]]
*makeUnannotated();
}
)cc"));
}
TEST(PointerNullabilityTest, CallExprWithPointerReturnTypeMemberFunction) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct Foo {
int *_Nonnull makeNonnull();
int *_Nullable makeNullable();
int *makeUnannotated();
};
void target(Foo foo) {
*foo.makeNonnull();
*foo.makeNullable(); // [[unsafe]]
*foo.makeUnannotated();
}
)cc"));
}
TEST(PointerNullabilityTest, CallExprWithPointerReturnTypeFunctionPointer) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int *_Nonnull (*makeNonnull)(),
int *_Nullable (*makeNullable)(), int *(*makeUnannotated)()) {
*makeNonnull();
*makeNullable(); // [[unsafe]]
*makeUnannotated();
}
)cc"));
}
TEST(PointerNullabilityTest,
CallExprWithPointerReturnTypePointerToFunctionPointer) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int *_Nonnull (**makeNonnull)(),
int *_Nullable (**makeNullable)(), int *(**makeUnannotated)()) {
*(*makeNonnull)();
*(*makeNullable)(); // [[unsafe]]
*(*makeUnannotated)();
}
)cc"));
}
TEST(PointerNullabilityTest,
CallExprWithPointerReturnTypeFunctionPointerNested) {
// function returning a function pointer which returns a pointer
EXPECT_TRUE(checkDiagnostics(R"cc(
typedef int *_Nonnull (*MakeNonnullT)();
typedef int *_Nullable (*MakeNullableT)();
typedef int *(*MakeUnannotatedT)();
void target(MakeNonnullT (*makeNonnull)(), MakeNullableT (*makeNullable)(),
MakeUnannotatedT (*makeUnannotated)()) {
*(*makeNonnull)()();
*(*makeNullable)()(); // [[unsafe]]
*(*makeUnannotated)()();
}
)cc"));
}
TEST(PointerNullabilityTest, CallExprWithPointerReturnTypePointerRef) {
// free function returns reference to pointer
EXPECT_TRUE(checkDiagnostics(R"cc(
int *_Nonnull &makeNonnull();
int *_Nullable &makeNullable();
int *&makeUnannotated();
void target() {
*makeNonnull();
*makeNullable(); // [[unsafe]]
*makeUnannotated();
// Check that we can take the address of the returned reference and still
// see the correct nullability "behind" the resulting pointer.
__assert_nullability<NK_nonnull, NK_nonnull>(&makeNonnull());
__assert_nullability<NK_nonnull, NK_nullable>(&makeNullable());
__assert_nullability<NK_nonnull, NK_unspecified>(&makeUnannotated());
}
)cc"));
}
TEST(PointerNullabilityTest, CallExprWithPointerReturnTypeInLoop) {
// function called in loop
EXPECT_TRUE(checkDiagnostics(R"cc(
int *_Nullable makeNullable();
bool makeBool();
void target() {
bool first = true;
while (true) {
int *x = makeNullable();
if (first && x == nullptr) return;
first = false;
*x; // [[unsafe]]
}
}
)cc"));
}
TEST(PointerNullabilityTest, OutputParameterBasic) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void maybeModifyPtr(int** p);
void target() {
int* p = nullptr;
maybeModifyPtr(&p);
*p;
}
)cc"));
}
TEST(PointerNullabilityTest, OutputParameterReference) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void maybeModifyPtr(int*& r);
void target() {
int* p = nullptr;
maybeModifyPtr(p);
*p;
}
)cc"));
}
TEST(PointerNullabilityTest, OutputParameterReferenceConst) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void pointerNotModified(int* const& r);
void target() {
int* p = nullptr;
pointerNotModified(p);
*p; // [[unsafe]]
}
)cc"));
}
TEST(PointerNullabilityTest, OutputParameterReferencePointerToPointer) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void maybeModifyPtr(int**& r);
void target() {
int** pp = nullptr;
maybeModifyPtr(pp);
*pp;
**pp;
}
)cc"));
}
TEST(PointerNullabilityTest, OutputParameterMemberPointerToPointer) {
// This is a crash repro. We don't yet support pointers-to-members -- we just
// care that this doesn't cause the analysis to crash.
EXPECT_TRUE(checkDiagnostics(R"cc(
struct S {
int *p;
};
void callee(int *S::*);
void target(int *S::*ptr_to_member_ptr) { callee(ptr_to_member_ptr); }
)cc"));
}
TEST(PointerNullabilityTest, OutputParameterConst) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void pointerNotModified(int* const* p);
void target(int* _Nullable p) {
pointerNotModified(&p);
*p; // [[unsafe]]
}
)cc"));
// The only const qualifier that should be considered is on the inner
// pointer, otherwise this pattern should be considered safe.
EXPECT_TRUE(checkDiagnostics(R"cc(
void maybeModifyPtr(const int** const p);
void target() {
const int* p = nullptr;
maybeModifyPtr(&p);
*p;
}
)cc"));
}
TEST(PointerNullabilityTest, OutputParameterNonnull) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void pointerNotModified(int* _Nonnull* p);
void target(int* _Nonnull p) {
pointerNotModified(&p);
*p;
}
)cc"));
}
TEST(PointerNullabilityTest, OutputParameterCheckedNullable) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void maybeModify(int* _Nullable* p);
void target(int* _Nullable p) {
if (!p) return;
maybeModify(&p);
*p; // false negative: this dereference is actually unsafe!
}
)cc"));
}
TEST(PointerNullabilityTest, OutputParameterNullable) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void maybeModifyPtr(int* _Nullable* p);
void target() {
int* p = nullptr;
maybeModifyPtr(&p);
*p; // [[unsafe]]
}
)cc"));
}
TEST(PointerNullabilityTest, OutputParameterConditional) {
// This tests that flow sensitivity is preserved, to catch for example if the
// underlying pointer was always set to Nonnull once it's passed as an
// output parameter.
EXPECT_TRUE(checkDiagnostics(R"cc(
void maybeModifyPtr(int** p);
void target(int* _Nullable j, bool b) {
if (b) {
maybeModifyPtr(&j);
}
if (b) {
*j;
}
if (!b) {
*j; // [[unsafe]]
}
}
)cc"));
}
TEST(PointerNullabilityTest, OutputParameterWithoutAmpersandOperator) {
// This tests that the call to maybeModifyPtr works as expected if the param
// passed in doesn't directly use the & operator
EXPECT_TRUE(checkDiagnostics(R"cc(
void maybeModifyPtr(int** p);
void target(int* _Nullable p) {
auto pp = &p;
maybeModifyPtr(pp);
*p;
}
)cc"));
}
TEST(PointerNullabilityTest, OutputParameterTemplate) {
EXPECT_TRUE(checkDiagnostics(R"cc(
template <typename T>
struct S {
void maybeModify(T& ref);
};
void target(S<int*> s, int* _Nullable p) {
s.maybeModify(p);
*p;
}
)cc"));
EXPECT_TRUE(checkDiagnostics(R"cc(
template <typename T>
struct S {
void maybeModify(T& ref);
};
void target(S<int* _Nullable> s, int* _Nullable p) {
s.maybeModify(p);
*p; // false negative
}
)cc"));
EXPECT_TRUE(checkDiagnostics(R"cc(
template <typename T>
struct S {
void maybeModify(T& ref);
};
void target(S<int* _Nonnull> s, int* _Nonnull p) {
s.maybeModify(p);
*p;
}
)cc"));
}
TEST(PointerNullabilityTest, OutputParameterVariadicCallee) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void maybeModifyPtr(int** p, ...);
void target() {
int* p = nullptr;
maybeModifyPtr(&p, 0);
*p;
}
)cc"));
}
TEST(PointerNullabilityTest, OutputParameterMemberOperator) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct MaybeModifyPtr {
void operator()(int** p);
};
void target() {
int* p = nullptr;
MaybeModifyPtr()(&p);
*p;
}
)cc"));
}
TEST(PointerNullabilityTest, CallExprParamAssignment) {
// free function with single param
EXPECT_TRUE(checkDiagnostics(R"cc(
void takeNonnull(int *_Nonnull);
void takeNullable(int *_Nullable);
void takeUnannotated(int *);
void target(int *_Nonnull ptr_nonnull, int *_Nullable ptr_nullable,
int *ptr_unannotated) {
takeNonnull(nullptr); // [[unsafe]]
takeNonnull(ptr_nonnull);
takeNonnull(ptr_nullable); // [[unsafe]]
takeNonnull(ptr_unannotated);
takeNullable(nullptr);
takeNullable(ptr_nonnull);
takeNullable(ptr_nullable);
takeNullable(ptr_unannotated);
takeUnannotated(nullptr);
takeUnannotated(ptr_nonnull);
takeUnannotated(ptr_nullable);
takeUnannotated(ptr_unannotated);
}
)cc"));
// free function with multiple params of mixed nullability
EXPECT_TRUE(checkDiagnostics(R"cc(
void takeMixed(int *, int *_Nullable, int *_Nonnull);
void target() {
takeMixed(nullptr, nullptr, nullptr); // [[unsafe]]
}
)cc"));
// member function
EXPECT_TRUE(checkDiagnostics(R"cc(
struct Foo {
void takeNonnull(int *_Nonnull);
void takeNullable(int *_Nullable);
void takeUnannotated(int *);
};
void target(Foo foo) {
foo.takeNonnull(nullptr); // [[unsafe]]
foo.takeNullable(nullptr);
foo.takeUnannotated(nullptr);
}
)cc"));
// function pointer
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(void (*takeNonnull)(int *_Nonnull),
void (*takeNullable)(int *_Nullable),
void (*takeUnannotated)(int *)) {
takeNonnull(nullptr); // [[unsafe]]
takeNullable(nullptr);
takeUnannotated(nullptr);
}
)cc"));
// pointer to function pointer
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(void (**takeNonnull)(int *_Nonnull),
void (**takeNullable)(int *_Nullable),
void (**takeUnannotated)(int *)) {
(*takeNonnull)(nullptr); // [[unsafe]]
(*takeNullable)(nullptr);
(*takeUnannotated)(nullptr);
}
)cc"));
// function returned from function
EXPECT_TRUE(checkDiagnostics(R"cc(
typedef void (*takeNonnullF)(int *_Nonnull);
typedef void (*takeNullableF)(int *_Nullable);
typedef void (*takeUnannotatedF)(int *);
void target(takeNonnullF (*takeNonnull)(), takeNullableF (*takeNullable)(),
takeUnannotatedF (*takeUnannotated)()) {
(*takeNonnull)()(nullptr); // [[unsafe]]
(*takeNullable)()(nullptr);
(*takeUnannotated)()(nullptr);
}
)cc"));
// passing a reference to a nonnull pointer
//
// TODO(b/233582219): Fix false negative. When the nonnull pointer is passed
// by reference into the callee which takes a nullable parameter, its value
// may be changed to null, making it unsafe to dereference when we return from
// the function call. Some possible approaches for handling this case:
// (1) Disallow passing a nonnull pointer as a nullable reference - and warn
// at the function call.
// (2) Assume in worst case the nonnull pointer becomes nullable after the
// call - and warn at the dereference.
// (3) Sacrifice soundness for reduction in noise, and skip the warning.
EXPECT_TRUE(checkDiagnostics(R"cc(
void takeNonnullRef(int *_Nonnull &);
void takeNullableRef(int *_Nullable &);
void takeUnannotatedRef(int *&);
void target(int *_Nonnull ptr_nonnull) {
takeNonnullRef(ptr_nonnull);
*ptr_nonnull;
// false-negative
takeNullableRef(ptr_nonnull);
*ptr_nonnull;
takeUnannotatedRef(ptr_nonnull);
*ptr_nonnull;
}
)cc"));
// passing a reference to a nullable pointer
EXPECT_TRUE(checkDiagnostics(R"cc(
void takeNonnullRef(int *_Nonnull &);
void takeNullableRef(int *_Nullable &);
void takeUnannotatedRef(int *&);
void target(int *_Nullable ptr_nullable) {
takeNonnullRef(ptr_nullable); // [[unsafe]]
*ptr_nullable; // [[unsafe]]
takeNullableRef(ptr_nullable);
*ptr_nullable; // [[unsafe]]
takeUnannotatedRef(ptr_nullable);
*ptr_nullable;
}
)cc"));
// passing a reference to an unannotated pointer
//
// TODO(b/233582219): Fix false negative. The unannotated pointer should be
// considered nullable if it has been used as a nullable pointer.
EXPECT_TRUE(checkDiagnostics(R"cc(
void takeNonnullRef(int *_Nonnull &);
void takeNullableRef(int *_Nullable &);
void takeUnannotatedRef(int *&);
void target(int *ptr_unannotated) {
takeNonnullRef(ptr_unannotated);
*ptr_unannotated;
takeNullableRef(ptr_unannotated);
*ptr_unannotated; // false-negative
takeUnannotatedRef(ptr_unannotated);
*ptr_unannotated;
}
)cc"));
}
// Test that relevant diagnostics are produced for declarations with templated
// annotations.
TEST(PointerNullabilityTest, CallExprParamAssignmentTemplateAnnotations) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include "nullability_annotations.h"
void takeNonnull(Nonnull<int *>);
void takeNullable(Nullable<int *>);
void takeUnknown(NullabilityUnknown<int *>);
void target(Nonnull<int *> ptr_nonnull, Nullable<int *> ptr_nullable,
NullabilityUnknown<int *> ptr_unknown) {
takeNonnull(nullptr); // [[unsafe]]
takeNonnull(ptr_nonnull);
takeNonnull(ptr_nullable); // [[unsafe]]
takeNonnull(ptr_unknown);
takeNullable(nullptr);
takeNullable(ptr_nonnull);
takeNullable(ptr_nullable);
takeNullable(ptr_unknown);
takeUnknown(nullptr);
takeUnknown(ptr_nonnull);
takeUnknown(ptr_nullable);
takeUnknown(ptr_unknown);
}
)cc"));
}
// Test that templated annotations work interchangeably, in diagnosis, with the
// built-in Clang annotations.
TEST(PointerNullabilityTest, CallExprParamAssignmentTemplateBuiltinMixed) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include "nullability_annotations.h"
void takeNonnull(int *_Nonnull);
void takeNullable(int *_Nullable);
void takeUnannotated(int *);
void target(Nonnull<int *> ptr_nonnull, Nullable<int *> ptr_nullable,
NullabilityUnknown<int *> ptr_unknown) {
takeNonnull(ptr_nonnull);
takeNonnull(ptr_nullable); // [[unsafe]]
takeNonnull(ptr_unknown);
takeNullable(ptr_nonnull);
takeNullable(ptr_nullable);
takeNullable(ptr_unknown);
takeUnannotated(ptr_nonnull);
takeUnannotated(ptr_nullable);
takeUnannotated(ptr_unknown);
}
)cc"));
}
TEST(PointerNullabilityTest, CallExprMultiNonnullParams) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void take(int *_Nonnull, int *_Nullable, int *_Nonnull);
void target() {
take(nullptr, // [[unsafe]]
nullptr,
nullptr); // [[unsafe]]
}
)cc"));
}
TEST(PointerNullabilityTest, CanOverwritePtrWithPtrCreatedFromRefReturnType) {
// Test that if we create a pointer from a function returning a reference, we
// can use that pointer to overwrite an existing nullable pointer and make it
// nonnull.
EXPECT_TRUE(checkDiagnostics(R"cc(
int &get_int();
void target(int *_Nullable i) {
i = &get_int();
*i;
}
)cc"));
}
TEST(PointerNullabilityTest, CanOverwritePtrWithPtrReturnedByFunction) {
EXPECT_TRUE(checkDiagnostics(R"cc(
int *_Nonnull get_int();
void target(int *_Nullable i) {
i = get_int();
*i;
}
)cc"));
}
TEST(PointerNullabilityTest, CallVariadicFunction) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void variadic(int *_Nonnull, ...);
void target() {
int i = 0;
variadic(&i, nullptr, &i);
variadic(nullptr, nullptr, &i); // [[unsafe]]
}
)cc"));
}
TEST(PointerNullabilityTest, CallVariadicConstructor) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct S {
S(int* _Nonnull, ...);
};
void target() {
int i = 0;
S(&i, nullptr, &i);
S(nullptr, nullptr, &i); // [[unsafe]]
}
)cc"));
}
TEST(PointerNullabilityTest, CallMemberOperatorNoParams) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct MakeNonnull {
int *_Nonnull operator()();
};
struct MakeNullable {
int *_Nullable operator()();
};
struct MakeUnannotated {
int *operator()();
};
void target() {
MakeNonnull makeNonnull;
*makeNonnull();
MakeNullable makeNullable;
*makeNullable(); // [[unsafe]]
MakeUnannotated makeUnannotated;
*makeUnannotated();
}
)cc"));
}
TEST(PointerNullabilityTest, CallMemberOperatorOneParam) {
// overloaded operator with single param
EXPECT_TRUE(checkDiagnostics(R"cc(
// map<int * _Nonnull, int>
struct MapWithNonnullKeys {
int &operator[](int *_Nonnull key);
};
// map<int * _Nullable, int>
struct MapWithNullableKeys {
int &operator[](int *_Nullable key);
};
// map<int *, int>
struct MapWithUnannotatedKeys {
int &operator[](int *key);
};
void target(int *_Nonnull ptr_nonnull, int *_Nullable ptr_nullable,
int *ptr_unannotated) {
MapWithNonnullKeys nonnull_keys;
nonnull_keys[nullptr] = 42; // [[unsafe]]
nonnull_keys[ptr_nonnull] = 42;
nonnull_keys[ptr_nullable] = 42; // [[unsafe]]
nonnull_keys[ptr_unannotated] = 42;
MapWithNullableKeys nullable_keys;
nullable_keys[nullptr] = 42;
nullable_keys[ptr_nonnull] = 42;
nullable_keys[ptr_nullable] = 42;
nullable_keys[ptr_unannotated] = 42;
MapWithUnannotatedKeys unannotated_keys;
unannotated_keys[nullptr] = 42;
unannotated_keys[ptr_nonnull] = 42;
unannotated_keys[ptr_nullable] = 42;
unannotated_keys[ptr_unannotated] = 42;
}
)cc"));
}
TEST(PointerNullabilityTest, CallMemberOperatorMultipleParams) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct TakeMixed {
void operator()(int *, int *_Nullable, int *_Nonnull);
};
void target() {
TakeMixed takeMixed;
takeMixed(nullptr, nullptr, nullptr); // [[unsafe]]
}
)cc"));
}
TEST(PointerNullabilityTest, CallFreeOperator) {
// No nullability involved. This is just a regression test to make sure we can
// process a call to a free overloaded operator.
EXPECT_TRUE(checkDiagnostics(R"cc(
struct A {};
A operator+(A, A);
void target() {
A a;
a = a + a;
}
)cc"));
}
// Check that we distinguish between the nullability of the return type and
// parameters.
TEST(PointerNullabilityTest, DistinguishFunctionReturnTypeAndParams) {
EXPECT_TRUE(checkDiagnostics(R"cc(
int *_Nullable callee(int *_Nonnull);
void target() {
int i = 0;
__assert_nullability<NK_nullable>(callee(&i));
}
)cc"));
}
TEST(PointerNullabilityTest, DistinguishMethodReturnTypeAndParams) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct S {
int *_Nullable callee(int *_Nonnull);
};
void target(S s) {
int i = 0;
__assert_nullability<NK_nullable>(s.callee(&i));
}
)cc"));
}
TEST(PointerNullabilityTest,
ClassTemplate_DistinguishMethodReturnTypeAndParams) {
EXPECT_TRUE(checkDiagnostics(R"cc(
template <typename T0, typename T1>
struct S {
T0 callee(T1);
};
void target(S<int *_Nullable, int *_Nonnull> s) {
int i = 0;
__assert_nullability<NK_nullable>(s.callee(&i));
}
)cc"));
}
TEST(PointerNullabilityTest,
CallFunctionTemplate_TemplateArgInReturnTypeHasNullTypeSourceInfo) {
// This test sets up a function call where we don't have a `TypeSourceInfo`
// for the argument to a template parameter used in the return type.
// This is a regression test for a crash that we observed on real-world code.
EXPECT_TRUE(checkDiagnostics(R"cc(
template <class T>
struct A {
using Type = T;
};
template <int, class T>
typename A<T>::Type f(T);
void target() { f<0>(1); }
)cc"));
}
TEST(PointerNullabilityTest, CallFunctionTemplate_PartiallyDeduced) {
EXPECT_TRUE(checkDiagnostics(R"cc(
template <int, class T>
T f(T);
void target() { f<0>(1); }
)cc"));
}
TEST(PointerNullabilityTest, CallBuiltinFunction) {
// Crash repro.
EXPECT_TRUE(checkDiagnostics(R"cc(
void target() { __builtin_operator_new(0); }
)cc"));
}
TEST(PointerNullabilityTest, CallNoreturnDestructor) {
// This test demonstrates that the check considers program execution to end
// when a `noreturn` destructor is called.
// Among other things, this is intended to demonstrate that Abseil's logging
// instruction `LOG(FATAL)` (which creates an object with a `noreturn`
// destructor) is correctly interpreted as terminating the program.
EXPECT_TRUE(checkDiagnostics(R"cc(
struct Fatal {
__attribute__((noreturn)) ~Fatal();
void method(int);
};
void target(int* _Nullable p) {
// Do warn here, as the `*p` dereference happens before the `Fatal` object
// is destroyed.
Fatal().method(*p); // [[unsafe]]
// Don't warn here: We know that this code never gets executed.
*p;
}
)cc"));
}
TEST(PointerNullabilityTest, ConstMethodNoParamsCheckFirst) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct C {
int *_Nullable property() const { return x; }
int *_Nullable x = nullptr;
};
void target() {
C obj;
if (obj.property() != nullptr) *obj.property();
}
)cc"));
}
TEST(PointerNullabilityTest, ConstMethodNoImpl) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct C {
int *_Nullable property() const;
void may_mutate();
C &operator=(const C &);
};
void target() {
C obj;
if (obj.property() != nullptr) {
obj.may_mutate();
*obj.property(); // [[unsafe]]
};
if (obj.property() != nullptr) {
// A non-const operator call may mutate as well.
obj = C();
*obj.property(); // [[unsafe]]
};
if (obj.property() != nullptr) *obj.property();
}
)cc"));
}
// Special modeling of accessors is not implemented for accessors references.
TEST(PointerNullabilityTest, ConstMethodReturnsReference) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct C {
int *const _Nullable &property() const { return x; }
int *_Nullable x = nullptr;
};
void target() {
C obj;
if (obj.property() != nullptr) *obj.property(); // [[unsafe]]
}
)cc"));
}
TEST(PointerNullabilityTest, ConstMethodEarlyReturn) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct C {
int *_Nullable property() const;
};
void target() {
C c;
if (!c.property()) return;
// No false positive in this case, as there is no join.
*c.property();
}
)cc"));
}
TEST(PointerNullabilityTest, ConstMethodWithConditional) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct C {
int *_Nullable property() const;
};
bool cond();
void some_operation(int);
void target() {
C c;
if (!c.property()) return;
if (cond()) {
some_operation(1);
} else {
some_operation(2);
}
// Verify that we still model `c.property()` as returning the same value
// after the join, i.e. a null check performed before control flow
// diverges is still valid when the paths rejoin.
*c.property();
}
)cc"));
}
TEST(PointerNullabilityTest, ConstMethodNullPointerCheckOnOnlyOneBranch) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct C {
int *_Nullable property() const;
};
bool cond();
void target() {
C c;
if (cond()) {
if (!c.property()) return;
}
// We didn't check for null on all paths that reach this dereference, so
// it is unsafe.
*c.property(); // [[unsafe]]
}
)cc"));
}
TEST(PointerNullabilityTest, ConstMethodConditionalWithSeparateNullChecks) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct C {
int *_Nullable property() const;
};
bool cond();
void target() {
C c;
if (cond()) {
if (!c.property()) return;
} else {
if (!c.property()) return;
}
// TODO: This is a false positive: We checked for null on all paths
// that reach this dereference, but the lattice doesn't join the return
// values we generated for `c.property()` on the two branches, so we don't
// see that this is safe. This pattern is likely to be rare in practice,
// so it doesn't seem worth making the join operation more complex to
// support this.
*c.property(); // [[unsafe]]
}
)cc"));
}
TEST(PointerNullabilityTest, ConstMethodJoinLosesInformation) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct A {
bool cond() const;
};
struct C {
int *_Nullable property() const;
A a() const;
};
void target(const C &c1, const C &c2) {
if (c1.property() == nullptr || c2.property() == nullptr || c2.a().cond())
return;
*c1.property();
// TODO(b/359457439): This is a false positive, caused by a suboptimal CFG
// structure. All of the possible edges out of the if statement's
// condition join in a single CFG block before branching out again to
// the `return` block on the one hand and the block that performs the
// dereferences on the other.
// When we perform the join for the various edges out of the condition,
// we discard the return value for `c2.property()` because in the case
// where `c1.property()` is null, we never evaluate `c2.property()` and
// hence don't have a return value for it. When we call `c2.property()`
// again, we therefore create a fresh return value for it, and we hence
// cannot infer that this value is nonnull.
// The false positive does not occur if `c2.a().cond()` is replaced with
// a simpler condition, e.g. `c2.cond()` (assuming that `cond()` is
// moved to `C`). In this case, the CFG is structured differently: All of
// the edges taken when one of the conditions in the if state is true
// lead directly to the `return` block, and the edge taken when all
// conditions are false leads diresctly to the block that performs the
// dereferences. No join is performed, and we can therefore conclude that
// `c2.property()` is nonnull.
// I am not sure what causes the different CFG structure in the two cases,
// but it may be triggered by the `A` temporary that is returned by `a()`.
*c2.property(); // [[unsafe]]
}
)cc"));
}
TEST(PointerNullabilityTest, ConstMethodNoRecordForCallObject) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct S {
int* _Nullable property() const;
};
S makeS();
void target() {
if (makeS().property()) {
// This is a const member call on a different object, so it's not safe.
// But this line and the line above also don't cause any crashes.
*(makeS().property()); // [[unsafe]]
}
}
)cc"));
}
TEST(PointerNullabilityTest, ConstMethodReturningBool) {
// This tests (indirectly) that we also model const methods returning
// booleans. We use `operator bool()` as the specific const method because
// this then also gives us coverage of this special case (which is quite
// common, for example in `std::function`).
EXPECT_TRUE(checkDiagnostics(R"cc(
struct S {
operator bool() const;
};
void target(S s) {
int *p = nullptr;
int i = 0;
if (s) p = &i;
if (s)
// Dereference is safe because we know `operator bool()` will return the
// same thing both times.
*p;
}
)cc"));
}
TEST(PointerNullabilityTest, ConstMethodReturningSmartPointer) {
test::EnableSmartPointers Enable;
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
struct S {
Nullable<std::shared_ptr<int>> property() const;
};
void target() {
S s;
if (s.property() != nullptr) {
*(s.property());
}
}
)cc"));
}
TEST(PointerNullabilityTest, ConstMethodReturningSmartPointerByReference) {
test::EnableSmartPointers Enable;
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
struct S {
const Nullable<std::shared_ptr<int>> &property() const;
};
void target() {
S s;
if (s.property() != nullptr) {
*(s.property());
}
}
)cc"));
}
TEST(PointerNullabilityTest, ConstOperatorReturningPointer) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct S {
Nullable<int *> x;
};
struct SmartPtr {
S *operator->() const;
void clear();
};
void target() {
SmartPtr ptr;
if (ptr->x != nullptr) {
*ptr->x;
ptr.clear();
*ptr->x; // [[unsafe]]
}
}
)cc"));
}
TEST(PointerNullabilityTest, NonConstMethodClearsSmartPointer) {
test::EnableSmartPointers Enable;
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
struct S {
Nullable<std::shared_ptr<int>> property() const;
void writer();
};
void target() {
S s;
if (s.property() != nullptr) {
s.writer();
*(s.property()); // [[unsafe]]
}
}
)cc"));
}
TEST(PointerNullabilityTest, NonConstMethodClearsPointerMembers) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
struct S {
Nullable<int*> p;
void writer();
};
void target(S s) {
if (s.p != nullptr) {
s.writer();
*(s.p); // [[unsafe]]
}
}
)cc"));
}
TEST(PointerNullabilityTest, NonConstMethodDoesNotClearConstPointerMembers) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
struct S {
const Nullable<int*> cp;
void writer();
};
void target(S s) {
if (s.cp != nullptr) {
s.writer();
*(s.cp); // safe
}
}
)cc"));
}
// This is a crash repro.
TEST(PointerNullabilityTest, NonConstMethodClearsPointerMembersInExpr) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void f(char* _Nonnull const&, char* const&);
struct S {
void target() {
// This used to cause a crash because of a very specific sequence of
// events:
// - We visit `p` and initialize its nullability properties.
// - We visit `returnsPtr()`, causing us to reset all pointer-type
// fields (in this case, `p`). When we did this, we used to create
// fresh `PointerValue`s for the fields, but without nullability
// properties. This would cause a crash in the next step (see below).
// (Instead, we now simply clear the values associated with the
// fields.)
// - We visit the function call and check that `p` is non-null, which
// used to crash because `p` had a `PointerValue` associated with it
// that didn't have nullability properties.
// Diagnosis produces a "pointer value not modeled" warning on this line
// because the value for `p` has been cleared.
f(p, returnsPtr()); // [[unsafe]]
}
char* returnsPtr();
char* p;
};
)cc"));
}
TEST(PointerNullabilityTest, OptionalOperatorArrowCall) {
// Check that repeated accesses to a pointer behind an optional are considered
// to yield the same pointer -- but only if the optional is not modified in
// the meantime.
EXPECT_TRUE(checkDiagnostics(R"cc(
namespace std {
template <class T>
struct optional {
bool has_value() const;
T* operator->();
};
} // namespace std
struct S {
int* _Nullable p;
};
void target(std::optional<S> opt1, std::optional<S> opt2) {
if (!opt1.has_value() || !opt2.has_value()) return;
*opt1->p; // [[unsafe]]
if (opt1->p != nullptr) {
*opt1->p;
opt1 = opt2;
*opt1->p; // [[unsafe]]
}
}
)cc"));
}
TEST(PointerNullabilityTest, FieldUndefinedValue) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct C {
int *_Nullable property() const { return x; }
int *_Nullable x = nullptr;
};
C foo();
void target() {
C obj;
if (foo().x != nullptr) *foo().x; // [[unsafe]]
}
)cc"));
}
TEST(PointerNullabilityTest, Accessor_BaseObjectReturnedByReference) {
// Crash repro:
// If the base object of the accessor call expression is a reference returned
// from a function call, we have a storage location for the object but no
// values for its fields. Check that we don't crash in this case.
EXPECT_TRUE(checkDiagnostics(R"cc(
struct C {
int *_Nullable property() const { return x; }
int *_Nullable x = nullptr;
};
C &foo();
void target() {
if (foo().property() != nullptr) int x = *foo().property(); // [[unsafe]]
}
)cc"));
}
TEST(PointerNullabilityTest, MethodNoParamsUndefinedValue) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct C {
int *_Nullable property() const { return x; }
int *_Nullable x = nullptr;
};
void target() {
int x = 0;
if (C().property() != nullptr) {
*C().property(); // [[unsafe]]
}
C obj;
if (obj.property() != nullptr) {
*obj.property();
}
}
)cc"));
}
TEST(PointerNullabilityTest, CallPseudoDestructor) {
// Repro for assertion failure:
// We used to assert-fail on calls to `CXXPseudoDestructorExpr` because we
// didn't detect that they were "bound member function types" (with which we
// don't associate nullability as they aren't pointers).
EXPECT_TRUE(checkDiagnostics(R"cc(
using Int = int;
void target(Int i) { i.~Int(); }
)cc"));
}
} // namespace
} // namespace clang::tidy::nullability