blob: 61284138e800200a2e8c4a380ef9939d35b40d91 [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 diagnostics on smart pointers.
#include "nullability/test/check_diagnostics.h"
#include "external/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
namespace clang::tidy::nullability {
namespace {
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) {
// We get a warning on the following line because the constructor leaves
// `NonnullMember` in a null state.
/* [[unsafe]] */ }
_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 _Nullable OtherS {
using pointer = char *;
};
struct _Nullable S {
using pointer = bool *;
// 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"));
}
TEST(SmartPointerTest, UserDefinedSmartPointer) {
// This is a regression test for a false negative where we were not issuing
// diagnostics on user-defined smart pointer types when the smart pointer type
// did not have a `pointer` or `element_type` type alias.
EXPECT_TRUE(checkDiagnostics(R"cc(
template <class T>
struct _Nullable MySmartPtr {
// The false negative happens whether or not we have this deprecated
// tag in place.
using absl_nullability_compatible = void;
};
_Nonnull MySmartPtr<int> target(_Nullable MySmartPtr<int> p) {
return p; // [[unsafe]]
}
)cc"));
}
TEST(SmartPointerTest, UserDefinedSmartPointerComplexAssignmentOperator) {
// This is a regression test for a crash involving an operator= where
// - the LHS is a smart pointer without `pointer` or `element_type` type
// aliases.
// - the RHS is not a smart pointer
EXPECT_TRUE(checkDiagnostics(R"cc(
struct OtherType {};
template <class T>
struct _Nullable MySmartPtr {
MySmartPtr& operator=(const OtherType& other);
};
void target(OtherType& other) {
_Nonnull MySmartPtr<int> p; // [[unsafe]]
p = other;
}
)cc"));
}
TEST(SmartPointerTest, DereferenceViaNonMemberStar) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
#include <utility>
template <class T>
struct _Nullable MySmartPtr {
using pointer = T*;
};
// A bit unusual, but one can define a non-member operator*.
template <typename T>
static T& operator*(MySmartPtr<T> P);
void target() {
MySmartPtr<int> Null;
*Null; // [[unsafe]]
}
)cc"));
}
TEST(SmartPointerTest, ArrowOperatorReturnsPointerThatNeedsNullState) {
EXPECT_TRUE(checkDiagnostics(R"cc(
// Take by reference to avoid an implicit cast of the argument to rvalue.
void bar(int *_Nonnull &A) {}
class _Nullable CustomSmartIntPtr {
using pointer = int *;
int *P;
public:
void target() {
// We used to crash when requesting the null state for `P` here, which
// is implicitly accessed via the arrow operator.
bar(P);
}
};
)cc"));
}
TEST(SmartPointerTest, UnusualSmartPointerTypes) {
// This smart pointer type is unusual in that expects its template argument to
// be the underlying pointer type, rather than the type that the underlying
// smart pointer points to.
// Smart pointers that are "unusual" in this way should define a `pointer`
// type alias to make it clear what the underlying pointer type is, but if
// they omit this, we shouldn't crash.
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <type_traits>
template <class T>
class _Nullable UnusualSmartPointer {
public:
T operator->() const;
std::remove_pointer_t<T> operator*() const;
T get() const;
T release();
};
struct S {
void nonConstMemberFunction();
};
void target() {
// We'll interpret the default constructor as initializing the smart
// pointer to nullptr.
// So we know at least (*ptr) is dereferencing a nullptr.
// For the other expressions, without the `pointer` type alias, our guess
// for the underlying pointer type is incorrect, so we bail out.
UnusualSmartPointer<S *> ptr;
(*ptr).nonConstMemberFunction(); // [[unsafe]]
ptr->nonConstMemberFunction();
ptr.get()->nonConstMemberFunction();
ptr.release()->nonConstMemberFunction();
}
)cc"));
}
TEST(SmartPointerTest, OperatorStarReturnsPointer) {
// This smart pointer type is unusual in that `operator*` returns a pointer
// (while standard smart pointers like `std::unique_ptr` return a reference to
// the pointee)
EXPECT_TRUE(checkDiagnostics(R"cc(
template <class T>
class _Nullable UnusualSmartPointer {
using pointer = T*;
public:
T* operator*() const;
};
struct S {
void nonConstMemberFunction();
};
void target(UnusualSmartPointer<S> ptr) { (*ptr)->nonConstMemberFunction(); }
)cc"));
}
TEST(SmartPointerTest, DerivedFromSmartPointerTemplateInstantiation) {
// This is a crash repro. We did not recognize `target` as a smart pointer
// type because we did not check the template argument of the smart pointer
// base class, and it had no other indication of the underlying raw pointer
// type.
EXPECT_TRUE(checkDiagnostics(R"cc(
template <typename Handle>
class _Nullable SmartPtr {
public:
SmartPtr(SmartPtr &&) noexcept;
};
// Public inheritance or private inheritance doesn't matter.
struct target : SmartPtr<int *> {
target(target &&Other)
// The built-in transfer function running over the implicit
// CXXConstructExpr in the initializer expression would crash when
// trying to copy the synthetic smart pointer field from the source
// RecordStorageLocation. The assignment of the result object location
// for the derived class to be the result object location for the
// base-class-typed initializer expression was removing the synthetic
// field because the derived class was not seen as a smart pointer and
// did not have the synthetic field.
: target::SmartPtr(static_cast<target::SmartPtr &&>(Other)) {}
};
)cc"));
}
TEST(SmartPointerTest, NonnullSmartPointerFieldMovedFromAtExit) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
struct SomeResource {};
class A {
public:
void target() {
std::unique_ptr<SomeResource> some_resource = std::move(some_resource_);
/* [[unsafe]] */ }
private:
_Nonnull std::unique_ptr<SomeResource> some_resource_;
};
)cc"));
}
TEST(SmartPointerTest, NonnullSmartPointerFieldNotMovedFromAtExit) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
struct SomeResource {};
class A {
public:
void target() {
// The function doesn't move from the nonnull smart pointer field (in
// fact, it doesn't even access it), so this is fine.
// This also tests that the analysis doesn't try to access the field
// when it isn't modeled.
}
private:
_Nonnull std::unique_ptr<SomeResource> some_resource_;
};
)cc"));
}
TEST(SmartPointerTest,
NonnullSmartPointerFieldMovedFromThenResetToNonnullAtExit) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
struct SomeResource {};
class A {
public:
void target() {
// It's permissible to move from the nonnull smart pointer field during
// execution of the method, as long as it is reset to a nonnull state
// before exiting the method.
std::unique_ptr<SomeResource> some_resource = std::move(some_resource_);
some_resource_ = std::make_unique<SomeResource>();
}
private:
_Nonnull std::unique_ptr<SomeResource> some_resource_;
};
)cc"));
}
TEST(SmartPointerTest, NonnullSmartPointerFieldMovedFromAtDestructorExit) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
struct SomeResource {};
class target {
public:
~target() {
// Don't warn if the nonnull smart pointer field is moved from at
// destructor exit, because it's not possible to access the field after
// this point.
std::unique_ptr<SomeResource> some_resource = std::move(some_resource_);
}
private:
_Nonnull std::unique_ptr<SomeResource> some_resource_;
};
)cc"));
}
TEST(SmartPointerTest, NullableSmartPointerFieldMovedFromAtExit) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#include <memory>
struct SomeResource {};
class A {
public:
void target() {
// `some_resource_` is nullable, so it's fine for it to be in the
// moved-from state when this member function exits.
std::unique_ptr<SomeResource> some_resource = std::move(some_resource_);
}
private:
_Nullable std::unique_ptr<SomeResource> some_resource_;
};
)cc"));
}
} // namespace
} // namespace clang::tidy::nullability