blob: a1e3c4450e5a1997cbc30ce834d73bf34b240a3f [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 smart pointers.
//
// Where `unique_ptr` and `shared_ptr` provide same API, we test the API only on
// `unique_ptr`. Duplicating the tests for `shared_ptr` would not give us
// additional coverage, as we know the implementation is shared between all
// smart pointer types. (This can be viewed as a form of white-box testing.)
// We therefore only test `shared_ptr` APIs that do not exist in equivalent form
// in `unique_ptr`.
#include <memory>
#include <utility>
#include "nullability_test.h"
Nonnull<std::unique_ptr<int>> makeNonnull();
Nullable<std::unique_ptr<int>> makeNullable();
std::unique_ptr<int> makeUnknown();
Nonnull<int *> makeNonnullRaw();
Nullable<int *> makeNullableRaw();
int *makeUnknownRaw();
const Nonnull<std::unique_ptr<int>> &returnNonnullRef();
const Nullable<std::unique_ptr<int>> &returnNullableRef();
const std::unique_ptr<int> &returnUnknownRef();
// Add an extra wrinkle for the following functions by wrapping the return type
// in a type alias. It used to be that we didn't canonicalize these types,
// leading to crashes.
template <typename T>
using Alias = T;
Alias<const Nonnull<std::unique_ptr<int>> *> returnPtrToNonnull();
Alias<const Nullable<std::unique_ptr<int>> *> returnPtrToNullable();
Alias<const std::unique_ptr<int> *> returnPtrToUnknown();
// Provided for various tests that require a base and derived class.
struct Base {
virtual ~Base();
};
struct Derived : public Base {
~Derived() override;
};
TEST void parameterAnnotations(Nonnull<std::unique_ptr<int>> NonnullParam,
Nullable<std::unique_ptr<int>> NullableParam,
std::unique_ptr<int> UnknownParam) {
nonnull(NonnullParam);
nullable(NullableParam);
unknown(UnknownParam);
}
TEST void returnValueAnnotations() {
nonnull(makeNonnull());
nullable(makeNullable());
unknown(makeUnknown());
}
TEST void returnValueAnnotationsRef() {
nonnull(returnNonnullRef());
nullable(returnNullableRef());
unknown(returnUnknownRef());
}
TEST void returnValueAnnotationsPtrToSmartPtr() {
nonnull(*returnPtrToNonnull());
nullable(*returnPtrToNullable());
unknown(*returnPtrToUnknown());
}
TEST void returnValueAnnotationsPtrToSmartPtrGet() {
// This is a crash repro.
// Call `get()` on the smart pointer with `->` syntax, so that there isn't
// a glvalue of smart pointer type in the AST. We used to crash on this
// because we didn't initialize the nullability properties on the contained
// raw pointer.
// Don't merge this test with the test above, or the `*` dereferences will
// cause the smart pointers to get initialized.
nonnull(returnPtrToNonnull()->get());
nullable(returnPtrToNullable()->get());
unknown(returnPtrToUnknown()->get());
}
TEST void outputParameters() {
// This test checks only a few of the most common cases for output parameters.
// The tests for raw pointers cover a broader set of cases. Because we know
// that the implementation is shared between raw pointers and smart pointers,
// we chose not to duplicate all of those tests here.
{
void maybeModifyPtr(std::unique_ptr<int> * P);
std::unique_ptr<int> P;
nullable(P);
maybeModifyPtr(&P);
unknown(P);
}
{
void maybeModifyPtr(std::unique_ptr<int> & P);
std::unique_ptr<int> P;
nullable(P);
maybeModifyPtr(P);
unknown(P);
}
{
void doesntModifyPtr(const std::unique_ptr<int> *P);
std::unique_ptr<int> P;
nullable(P);
doesntModifyPtr(&P);
nullable(P);
}
{
void doesntModifyPtr(const std::unique_ptr<int> &P);
std::unique_ptr<int> P;
nullable(P);
doesntModifyPtr(P);
nullable(P);
}
}
TEST void defaultConstructor() { nullable(std::unique_ptr<int>()); }
TEST void nullptrConstructor() {
nullable(std::unique_ptr<int>(nullptr));
nullable(std::shared_ptr<int>(nullptr, std::default_delete<int>()));
nullable(std::shared_ptr<int>(nullptr, std::default_delete<int>(),
std::allocator<int>()));
}
TEST void constructorTakingPointer() {
nonnull(std::unique_ptr<int>(makeNonnullRaw()));
nullable(std::unique_ptr<int>(makeNullableRaw()));
unknown(std::unique_ptr<int>(makeUnknownRaw()));
nonnull(std::unique_ptr<int>(makeNonnullRaw(), std::default_delete<int>()));
nullable(std::unique_ptr<int>(makeNullableRaw(), std::default_delete<int>()));
unknown(std::unique_ptr<int>(makeUnknownRaw(), std::default_delete<int>()));
nonnull(std::shared_ptr<int>(makeNonnullRaw(), std::default_delete<int>(),
std::allocator<int>()));
nullable(std::shared_ptr<int>(makeNullableRaw(), std::default_delete<int>(),
std::allocator<int>()));
unknown(std::shared_ptr<int>(makeUnknownRaw(), std::default_delete<int>(),
std::allocator<int>()));
}
TEST void constructorTakingPointer_ArrayVersion() {
nonnull(std::unique_ptr<int[]>(makeNonnullRaw()));
nullable(std::unique_ptr<int[]>(makeNullableRaw()));
unknown(std::unique_ptr<int[]>(makeUnknownRaw()));
nonnull(
std::unique_ptr<int[]>(makeNonnullRaw(), std::default_delete<int[]>()));
nullable(
std::unique_ptr<int[]>(makeNullableRaw(), std::default_delete<int[]>()));
unknown(
std::unique_ptr<int[]>(makeUnknownRaw(), std::default_delete<int[]>()));
}
TEST void constructorTakingPointer_DerivedToBaseConversion() {
// Test the `shared_ptr` constructor taking a pointer that can be converted
// to the underlying pointer type.
std::shared_ptr<Base> base(new Derived());
nonnull(base);
nonnull(base.get());
}
TEST void moveConstructor(Nonnull<std::unique_ptr<int>> NonnullParam,
Nullable<std::unique_ptr<int>> NullableParam,
std::unique_ptr<int> UnknownParam) {
nonnull(std::unique_ptr<int>(std::move(NonnullParam)));
nullable(std::unique_ptr<int>(std::move(NullableParam)));
unknown(std::unique_ptr<int>(std::move(UnknownParam)));
nullable(NonnullParam);
nullable(NullableParam);
nullable(UnknownParam);
}
TEST void sharedPtrFromUniquePtr(Nonnull<std::unique_ptr<int>> NonnullParam,
Nullable<std::unique_ptr<int>> NullableParam,
std::unique_ptr<int> UnknownParam) {
nonnull(std::shared_ptr<int>(std::move(NonnullParam)));
nullable(std::shared_ptr<int>(std::move(NullableParam)));
unknown(std::shared_ptr<int>(std::move(UnknownParam)));
nullable(NonnullParam);
nullable(NullableParam);
nullable(UnknownParam);
}
TEST void copyConstructor(Nonnull<std::shared_ptr<int>> NonnullParam,
Nullable<std::shared_ptr<int>> NullableParam,
std::shared_ptr<int> UnknownParam) {
nonnull(std::shared_ptr<int>(NonnullParam));
nullable(std::shared_ptr<int>(NullableParam));
unknown(std::shared_ptr<int>(UnknownParam));
nonnull(NonnullParam);
nullable(NullableParam);
unknown(UnknownParam);
}
TEST void aliasingConstructor(Nonnull<std::shared_ptr<int>> NonnullParam) {
nullable(std::shared_ptr<int>(NonnullParam, nullptr));
nonnull(NonnullParam);
nullable(std::shared_ptr<int>(std::move(NonnullParam), nullptr));
nullable(NonnullParam);
}
TEST void sharedPtrFromWeakPtr(std::weak_ptr<int> Weak) {
nonnull(std::shared_ptr<int>(Weak));
}
TEST void nullptrAssignment() {
std::unique_ptr<int> P = makeUnknown();
unknown(P);
P = nullptr;
nullable(P);
}
TEST void moveAssignment(Nonnull<std::unique_ptr<int>> NonnullParam,
Nullable<std::unique_ptr<int>> NullableParam,
std::unique_ptr<int> UnknownParam) {
std::unique_ptr<int> NonnullLocal;
nonnull(NonnullLocal = std::move(NonnullParam));
std::unique_ptr<int> NullableLocal;
nullable(NullableLocal = std::move(NullableParam));
std::unique_ptr<int> UnknownLocal;
unknown(UnknownLocal = std::move(UnknownParam));
nullable(NonnullParam);
nullable(NullableParam);
nullable(UnknownParam);
}
TEST void copyAssignment(Nonnull<std::shared_ptr<int>> NonnullParam,
Nullable<std::shared_ptr<int>> NullableParam,
std::shared_ptr<int> UnknownParam) {
std::shared_ptr<int> NonnullLocal;
nonnull(NonnullLocal = NonnullParam);
std::shared_ptr<int> NullableLocal;
nullable(NullableLocal = NullableParam);
std::shared_ptr<int> UnknownLocal;
unknown(UnknownLocal = UnknownParam);
nonnull(NonnullParam);
nullable(NullableParam);
unknown(UnknownParam);
}
TEST void release(Nonnull<std::unique_ptr<int>> NonnullParam,
Nullable<std::unique_ptr<int>> NullableParam,
std::unique_ptr<int> UnknownParam) {
nonnull(NonnullParam.release());
nullable(NullableParam.release());
unknown(UnknownParam.release());
nullable(NonnullParam);
nullable(NullableParam);
nullable(UnknownParam);
}
TEST void reset() {
{
auto P = std::make_unique<int>();
P.reset();
provable(P.get() == nullptr);
}
{
std::unique_ptr<int> P;
int *Raw = new int();
P.reset(Raw);
provable(P.get() == Raw);
}
{
auto P = std::make_unique<int[]>(1);
P.reset();
provable(P.get() == nullptr);
}
{
auto P = std::make_unique<int[]>(1);
P.reset(nullptr);
provable(P.get() == nullptr);
}
{
std::unique_ptr<int[]> P;
int *Raw = new int[1];
P.reset(Raw);
provable(P.get() == Raw);
}
{
auto P = std::make_shared<int>();
P.reset();
provable(P.get() == nullptr);
}
{
std::shared_ptr<int> P;
int *Raw = new int();
P.reset(Raw);
provable(P.get() == Raw);
}
{
std::shared_ptr<int> P;
int *Raw = new int();
P.reset(Raw, std::default_delete<int>());
provable(P.get() == Raw);
}
{
std::shared_ptr<int> P;
int *Raw = new int();
P.reset(Raw, std::default_delete<int>(), std::allocator<int>());
provable(P.get() == Raw);
}
}
TEST void swap() {
{
auto P1 = std::make_unique<int>();
auto P2 = std::make_unique<int>();
int *Raw1 = P1.get();
int *Raw2 = P2.get();
P1.swap(P2);
provable(P1.get() == Raw2);
provable(P2.get() == Raw1);
}
{
auto P1 = std::make_unique<int>();
auto P2 = std::make_unique<int>();
int *Raw1 = P1.get();
int *Raw2 = P2.get();
std::swap(P1, P2);
provable(P1.get() == Raw2);
provable(P2.get() == Raw1);
}
}
TEST void get(int *Raw) {
std::unique_ptr<int> Null;
provable(Null.get() == nullptr);
std::unique_ptr<int> P(Raw);
provable(P.get() == Raw);
// Test `->method()` call syntax.
provable((&Null)->get() == nullptr);
provable((&P)->get() == Raw);
}
TEST void operatorBool() {
provable(!std::unique_ptr<int>());
provable(static_cast<bool>(std::make_unique<int>()));
// Test `->method()` call syntax.
auto P = std::make_unique<int>();
provable((&P)->operator bool());
}
TEST void operatorStar() {
auto P = std::make_unique<int>();
provable(P.get() == &*P);
}
namespace operator_arrow {
struct S {
int I;
};
TEST void operatorArrow() {
auto P = std::make_unique<S>();
provable(&P.get()->I == &P->I);
}
} // namespace operator_arrow
TEST void makeUnique() {
nonnull(std::make_unique<int>());
nonnull(std::make_unique<int>(42));
nonnull(std::make_unique_for_overwrite<int>());
nonnull(std::make_unique_for_overwrite<int[]>(5));
}
TEST void makeShared() {
nonnull(std::make_shared<int>());
nonnull(std::make_shared<int>(42));
nonnull(std::make_shared_for_overwrite<int>());
nonnull(std::make_shared_for_overwrite<int[]>(5));
}
TEST void allocateShared() {
nonnull(std::allocate_shared<int>(std::allocator<int>()));
nonnull(std::allocate_shared<int>(std::allocator<int>(), 42));
nonnull(std::allocate_shared_for_overwrite<int>(std::allocator<int>()));
nonnull(
std::allocate_shared_for_overwrite<int[]>(std::allocator<int[]>(), 5));
}
TEST void staticPointerCast(Nonnull<std::shared_ptr<Base>> NonnullParam,
Nullable<std::shared_ptr<Base>> NullableParam,
std::shared_ptr<Base> UnknownParam) {
provable(std::static_pointer_cast<Derived>(std::shared_ptr<Base>()) ==
nullptr);
nonnull(std::static_pointer_cast<Derived>(NonnullParam));
nullable(std::static_pointer_cast<Derived>(NullableParam));
unknown(std::static_pointer_cast<Derived>(UnknownParam));
// Arguments are unchanged after calling const lvalue reference overload.
nonnull(NonnullParam);
nullable(NullableParam);
unknown(UnknownParam);
nonnull(std::static_pointer_cast<Derived>(std::move(NonnullParam)));
nullable(std::static_pointer_cast<Derived>(std::move(NullableParam)));
unknown(std::static_pointer_cast<Derived>(std::move(UnknownParam)));
// Arguments are empty after calling rvalue reference overload.
provable(!NonnullParam);
provable(!NullableParam);
provable(!UnknownParam);
}
TEST void dynamicPointerCast(Nonnull<std::shared_ptr<Base>> NonnullParam,
Nullable<std::shared_ptr<Base>> NullableParam,
std::shared_ptr<Base> UnknownParam) {
provable(std::dynamic_pointer_cast<Derived>(std::shared_ptr<Base>()) ==
nullptr);
nullable(std::dynamic_pointer_cast<Derived>(NonnullParam));
nullable(std::dynamic_pointer_cast<Derived>(NullableParam));
nullable(std::dynamic_pointer_cast<Derived>(UnknownParam));
// Arguments are unchanged after calling const lvalue reference overload.
nonnull(NonnullParam);
nullable(NullableParam);
unknown(UnknownParam);
nullable(std::dynamic_pointer_cast<Derived>(std::move(NonnullParam)));
nullable(std::dynamic_pointer_cast<Derived>(std::move(NullableParam)));
nullable(std::dynamic_pointer_cast<Derived>(std::move(UnknownParam)));
// Arguments are nullable (but not provably Null) after calling rvalue
// reference overload (because they may or may not have been moved from).
nullable(NonnullParam);
nullable(NullableParam);
nullable(UnknownParam);
possible(NonnullParam != nullptr);
possible(NullableParam != nullptr);
possible(UnknownParam != nullptr);
// However, if the argument was Null, then it should remain Null (and not just
// nullable) after calling the rvalue reference overload.
std::shared_ptr<Base> Null;
provable(std::dynamic_pointer_cast<Derived>(Null) == nullptr);
provable(Null == nullptr);
}
TEST void constPointerCast() {
// A `const_pointer_cast`, unlike the other cast types, will definitely
// produce a pointer with the same storage location as the source, so we can
// test this cast more easily than the others.
provable(std::const_pointer_cast<int>(std::shared_ptr<const int>()) ==
nullptr);
auto P = std::make_shared<const int>();
provable(std::const_pointer_cast<int>(P).get() == P.get());
provable(P != nullptr);
std::const_pointer_cast<int>(std::move(P));
provable(!P);
}
namespace reinterpret_pointer_cast {
// `S` and `S::I` are pointer-interconvertible.
struct S {
int I;
};
TEST void reinterpretPointerCast(Nonnull<std::shared_ptr<S>> NonnullParam,
Nullable<std::shared_ptr<S>> NullableParam,
std::shared_ptr<S> UnknownParam) {
// By the standard, the pointers we produce through `reinterpret_pointer_cast`
// in this test should have the same address, but the dataflow framework does
// not allow us to express this (as it requires different `StorageLocation`s
// for different types). Therefore, we need to test `reinterpret_pointer_cast`
// more indirectly, similar to `static_pointer_cast` and
// `dynamic_pointer_cast` above.
provable(std::reinterpret_pointer_cast<int>(std::shared_ptr<S>()) == nullptr);
nonnull(std::reinterpret_pointer_cast<int>(NonnullParam));
nullable(std::reinterpret_pointer_cast<int>(NullableParam));
unknown(std::reinterpret_pointer_cast<int>(UnknownParam));
// Arguments are unchanged after calling const lvalue reference overload.
nonnull(NonnullParam);
nullable(NullableParam);
unknown(UnknownParam);
nonnull(std::reinterpret_pointer_cast<int>(std::move(NonnullParam)));
nullable(std::reinterpret_pointer_cast<int>(std::move(NullableParam)));
unknown(std::reinterpret_pointer_cast<int>(std::move(UnknownParam)));
// Arguments are empty after calling rvalue reference overload.
provable(!NonnullParam);
provable(!NullableParam);
provable(!UnknownParam);
}
} // namespace reinterpret_pointer_cast
TEST void operatorEqualsAndNotEquals() {
// We perform this test on `shared_ptr` rather than `unique_ptr` because it
// allows us the test to be stronger: We can check that two different
// `shared_ptr`s with the same underlying Raw pointer compare equal. We can't
// test this with `unique_ptr` because it is, well, unique.
auto P1 = std::make_shared<int>();
auto P2 = std::make_shared<int>();
std::shared_ptr<int> Null;
provable(P1 == P1);
provable(P1 == std::shared_ptr<int>(P1));
provable(Null == std::shared_ptr<int>());
provable(P1 != P2);
provable(P1 != Null);
provable(P2 != Null);
provable(Null == nullptr);
provable(P1 != nullptr);
provable(nullptr == Null);
provable(nullptr != P1);
}
TEST void weakPtrLocReturnsNullable(std::shared_ptr<int> Shared) {
std::weak_ptr<int> Weak(Shared);
nullable(Weak.lock());
}
namespace user_defined_smart_pointers {
template <typename T>
struct UserDefinedSmartPointer {
using absl_nullability_compatible = void;
using pointer = T *;
pointer get() const;
};
TEST void userDefinedSmartPointers(
Nonnull<UserDefinedSmartPointer<int>> NonnullParam,
Nullable<UserDefinedSmartPointer<int>> NullableParam,
UserDefinedSmartPointer<int> UnknownParam) {
// Just spot-check some basic behaviors, as the implementation treats
// user-defined smart pointers like standard smart pointers, so the tests for
// standard smart pointers provide sufficient coverage.
nonnull(NonnullParam);
nullable(NullableParam);
unknown(UnknownParam);
nonnull(NonnullParam.get());
nullable(NullableParam.get());
unknown(UnknownParam.get());
}
template <typename T>
struct _Nullable UserDefinedSmartPointerWithAttribute {
using pointer = T *;
pointer get() const;
};
TEST void userDefinedSmartPointersWithAttribute(
UserDefinedSmartPointerWithAttribute<int> _Nonnull NonnullParam,
UserDefinedSmartPointerWithAttribute<int> _Nullable NullableParam,
UserDefinedSmartPointerWithAttribute<int> _Null_unspecified UnknownParam) {
// All that we really want to test here is that we can put nullability
// attributes on a smart pointer class that has the `_Nullable` attribute.
// But let's also spot-check that the nullability is set correctly.
nonnull(NonnullParam);
nullable(NullableParam);
unknown(UnknownParam);
}
} // namespace user_defined_smart_pointers
namespace derived_from_unique_ptr {
struct Allocator {};
struct S {};
template <typename T>
struct DerivedPtr : public std::unique_ptr<T> {
// Allocates a new object using the given allocator.
DerivedPtr(Allocator *);
};
// This is a crash repro. Make sure we can handle classes that are derived from
// smart pointers.
TEST void derivedFromUniquePtr(Nonnull<DerivedPtr<int>> Ptr) {
nonnull(Ptr);
nonnull(Ptr.get());
}
// This is a crash repro. Make sure we don't assume that `A` is the underlying
// pointer for the smart pointer.
TEST void constructorTakingUnrelatedPointer(Allocator *A) {
DerivedPtr<S> Ptr(A);
// This used to cause a crash because we would attempt to copy a record of
// type `S` to a record of type `Allocator`.
*Ptr = S();
}
template <typename T>
struct PrivatelyDerivedPtr : private std::unique_ptr<T> {
PrivatelyDerivedPtr();
using std::unique_ptr<T>::operator=;
};
// This is a crash repro. Make sure we can handle classes that are privately
// derived from smart pointers. We don't consider such a class itself to be a
// supported smart pointer type, but we need to model the pointer field because
// copy and assignment operations may copy to it.
TEST void privatelyDerivedFromUniquePtr(Nonnull<std::unique_ptr<int>> Ptr) {
PrivatelyDerivedPtr<int> Dest;
Dest = std::move(Ptr);
}
} // namespace derived_from_unique_ptr
namespace underlying_type_is_not_raw_pointer {
struct Deleter {
// Use a `shared_ptr` as the underlying pointer type. This wouldn't make a lot
// of sense in production code, but we use it in the test because we already
// have it available.
using pointer = std::shared_ptr<int>;
void operator()(pointer);
};
// This is a crash repro. Make sure we don't crash on a `unique_ptr` whose
// underlying `pointer` type is not a raw pointer.
// For the time being, we don't check these but silently ignore them; this seems
// acceptable, as this case is rare.
TEST Deleter::pointer underlyingTypeIsNotRawPointer() {
std::unique_ptr<int, Deleter> Ptr;
// TODO: Should be nullable, but we don't model this case.
unknown(Ptr.get());
return Ptr.get();
}
} // namespace underlying_type_is_not_raw_pointer