blob: 033186b251fb02b203a2b1b53130f7fa4b574cf0 [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 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 <type_traits>
#include <utility>
#include "nullability_test.h"
_Nonnull std::unique_ptr<int> makeNonnull();
_Nullable std::unique_ptr<int> makeNullable();
std::unique_ptr<int> makeUnknown();
int *_Nonnull makeNonnullRaw();
int *_Nullable 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 constructorWithOpaqueValueExprSource(bool b) {
// This is a crash repro, potentially because we are missing storage locations
// through `OpaqueValueExpr` (TODO(b/444678008)). To repro, we use a GNU
// extension of ?: where the second operand is missing, defaulting to the
// first operand.
std::unique_ptr<int> NonnullWithGnuExtension(
makeNonnull() ?: std::make_unique<int>(10));
// In contrast, this uses the normal ?: operator and appears to get
// copy-elision, so doesn't have the `OpaqueValueExpr` and no
// CXXConstructExpr at all.
std::unique_ptr<int> NonnullWithNormalConditionalOperator(
makeNonnull() ? makeNonnull() : std::make_unique<int>(10));
nonnull(NonnullWithNormalConditionalOperator);
}
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 makeSmartPointerToRawPointer() {
nullable(*std::make_shared<int *>(nullptr));
nullable(*std::make_shared<int *>(nullptr).get());
int *Raw = nullptr;
nullable(*std::make_shared<int *>(Raw));
nullable(*std::make_shared<int *>(Raw).get());
int I;
Raw = &I;
nonnull(*std::make_shared<int *>(Raw));
nonnull(*std::make_shared<int *>(Raw).get());
}
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 _Nullable UserDefinedSmartPointer {
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. This also tests that
// we can put nullability specifiers on a smart pointer class that has the
// `_Nullable` attribute.
nonnull(NonnullParam);
nullable(NullableParam);
unknown(UnknownParam);
nonnull(NonnullParam.get());
nullable(NullableParam.get());
unknown(UnknownParam.get());
}
template <typename T>
using Nonnull = _Nonnull T;
template <typename T>
using Nullable = _Nullable T;
TEST void userDefinedSmartPointersWithAliasAnnotation(
Nonnull<UserDefinedSmartPointer<int>> NonnullParam,
Nullable<UserDefinedSmartPointer<int>> NullableParam,
UserDefinedSmartPointer<int> UnknownParam) {
// Spot-check that the nullability is set correctly when the specifier is
// applied to a user-defined smart pointer by a template alias.
nonnull(NonnullParam);
nullable(NullableParam);
unknown(UnknownParam);
}
struct NonPointer {};
template <typename T>
struct _Nullable UserDefinedSmartPointerWithNonPointerRHSAssignOp {
UserDefinedSmartPointerWithNonPointerRHSAssignOp &operator=(
const NonPointer &);
};
TEST void userDefinedSmartPointersWithNonPointerRHSAssignOp(NonPointer Param) {
UserDefinedSmartPointerWithNonPointerRHSAssignOp<int> Local;
unknown(Local = Param);
}
// An example where operator== takes a raw pointer argument instead of
// std::nullptr_t for comparison to `nullptr` literals.
struct _Nullable WithCompareRawPointer {
using pointer = int*;
WithCompareRawPointer();
};
bool operator==(const WithCompareRawPointer& X, const WithCompareRawPointer& Y);
bool operator==(const WithCompareRawPointer& X, int* Y);
bool operator!=(const WithCompareRawPointer& X, int* Y);
TEST void userDefinedSmartPointersWithCompareRawPointer(
_Nonnull WithCompareRawPointer NonnullParam,
_Nullable WithCompareRawPointer NullableParam,
int* _Nonnull NonnullRawPointer) {
// This is a crash repro -- make sure that when the argument is literal
// `nullptr`, the analysis looks through the cast from `nullptr_t` to `int*`.
WithCompareRawPointer Null;
provable(Null == nullptr);
provable(NonnullParam != nullptr);
possible(NullableParam == nullptr);
possible(NullableParam != nullptr);
// Other things can prove.
provable(Null == WithCompareRawPointer());
provable(NonnullParam == NonnullParam);
// Compare to a raw pointer that isn't literal nullptr
// (we don't model for now, just don't want to crash)
NonnullParam == NonnullRawPointer;
NonnullParam != NonnullRawPointer;
}
// Assignable from a smart pointer where the operator= is the more usual one
// (param is a reference).
template <typename T>
struct _Nullable AssignFromUniquePtrRvalueRef {
using pointer = T*;
AssignFromUniquePtrRvalueRef();
AssignFromUniquePtrRvalueRef& operator=(std::unique_ptr<T>&& P);
};
// Assignable from a smart pointer where the operator= is the less usual
// (param is a value).
template <typename T>
struct _Nullable AssignFromUniquePtrValue {
using pointer = T*;
AssignFromUniquePtrValue();
AssignFromUniquePtrValue& operator=(std::unique_ptr<T> P);
};
TEST void assignOperatorByRefOrByValue() {
AssignFromUniquePtrRvalueRef<int> TakingRvalueRef;
TakingRvalueRef = std::make_unique<int>();
nonnull(TakingRvalueRef);
AssignFromUniquePtrValue<int> TakingValue;
TakingValue = std::make_unique<int>();
nonnull(TakingValue);
}
} // namespace user_defined_smart_pointers
namespace derived_from_unique_ptr {
struct Allocator {};
struct S {};
template <typename T>
struct _Nullable 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
namespace unusual_smart_pointer_type {
// This is a crash repro.
// 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.
template <class T>
struct _Nullable UnusualSmartPointer {
// A more "usual" smart pointer type, such as `std::unique_ptr`, would return
// `T*` from `operator->()`, `get()`, and `release()`, and `T&` from
// `operator*()`.
T operator->() const;
std::remove_pointer_t<T> operator*() const;
T get() const;
T release();
};
struct S {
void nonConstMemberFunction();
};
TEST void unusualSmartPointerType() {
UnusualSmartPointer<S *> Ptr;
// We shouldn't crash while analyzing these calls.
Ptr->nonConstMemberFunction();
(*Ptr).nonConstMemberFunction();
Ptr.get()->nonConstMemberFunction();
Ptr.release()->nonConstMemberFunction();
}
// Similar to `UnusualSmartPointer`, but define the operators in a base class
// and only mark the child class as a smart pointer.
template <class T>
class UnusualSmartPointerBase {
public:
T operator->() const;
std::remove_pointer_t<T> operator*() const;
T get() const;
T release();
};
template <class T>
class _Nullable UnusualSmartPointerOperatorsInBase
: public UnusualSmartPointerBase<T>{};
TEST void unusualSmartPointerTypeOperatorsInBase() {
UnusualSmartPointerOperatorsInBase<S *> Ptr;
// We shouldn't crash while analyzing these calls.
Ptr->nonConstMemberFunction();
(*Ptr).nonConstMemberFunction();
Ptr.get()->nonConstMemberFunction();
Ptr.release()->nonConstMemberFunction();
}
// An unusual smart pointer type which indicates that the `pointer` type
// is `void*`. Perhaps later it is possible to convert to a `T*`, but
// that conversion is hidden from our analysis.
template <class T>
struct _Nullable VoidStarSmartPointer {
using pointer = void*;
void* get() const;
};
// An unusual smart pointer type that can be constructed from
// a VoidStarSmartPointer (or assigned, swapped, or reset).
// Likely, to make this work, the operations do more than just copy the
// underlying pointers around, but that is invisible to the analysis.
//
// The operator*, operator-> would have type checked, but if we naively copied
// the underlying pointers directly, it would break assumptions about the types.
template <class T>
struct _Nullable NonVoidStarSmartPointer {
using pointer = T*;
NonVoidStarSmartPointer(VoidStarSmartPointer<T> & Other);
NonVoidStarSmartPointer& operator=(const VoidStarSmartPointer<T>&);
NonVoidStarSmartPointer& operator=(const VoidStarSmartPointer<T>&&);
NonVoidStarSmartPointer();
void swap(VoidStarSmartPointer<T> & Other);
void reset(void* P = nullptr);
T* operator->() const;
T& operator*() const;
T* get() const;
};
TEST void copyFromVoidStarSmartPointerType(VoidStarSmartPointer<S> Void) {
NonVoidStarSmartPointer<S> NonVoid(Void);
// We shouldn't crash while analyzing these calls (copying the incorrectly
// typed `void*` pointer from Arg to Ptr, and using it in subsequent
// analysis steps).
NonVoid->nonConstMemberFunction();
(*NonVoid).nonConstMemberFunction();
NonVoid.get()->nonConstMemberFunction();
NonVoid = Void;
NonVoid->nonConstMemberFunction();
NonVoid = std::move(Void);
NonVoid->nonConstMemberFunction();
VoidStarSmartPointer<S> FreshVoid;
NonVoid.reset(FreshVoid.get());
NonVoid->nonConstMemberFunction();
NonVoid.swap(FreshVoid);
NonVoid->nonConstMemberFunction();
}
// Test what happens if you have a copy involving an incomplete type.
// Since the code is dealing with pointers and this could be used only for
// a nullptr it may seem fine. But we need to query "isDerivedFrom" to check
// if the copy is okay, and isDerivedFrom will crash on incomplete types.
struct IncompleteType;
struct _Nullable NullOnlyPointer {
using pointer = IncompleteType*;
NullOnlyPointer();
};
template <class T>
struct _Nullable CopyableFromNullOnlyPointer {
using pointer = T*;
CopyableFromNullOnlyPointer(NullOnlyPointer Null);
pointer get() const;
};
TEST void copyFromNullOnlyPointerWithIncompleteType(NullOnlyPointer Null) {
CopyableFromNullOnlyPointer<S> Dest(Null);
Dest.get()->nonConstMemberFunction();
}
} // namespace unusual_smart_pointer_type
// Check non-member operator overloading (check if we have assumptions
// about presence of receiver arg that may be a pointer type, etc.).
namespace free_standing_operator_calls {
struct A {
explicit A(int X) : X(X) {}
int X;
};
static _Nonnull std::unique_ptr<A> operator*(std::unique_ptr<A> L,
std::unique_ptr<A> R) {
return std::make_unique<A>(L->X * R->X);
}
TEST void nonMemberBinaryOperatorStar() {
auto P1 = std::make_unique<A>(1);
auto P2 = std::make_unique<A>(2);
nonnull(std::move(P1) * std::move(P2));
}
template <class T>
struct _Nullable MySmartPtr {
using pointer = T *;
T *get() const;
T *Ptr;
};
// A bit unusual, but one can define a non-member operator*.
template <typename T>
static T &operator*(MySmartPtr<T> P) {
return *P.Ptr;
}
TEST void nonMemberUnaryOperatorStar() {
int X = 42;
int *_Nonnull PtrToX = &X;
MySmartPtr<int *> NonNull = {&PtrToX};
provable(NonNull.get() == &*NonNull);
}
} // namespace free_standing_operator_calls