| // 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); | |
| auto* PrevNonnullParam = NonnullParam.get(); | |
| auto* PrevNullableParam = NullableParam.get(); | |
| auto* PrevUnknownParam = UnknownParam.get(); | |
| 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); | |
| // The pointer value may also have changed. | |
| possible(NonnullParam.get() != PrevNonnullParam); | |
| possible(NullableParam.get() != PrevNullableParam); | |
| possible(UnknownParam.get() != PrevUnknownParam); | |
| // 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 |