blob: da545baeff5d46cb33aac854ed92c997c40d59f5 [file] [log] [blame]
// 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 diagnostics on smart pointers.
#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 {
// Static initializer turns on support for smart pointers.
test::EnableSmartPointers Enable;
TEST(SmartPointerTest, Dereference) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
void target() {
*std::unique_ptr<int>(); // [[unsafe]]
*std::make_unique<int>();
}
)cc"));
}
TEST(SmartPointerTest, ArrowOp) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
struct S {
int i = 0;
};
void target() {
std::unique_ptr<S>()->i; // [[unsafe]]
std::make_unique<S>()->i;
}
)cc"));
}
TEST(SmartPointerTest, Subscript) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
void target() {
std::unique_ptr<int[]>()[0]; // [[unsafe]]
std::make_unique<int[]>(1)[0];
}
)cc"));
}
TEST(SmartPointerTest, Assignment) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
void target(Nonnull<std::unique_ptr<int>> NonnullPtr,
Nullable<std::unique_ptr<int>> NullablePtr,
std::unique_ptr<int> UnannotatedPtr) {
NonnullPtr = std::make_unique<int>();
NonnullPtr = nullptr; // [[unsafe]]
NullablePtr = nullptr;
UnannotatedPtr = nullptr;
}
)cc"));
}
TEST(SmartPointerTest, ResetUniquePtr) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
void target(Nonnull<std::unique_ptr<int>> NonnullPtr,
Nullable<std::unique_ptr<int>> NullablePtr,
std::unique_ptr<int> UnannotatedPtr) {
NonnullPtr.reset(); // [[unsafe]]
NullablePtr.reset();
UnannotatedPtr.reset();
(&NonnullPtr)->reset(); // [[unsafe]]
(&NullablePtr)->reset();
(&UnannotatedPtr)->reset();
NonnullPtr.reset(nullptr); // [[unsafe]]
NullablePtr.reset(nullptr);
UnannotatedPtr.reset(nullptr);
NonnullPtr.reset(new int);
}
)cc"));
}
TEST(SmartPointerTest, ResetSharedPtr) {
// `shared_ptr` has different `reset()` overloads than `unique_ptr`, so test
// it too.
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
void target(Nonnull<std::shared_ptr<int>> NonnullPtr,
Nullable<std::shared_ptr<int>> NullablePtr,
std::shared_ptr<int> UnannotatedPtr) {
NonnullPtr.reset(); // [[unsafe]]
NullablePtr.reset();
UnannotatedPtr.reset();
(&NonnullPtr)->reset(); // [[unsafe]]
(&NullablePtr)->reset();
(&UnannotatedPtr)->reset();
// Cannot pass `nullptr` directly to `shared_ptr::reset()` because it is
// not a pointer type.
int *NullIntPtr = nullptr;
NonnullPtr.reset(NullIntPtr); // [[unsafe]]
NullablePtr.reset(NullIntPtr);
UnannotatedPtr.reset(NullIntPtr);
NonnullPtr.reset(new int);
}
)cc"));
}
TEST(SmartPointerTest, FunctionParameters) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
void TakeNonnull(Nonnull<std::shared_ptr<int>>);
void TakeNullable(Nullable<std::shared_ptr<int>>);
void TakeUnannotated(std::shared_ptr<int>);
void target(Nonnull<std::shared_ptr<int>> NonnullPtr,
Nullable<std::shared_ptr<int>> NullablePtr,
std::shared_ptr<int> UnannotatedPtr) {
TakeNonnull(std::shared_ptr<int>()); // [[unsafe]]
TakeNonnull(NonnullPtr);
TakeNonnull(NullablePtr); // [[unsafe]]
TakeNonnull(UnannotatedPtr);
TakeNullable(std::shared_ptr<int>());
TakeNullable(NonnullPtr);
TakeNullable(NullablePtr);
TakeNullable(UnannotatedPtr);
TakeUnannotated(std::shared_ptr<int>());
TakeUnannotated(NonnullPtr);
TakeUnannotated(NullablePtr);
TakeUnannotated(UnannotatedPtr);
}
)cc"));
}
TEST(SmartPointerTest, ConstructorParameters) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
struct TakeNonnull {
TakeNonnull(Nonnull<std::shared_ptr<int>>);
};
struct TakeNullable {
TakeNullable(Nullable<std::shared_ptr<int>>);
};
struct TakeUnannotated {
TakeUnannotated(std::shared_ptr<int>);
};
void target(Nonnull<std::shared_ptr<int>> NonnullPtr,
Nullable<std::shared_ptr<int>> NullablePtr,
std::shared_ptr<int> UnannotatedPtr) {
TakeNonnull{std::shared_ptr<int>()}; // [[unsafe]]
TakeNonnull{NonnullPtr};
TakeNonnull{NullablePtr}; // [[unsafe]]
TakeNonnull{UnannotatedPtr};
TakeNullable{std::shared_ptr<int>()};
TakeNullable{NonnullPtr};
TakeNullable{NullablePtr};
TakeNullable{UnannotatedPtr};
TakeUnannotated{std::shared_ptr<int>()};
TakeUnannotated{NonnullPtr};
TakeUnannotated{NullablePtr};
TakeUnannotated{UnannotatedPtr};
}
)cc"));
}
TEST(SmartPointerTest, ReturnValue_Nullable) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
bool cond();
Nullable<std::unique_ptr<int>> target() {
if (cond())
return std::make_unique<int>(0);
else
return std::unique_ptr<int>();
}
)cc"));
}
TEST(SmartPointerTest, ReturnValue_Unknown) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
bool cond();
std::unique_ptr<int> target() {
if (cond())
return std::make_unique<int>(0);
else
return std::unique_ptr<int>();
}
)cc"));
}
TEST(SmartPointerTest, InitializeMemberWithNonnull) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
struct target {
target(Nonnull<std::shared_ptr<int>> NonnullPtr)
: NonnullMember(NonnullPtr),
NullableMember(NonnullPtr),
UnannotatedMember(NonnullPtr) {}
Nonnull<std::shared_ptr<int>> NonnullMember;
Nullable<std::shared_ptr<int>> NullableMember;
std::shared_ptr<int> UnannotatedMember;
};
)cc"));
}
TEST(SmartPointerTest, InitializeMemberWithNullable) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
struct target {
target(Nullable<std::shared_ptr<int>> NullablePtr)
: NonnullMember(NullablePtr), // [[unsafe]]
NullableMember(NullablePtr),
UnannotatedMember(NullablePtr) {}
Nonnull<std::shared_ptr<int>> NonnullMember;
Nullable<std::shared_ptr<int>> NullableMember;
std::shared_ptr<int> UnannotatedMember;
};
)cc"));
}
TEST(SmartPointerTest, InitializeMemberWithUnannotated) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
struct target {
target(std::shared_ptr<int> UnannotatedPtr)
: NonnullMember(UnannotatedPtr),
NullableMember(UnannotatedPtr),
UnannotatedMember(UnannotatedPtr) {}
Nonnull<std::shared_ptr<int>> NonnullMember;
Nullable<std::shared_ptr<int>> NullableMember;
std::shared_ptr<int> UnannotatedMember;
};
)cc"));
}
TEST(SmartPointerTest, AccessSmartPointerReturnedByReference) {
// This is a repro for an assertion failure.
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
struct S {
void f();
};
// The `const` is important for the repro (so that the AST at the callsite
// below doesn't contain an `ImplicitCastExpr` to remove the const).
const Nonnull<std::unique_ptr<S>>& ReturnNonnull();
const Nullable<std::unique_ptr<S>>& ReturnNullable();
const std::unique_ptr<S>& ReturnUnannotated();
void target() {
ReturnNonnull()->f();
ReturnNullable()->f(); // [[unsafe]]
ReturnUnannotated()->f();
}
)cc"));
}
TEST(SmartPointerTest, AccessSmartPointerReturnedByPointerAlias) {
// This is a crash repro.
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
// When checking whether the base of a `->` member expression is a
// pointer-to-smart-pointer, it used to be that we didn't canonicalize the
// type. This test wraps such a return type in a type alias, which used to
// cause a crash. The `const` is important because, without it, the AST
// contains an `ImplicitCastExpr` that adds a `const`, desugaring the type
// in the process.
template <typename T>
using Alias = T;
Alias<const std::unique_ptr<int> *> getPtr();
void target() { *getPtr()->get(); }
)cc"));
}
TEST(SmartPointerTest, SmartPointerFlowSensitive) {
// Simple flow-sensitive test with a smart pointer.
// This is a repro for a false positive that we used to encounter in C++20
// mode.
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
void target(Nullable<std::shared_ptr<int>> NullablePtr) {
*NullablePtr; // [[unsafe]]
if (NullablePtr != nullptr) *NullablePtr;
}
)cc"));
}
TEST(SmartPointerTest, SimpleIfFpRepro) {
// This is a repro for a false positive.
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
bool cond();
void target() {
std::shared_ptr<int> p;
while (cond()) {
if (p != nullptr) {
*p; // False positive here
} else {
p = std::make_shared<int>();
}
if (cond()) continue;
p.reset();
}
}
)cc"));
}
TEST(SmartPointerTest, NestedPointersArrowOperatorOnInner) {
// This is a crash repro.
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
struct S {
int i;
};
void target() {
// The `const` is important because, without it, the AST for the arrow
// access contains an `ImplicitCastExpr` that adds a `const` and is seen
// as a smart pointer expression that initializes null state for the inner
// smart pointer.
std::unique_ptr<const std::unique_ptr<S>> p = nullptr;
(void)(*p)->i; // [[unsafe]]
}
)cc"));
}
TEST(SmartPointerTest, ConstructSmartPointerFromTemporarySmartPointer) {
// This is a crash repro.
EXPECT_TRUE(checkDiagnostics(R"cc(
struct OtherS {
using pointer = char *;
using absl_nullability_compatible = void;
};
struct S {
using pointer = bool *;
using absl_nullability_compatible = void;
// S needs to be constructed by value from another smart pointer,
// otherwise, didn't crash.
explicit S(OtherS) {}
};
void target() { S{OtherS()}; }
)cc"));
}
TEST(SmartPointerTest, ConditionalSmartPointer) {
// This is a crash repro.
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
void takesNonnull(Nonnull<std::unique_ptr<int>> a);
void target(bool b) {
takesNonnull(b ? nullptr // [[unsafe]]
: std::make_unique<int>());
}
)cc"));
}
TEST(SmartPointerTest, MovedFromNonnullSmartPointer) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
#include <utility>
struct S {
int i;
};
void target(Nonnull<std::unique_ptr<S>> NonnullPtr) {
// Moving from a nonnull smart pointer is legal.
std::unique_ptr<S> Local = std::move(NonnullPtr);
// Moving from a moved-from nonnull smart pointer is not legal.
// This also serves as a more general test that passing a moved-from
// nonnull smart pointer as an argument is always disallowed, even if
// the parameter is not annotated nonnull.
Local = std::move(NonnullPtr); // [[unsafe]]
// Calling any member function or operator on a moved-from nonnull
// smart pointer is not legal, with two exceptions:
// - operator=
// - reset() (non-`nullptr_t` overload)
// We don't test all member functions and operators exhaustively, but we
// test a few to satisfy ourselves that we have the necessary generality.
NonnullPtr.get(); // [[unsafe]]
// Do a test with a pointer receiver.
(&NonnullPtr)->get(); // [[unsafe]]
NonnullPtr.release(); // [[unsafe]]
NonnullPtr.reset(nullptr); // [[unsafe]]
*NonnullPtr; // [[unsafe]]
NonnullPtr->i; // [[unsafe]]
NonnullPtr == nullptr; // [[unsafe]]
nullptr == NonnullPtr; // [[unsafe]]
// Test `operator bool`.
if (NonnullPtr) // [[unsafe]]
;
// Assigning to a moved-from nonnull smart pointer is legal.
// Make sure that we also allow this if the left-hand side contains
// additional nodes such as parentheses or casts.
NonnullPtr = std::make_unique<S>();
Local = std::move(NonnullPtr);
(NonnullPtr) = std::make_unique<S>();
Local = std::move(NonnullPtr);
static_cast<std::unique_ptr<S> &>(NonnullPtr) = std::make_unique<S>();
// Calling non-nullptr reset() on a moved-from nonnull smart pointer is
// legal.
Local = std::move(NonnullPtr);
NonnullPtr.reset(new S());
Local = std::move(NonnullPtr);
(NonnullPtr).reset(new S());
Local = std::move(NonnullPtr);
static_cast<std::unique_ptr<S> &>(NonnullPtr).reset(new S());
}
)cc"));
}
} // namespace
} // namespace clang::tidy::nullability