blob: 97df40171352f3bcad31ea582a1ab90604f81be6 [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 diagnosis on function calls.
#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, 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
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;
takeNullableRef(ptr_nonnull); // [[unsafe]]
*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
EXPECT_TRUE(checkDiagnostics(R"cc(
void takeNonnullRef(int* _Nonnull&);
void takeNullableRef(int* _Nullable&);
void takeUnannotatedRef(int*&);
void target(int* ptr_unannotated) {
int* UnannotatedNullValue = nullptr;
takeNonnullRef(UnannotatedNullValue); // [[unsafe]]
takeNonnullRef(ptr_unannotated);
*ptr_unannotated;
takeNullableRef(ptr_unannotated);
*ptr_unannotated; // false-negative? The unannotated pointer could be
// considered nullable if it has been used as a
// nullable pointer.
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(
template <typename T>
using Nonnull = T _Nonnull;
template <typename T>
using Nullable = T _Nullable;
template <typename T>
using NullabilityUnknown = T;
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(
template <typename T>
using Nonnull = T _Nonnull;
template <typename T>
using Nullable = T _Nullable;
template <typename T>
using NullabilityUnknown = T;
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, 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, 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, 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"));
}
// This is a crash repro.
TEST(PointerNullabilityTest, NonConstMethodClearsPointerMembersInExpr) {
EXPECT_TRUE(checkDiagnosticsHasUntracked(R"cc(
void f(char* _Nonnull const&, char* const&);
template <class T>
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. (This only happens if
// `S` is a template, which is why `S` has a template parameter that is
// otherwise unused.)
f(p, returnsPtr()); // [[unsafe]]
}
char* returnsPtr();
char* p;
};
template class S<int>;
)cc"));
}
TEST(SmartPointerTest, JoinCausesLossOfNullabilityPropertiesAtExit) {
// This is a regression test for the crash seen in b/414348238.
EXPECT_TRUE(checkDiagnostics(R"cc(
class A {
public:
void target(bool b) {
if (b) return;
// Calling a non-const member function clears out the framework-produced
// value for `p_` but (crucially) without mentioning `p_` in an
// expression (which would initialize the nullability properties for the
// previous value of `p_`).
non_const_member_fn();
// We now mention `p_` in an expression to make sure the nullability
// properties for `p_` are initialized.
p_;
// At function exit, we see the joined state from two blocks:
// - The block containing the return statement above. Here, `p_` has the
// value that the framework initialized it with. Because we never
// saw the expression `p_` on this path, we didn't initialize the
// nullability properties for this value.
// - The block that follows the if statement. Here, `p_` has a different
// value, which is associated with nullablility properties, as
// explained above.
// When we join these two values, because one of them does not have
// nullability properties, we also don't associate nullability
// properties with the joined value. It's important to test for this;
// our failure to do this previously resulted in the crash.
}
void non_const_member_fn();
private:
int* _Nonnull p_;
};
)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"));
}
} // namespace
} // namespace clang::tidy::nullability