[K/N] Add tracking of application state to the GC scheduler.

Merge-request: KT-MR-6435
Merged-by: Alexander Shabalin <Alexander.Shabalin@jetbrains.com>
This commit is contained in:
Alexander Shabalin
2022-06-28 06:38:36 +00:00
committed by Space
parent fcfc79aa35
commit d47193d36f
21 changed files with 1246 additions and 22 deletions
@@ -25,6 +25,8 @@ RUNTIME_WEAK Kotlin_getSourceInfo_FunctionType Kotlin_getSourceInfo_Function = n
#ifdef KONAN_ANDROID
RUNTIME_WEAK int32_t Kotlin_printToAndroidLogcat = 1;
#endif
// Keep it 0 even when the compiler defaults to 1: if the overriding mechanism breaks, keeping it disabled is safer.
RUNTIME_WEAK int32_t Kotlin_appStateTracking = 0;
ALWAYS_INLINE compiler::DestroyRuntimeMode compiler::destroyRuntimeMode() noexcept {
return static_cast<compiler::DestroyRuntimeMode>(Kotlin_destroyRuntimeMode);
@@ -39,6 +41,9 @@ ALWAYS_INLINE bool compiler::suspendFunctionsFromAnyThreadFromObjCEnabled() noex
return Kotlin_suspendFunctionsFromAnyThreadFromObjC != 0;
}
ALWAYS_INLINE compiler::AppStateTracking compiler::appStateTracking() noexcept {
return static_cast<compiler::AppStateTracking>(Kotlin_appStateTracking);
}
#ifdef KONAN_ANDROID
ALWAYS_INLINE bool compiler::printToAndroidLogcat() noexcept {
@@ -52,4 +57,4 @@ ALWAYS_INLINE int compiler::getSourceInfo(void* addr, SourceInfo *result, int re
} else {
return Kotlin_getSourceInfo_Function(addr, result, result_size);
}
}
}
@@ -72,6 +72,11 @@ enum class GCSchedulerType {
kAggressive = 3,
};
// Must match AppStateTracking in AppStateTracking.kt
enum class AppStateTracking {
kDisabled = 0,
kEnabled = 1,
};
ALWAYS_INLINE inline bool shouldContainDebugInfo() noexcept {
return Kotlin_needDebugInfo != 0;
@@ -101,6 +106,7 @@ ALWAYS_INLINE inline GCSchedulerType getGCSchedulerType() noexcept {
WorkerExceptionHandling workerExceptionHandling() noexcept;
DestroyRuntimeMode destroyRuntimeMode() noexcept;
bool suspendFunctionsFromAnyThreadFromObjCEnabled() noexcept;
AppStateTracking appStateTracking() noexcept;
int getSourceInfo(void* addr, SourceInfo *result, int result_size) noexcept;
#ifdef KONAN_ANDROID
@@ -0,0 +1,44 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#pragma once
#if KONAN_HAS_FOUNDATION_FRAMEWORK
#include <functional>
#include <objc/objc.h>
#include "ObjCForward.hpp"
#include "ObjectPtr.hpp"
#include "Utils.hpp"
OBJC_FORWARD_DECLARE(NSNotificationCenter);
OBJC_FORWARD_DECLARE(NSString);
namespace kotlin::objc_support {
class NSNotificationSubscription : private MoveOnly {
public:
NSNotificationSubscription(NSNotificationCenter* center, NSString* name, std::function<void()> handler) noexcept;
NSNotificationSubscription(NSString* name, std::function<void()> handler) noexcept;
NSNotificationSubscription(NSNotificationSubscription&&) = default;
NSNotificationSubscription& operator=(NSNotificationSubscription&&) = default;
~NSNotificationSubscription() { reset(); }
void reset() noexcept;
bool subscribed() const noexcept;
explicit operator bool() const noexcept { return subscribed(); }
private:
object_ptr<NSNotificationCenter> center_;
// center_ will hold the strong reference to token_ anyway.
id token_;
};
} // namespace kotlin::objc_support
#endif
@@ -0,0 +1,39 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#if KONAN_HAS_FOUNDATION_FRAMEWORK
#include "NSNotificationSubscription.hpp"
#import <Foundation/NSNotification.h>
using namespace kotlin;
objc_support::NSNotificationSubscription::NSNotificationSubscription(
NSNotificationCenter* center, NSString* name, std::function<void()> handler) noexcept :
center_([center retain]),
token_([center addObserverForName:name
object:nil
queue:nil
usingBlock:^(NSNotification* notification) {
handler();
}]) {}
objc_support::NSNotificationSubscription::NSNotificationSubscription(NSString* name, std::function<void()> handler) noexcept :
NSNotificationSubscription([NSNotificationCenter defaultCenter], name, std::move(handler)) {}
bool objc_support::NSNotificationSubscription::subscribed() const noexcept {
return token_ != nil;
}
void objc_support::NSNotificationSubscription::reset() noexcept {
@autoreleasepool {
[*center_ removeObserver:token_];
center_.reset();
token_ = nil;
}
}
#endif
@@ -0,0 +1,228 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#if KONAN_HAS_FOUNDATION_FRAMEWORK
#include "NSNotificationSubscription.hpp"
#import <Foundation/NSNotification.h>
#import <Foundation/NSString.h>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "std_support/Memory.hpp"
using namespace kotlin;
using testing::_;
namespace {
class WithDestructorHook;
using DestructorHook = void(WithDestructorHook*);
class WithDestructorHook : private Pinned {
public:
explicit WithDestructorHook(std::function<DestructorHook> hook) : hook_(std::move(hook)) {}
~WithDestructorHook() { hook_(this); }
private:
std::function<DestructorHook> hook_;
};
} // namespace
@interface Kotlin_objc_support_NSNotificationSubscriptionTest : NSObject {
NSNotificationCenter* center_;
std::function<void()> handler_;
}
- (instancetype)initWithNotificationCenter:(NSNotificationCenter*)center
name:(NSNotificationName)name
handler:(std::function<void()>)handler;
- (void)reset;
- (void)onNotification:(NSNotification*)notification;
@end
@implementation Kotlin_objc_support_NSNotificationSubscriptionTest
- (instancetype)initWithNotificationCenter:(NSNotificationCenter*)center
name:(NSNotificationName)name
handler:(std::function<void()>)handler {
if ((self = [super init])) {
center_ = center;
handler_ = std::move(handler);
[center_ addObserver:self selector:@selector(onNotification:) name:name object:nil];
}
return self;
}
- (void)reset {
[center_ removeObserver:self];
}
- (void)onNotification:(NSNotification*)notification {
handler_();
}
@end
class NSNotificationSubscriptionTest : public testing::Test {
public:
objc_support::NSNotificationSubscription subscribe(const char* name, std::function<void()> handler) noexcept {
return objc_support::NSNotificationSubscription(center_, [NSString stringWithUTF8String:name], std::move(handler));
}
objc_support::object_ptr<Kotlin_objc_support_NSNotificationSubscriptionTest> subscribeOther(
const char* name, std::function<void()> handler) noexcept {
return objc_support::object_ptr<Kotlin_objc_support_NSNotificationSubscriptionTest>(
[[Kotlin_objc_support_NSNotificationSubscriptionTest alloc] initWithNotificationCenter:center_
name:[NSString stringWithUTF8String:name]
handler:std::move(handler)]);
}
void post(const char* name) noexcept { [center_ postNotificationName:[NSString stringWithUTF8String:name] object:nil]; }
private:
NSNotificationCenter* center_ = [[NSNotificationCenter alloc] init];
};
TEST_F(NSNotificationSubscriptionTest, Subscribed) {
constexpr const char* name = "NOTIFICATION_NAME";
testing::StrictMock<testing::MockFunction<void()>> handler;
auto subscription = subscribe(name, handler.AsStdFunction());
EXPECT_TRUE(subscription.subscribed());
EXPECT_TRUE(subscription);
subscription.reset();
EXPECT_FALSE(subscription.subscribed());
EXPECT_FALSE(subscription);
}
TEST_F(NSNotificationSubscriptionTest, Post) {
constexpr const char* name = "NOTIFICATION_NAME";
testing::StrictMock<testing::MockFunction<void()>> handler;
auto subscription = subscribe(name, handler.AsStdFunction());
EXPECT_CALL(handler, Call());
post(name);
testing::Mock::VerifyAndClearExpectations(&handler);
}
TEST_F(NSNotificationSubscriptionTest, PostWrongName) {
constexpr const char* name = "NOTIFICATION_NAME";
constexpr const char* wrongName = "NOTIFICATION_NAME_WRONG";
testing::StrictMock<testing::MockFunction<void()>> handler;
auto subscription = subscribe(name, handler.AsStdFunction());
EXPECT_CALL(handler, Call()).Times(0);
post(wrongName);
testing::Mock::VerifyAndClearExpectations(&handler);
}
TEST_F(NSNotificationSubscriptionTest, PostAfterReset) {
constexpr const char* name = "NOTIFICATION_NAME";
testing::StrictMock<testing::MockFunction<void()>> handler;
auto subscription = subscribe(name, handler.AsStdFunction());
subscription.reset();
EXPECT_CALL(handler, Call()).Times(0);
post(name);
testing::Mock::VerifyAndClearExpectations(&handler);
}
TEST_F(NSNotificationSubscriptionTest, PostAfterDtor) {
constexpr const char* name = "NOTIFICATION_NAME";
testing::StrictMock<testing::MockFunction<void()>> handler;
{
// Create and destroy subscription object.
subscribe(name, handler.AsStdFunction());
}
EXPECT_CALL(handler, Call()).Times(0);
post(name);
testing::Mock::VerifyAndClearExpectations(&handler);
}
TEST_F(NSNotificationSubscriptionTest, MultipleSubscribers) {
constexpr const char* name = "NOTIFICATION_NAME";
testing::StrictMock<testing::MockFunction<void()>> handler1;
testing::StrictMock<testing::MockFunction<void()>> handler2;
testing::StrictMock<testing::MockFunction<void()>> handler3;
testing::StrictMock<testing::MockFunction<void()>> handler4;
auto subscription1 = subscribeOther(name, handler1.AsStdFunction());
auto subscription2 = subscribe(name, handler2.AsStdFunction());
auto subscription3 = subscribe(name, handler3.AsStdFunction());
auto subscription4 = subscribeOther(name, handler4.AsStdFunction());
EXPECT_CALL(handler1, Call());
EXPECT_CALL(handler2, Call());
EXPECT_CALL(handler3, Call());
EXPECT_CALL(handler4, Call());
post(name);
testing::Mock::VerifyAndClearExpectations(&handler1);
testing::Mock::VerifyAndClearExpectations(&handler2);
testing::Mock::VerifyAndClearExpectations(&handler3);
testing::Mock::VerifyAndClearExpectations(&handler4);
subscription3.reset();
EXPECT_CALL(handler1, Call());
EXPECT_CALL(handler2, Call());
EXPECT_CALL(handler4, Call());
post(name);
testing::Mock::VerifyAndClearExpectations(&handler1);
testing::Mock::VerifyAndClearExpectations(&handler2);
testing::Mock::VerifyAndClearExpectations(&handler4);
[*subscription4 reset];
subscription4.reset();
EXPECT_CALL(handler1, Call());
EXPECT_CALL(handler2, Call());
post(name);
testing::Mock::VerifyAndClearExpectations(&handler1);
testing::Mock::VerifyAndClearExpectations(&handler2);
subscription2.reset();
EXPECT_CALL(handler1, Call());
post(name);
testing::Mock::VerifyAndClearExpectations(&handler1);
// Make sure to unsubscribe.
[*subscription1 reset];
}
TEST_F(NSNotificationSubscriptionTest, DestroysHandler) {
constexpr const char* name = "NOTIFICATION_NAME";
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
EXPECT_CALL(destructorHook, Call(_)).Times(0);
auto subscription =
subscribe(name, [withDestructorHook = std_support::make_shared<WithDestructorHook>(destructorHook.AsStdFunction())] {});
post(name);
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CALL(destructorHook, Call(_));
subscription.reset();
testing::Mock::VerifyAndClearExpectations(&destructorHook);
}
#endif
@@ -0,0 +1,12 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#pragma once
#ifdef __OBJC__
#define OBJC_FORWARD_DECLARE(clazz) @class clazz
#else
#define OBJC_FORWARD_DECLARE(clazz) class clazz
#endif
@@ -0,0 +1,164 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#pragma once
#if KONAN_HAS_FOUNDATION_FRAMEWORK
#if defined(__has_feature) && __has_feature(objc_arc)
#error "Assumes that ARC is not used"
#endif
#include <functional>
#include <utility>
#include "ObjCForward.hpp"
OBJC_FORWARD_DECLARE(NSObject);
namespace kotlin::objc_support {
namespace internal {
class ObjectPtrImpl {
public:
ObjectPtrImpl() noexcept;
explicit ObjectPtrImpl(NSObject* object) noexcept;
ObjectPtrImpl(const ObjectPtrImpl& rhs) noexcept;
ObjectPtrImpl(ObjectPtrImpl&& rhs) noexcept;
~ObjectPtrImpl();
ObjectPtrImpl& operator=(const ObjectPtrImpl& rhs) noexcept {
ObjectPtrImpl tmp(rhs);
swap(tmp);
return *this;
}
ObjectPtrImpl& operator=(ObjectPtrImpl&& rhs) noexcept {
ObjectPtrImpl tmp(std::move(rhs));
swap(tmp);
return *this;
}
void swap(ObjectPtrImpl& rhs) noexcept;
NSObject* get() const noexcept;
bool valid() const noexcept;
void reset() noexcept;
void reset(NSObject* object) noexcept;
bool operator==(const ObjectPtrImpl& rhs) const noexcept;
bool operator<(const ObjectPtrImpl& rhs) const noexcept;
std::size_t computeHash() const noexcept;
private:
NSObject* object_;
};
} // namespace internal
// `std::shared_ptr`-like smart pointer for ObjC objects.
//
// IMPORTANT: Must be constructed from a retained ObjC object.
// This optimizes for the common case of constructing directly from `[[T alloc] init...]`.
//
// Unlike a regular C++ smart pointer `operator*` does not return a reference but return a pointer
// for convenient syntax of calling ObjC methods: `[*smartPtr methodName]`.
// Use `(*smartPtr).propertyName` to access properties.
// Use `smartPtr->fieldName` to access fields.
//
// Implements hashes and comparisons making it suitable to store in associative containers.
// Equality comparison is shallow (does not use `[NSObject isEqual:]`) mirroring regular C++ smart pointers.
//
// When transfering ownership, prefer to move because it avoids calling `retain` and `release`.
template <typename T>
class object_ptr {
public:
// Construct empty `object_ptr`.
object_ptr() noexcept = default;
// Construct from ObjC object.
//
// IMPORTANT: this does not `retain` `ptr`.
explicit object_ptr(T* ptr) noexcept : impl_(ptr) {}
void swap(object_ptr<T>& rhs) noexcept { impl_.swap(rhs.impl_); }
T* get() const noexcept { return (T*)impl_.get(); }
T* operator*() const noexcept { return (T*)impl_.get(); }
T* operator->() const noexcept { return (T*)impl_.get(); }
explicit operator bool() const noexcept { return impl_.valid(); }
// Release stored pointer and become empty.
void reset() noexcept { impl_.reset(); }
// Release stored pointer and store `ptr` instead.
//
// IMPORTANT: this does not `retain` `ptr`.
void reset(T* ptr) noexcept { impl_.reset(ptr); }
template <typename U>
bool operator==(const object_ptr<U>& rhs) const noexcept {
return impl_ == rhs.impl_;
}
template <typename U>
bool operator<(const object_ptr<U>& rhs) const noexcept {
return impl_ < rhs.impl_;
}
private:
friend struct std::hash<object_ptr>;
template <typename U>
friend class object_ptr;
internal::ObjectPtrImpl impl_;
};
template <typename T, typename U>
bool operator!=(const object_ptr<T>& lhs, const object_ptr<U>& rhs) noexcept {
return !(lhs == rhs);
}
template <typename T, typename U>
bool operator>(const object_ptr<T>& lhs, const object_ptr<U>& rhs) noexcept {
return rhs < lhs;
}
template <typename T, typename U>
bool operator<=(const object_ptr<T>& lhs, const object_ptr<U>& rhs) noexcept {
return !(lhs > rhs);
}
template <typename T, typename U>
bool operator>=(const object_ptr<T>& lhs, const object_ptr<U>& rhs) noexcept {
return !(lhs < rhs);
}
} // namespace kotlin::objc_support
namespace std {
template <typename T>
void swap(kotlin::objc_support::object_ptr<T>& lhs, kotlin::objc_support::object_ptr<T>& rhs) noexcept {
lhs.swap(rhs);
}
template <typename T>
struct hash<kotlin::objc_support::object_ptr<T>> {
std::size_t operator()(const kotlin::objc_support::object_ptr<T>& value) { return std::hash<NSObject*>()(value.impl_.get()); }
};
// TODO: std::atomic specialization?
} // namespace std
#endif
@@ -0,0 +1,66 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#if KONAN_HAS_FOUNDATION_FRAMEWORK
#include "ObjectPtr.hpp"
#import <Foundation/NSObject.h>
using namespace kotlin;
objc_support::internal::ObjectPtrImpl::ObjectPtrImpl() noexcept : object_(nil) {}
objc_support::internal::ObjectPtrImpl::ObjectPtrImpl(NSObject* object) noexcept : object_(object) {}
objc_support::internal::ObjectPtrImpl::ObjectPtrImpl(const ObjectPtrImpl& rhs) noexcept : object_([rhs.object_ retain]) {}
objc_support::internal::ObjectPtrImpl::ObjectPtrImpl(ObjectPtrImpl&& rhs) noexcept : object_(rhs.object_) {
rhs.object_ = nil;
}
objc_support::internal::ObjectPtrImpl::~ObjectPtrImpl() {
@autoreleasepool {
[object_ release];
}
}
void objc_support::internal::ObjectPtrImpl::swap(ObjectPtrImpl& rhs) noexcept {
using std::swap;
swap(object_, rhs.object_);
}
NSObject* objc_support::internal::ObjectPtrImpl::get() const noexcept {
return object_;
}
bool objc_support::internal::ObjectPtrImpl::valid() const noexcept {
return object_ != nil;
}
void objc_support::internal::ObjectPtrImpl::reset() noexcept {
reset(nil);
}
void objc_support::internal::ObjectPtrImpl::reset(NSObject* object) noexcept {
@autoreleasepool {
[object_ release];
object_ = object;
}
}
std::size_t objc_support::internal::ObjectPtrImpl::computeHash() const noexcept {
return object_.hash;
}
bool objc_support::internal::ObjectPtrImpl::operator==(const ObjectPtrImpl& rhs) const noexcept {
return object_ == rhs.object_;
}
bool objc_support::internal::ObjectPtrImpl::operator<(const ObjectPtrImpl& rhs) const noexcept {
return std::less<>()(object_, rhs.object_);
}
#endif
@@ -0,0 +1,460 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#if KONAN_HAS_FOUNDATION_FRAMEWORK
#include "ObjectPtr.hpp"
#include <functional>
#import <Foundation/NSObject.h>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "std_support/Memory.hpp"
#include "Utils.hpp"
using namespace kotlin;
namespace {
class WithDestructorHook;
using DestructorHook = void(WithDestructorHook*);
class WithDestructorHook : private Pinned {
public:
explicit WithDestructorHook(std::function<DestructorHook> hook) : hook_(std::move(hook)) {}
~WithDestructorHook() { hook_(this); }
private:
std::function<DestructorHook> hook_;
};
} // namespace
@interface WithDestructorHookObjC : NSObject {
std_support::unique_ptr<WithDestructorHook> impl_;
}
@property(readonly) WithDestructorHook* impl;
- (instancetype)initWithDestructorHook:(std::function<DestructorHook>)hook;
@end
@implementation WithDestructorHookObjC
- (instancetype)initWithDestructorHook:(std::function<DestructorHook>)hook {
if ((self = [super init])) {
impl_ = std_support::make_unique<WithDestructorHook>(hook);
}
return self;
}
- (WithDestructorHook*)impl {
return impl_.get();
}
@end
#define EXPECT_CONTAINS(smartPtr, ptr) \
do { \
EXPECT_THAT(static_cast<bool>(smartPtr), (ptr) != nil); \
EXPECT_THAT(*(smartPtr), (ptr)); \
EXPECT_THAT((smartPtr).get(), (ptr)); \
} while (false)
TEST(ObjectPtrTest, DefaultCtor) {
objc_support::object_ptr<WithDestructorHookObjC> obj;
EXPECT_CONTAINS(obj, nil);
}
TEST(ObjectPtrTest, ObjectCtor) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
{
objc_support::object_ptr<WithDestructorHookObjC> obj(ptr);
EXPECT_CONTAINS(obj, ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl));
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
}
TEST(ObjectPtrTest, CopyCtor) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
{
objc_support::object_ptr<WithDestructorHookObjC> obj1(ptr);
objc_support::object_ptr<WithDestructorHookObjC> obj2(obj1);
EXPECT_CONTAINS(obj1, ptr);
EXPECT_CONTAINS(obj2, ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl)).Times(0);
obj1.reset();
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CONTAINS(obj1, nil);
EXPECT_CONTAINS(obj2, ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl));
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
}
TEST(ObjectPtrTest, MoveCtor) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
{
objc_support::object_ptr<WithDestructorHookObjC> obj1(ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl)).Times(0);
objc_support::object_ptr<WithDestructorHookObjC> obj2(std::move(obj1));
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CONTAINS(obj1, nil);
EXPECT_CONTAINS(obj2, ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl));
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
}
TEST(ObjectPtrTest, CopyAssignmentIntoEmpty) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
{
objc_support::object_ptr<WithDestructorHookObjC> obj2;
{
objc_support::object_ptr<WithDestructorHookObjC> obj1(ptr);
obj2 = obj1;
EXPECT_CONTAINS(obj1, ptr);
EXPECT_CONTAINS(obj2, ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl)).Times(0);
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CONTAINS(obj2, ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl));
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
}
TEST(ObjectPtrTest, CopyAssignmentIntoSet) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr1 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr2 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
{
objc_support::object_ptr<WithDestructorHookObjC> obj2(ptr2);
{
objc_support::object_ptr<WithDestructorHookObjC> obj1(ptr1);
EXPECT_CALL(destructorHook, Call(ptr2.impl));
obj2 = obj1;
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CONTAINS(obj1, ptr1);
EXPECT_CONTAINS(obj2, ptr1);
EXPECT_CALL(destructorHook, Call(ptr1.impl)).Times(0);
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CONTAINS(obj2, ptr1);
EXPECT_CALL(destructorHook, Call(ptr1.impl));
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
}
TEST(ObjectPtrTest, MoveAssignmentIntoEmpty) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
{
objc_support::object_ptr<WithDestructorHookObjC> obj2;
{
EXPECT_CALL(destructorHook, Call(ptr.impl)).Times(0);
objc_support::object_ptr<WithDestructorHookObjC> obj1(ptr);
obj2 = std::move(obj1);
EXPECT_CONTAINS(obj1, nil);
EXPECT_CONTAINS(obj2, ptr);
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CONTAINS(obj2, ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl));
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
}
TEST(ObjectPtrTest, MoveAssignmentIntoSet) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr1 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr2 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
{
objc_support::object_ptr<WithDestructorHookObjC> obj2(ptr2);
{
objc_support::object_ptr<WithDestructorHookObjC> obj1(ptr1);
EXPECT_CALL(destructorHook, Call(ptr2.impl));
EXPECT_CALL(destructorHook, Call(ptr1.impl)).Times(0);
obj2 = std::move(obj1);
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CONTAINS(obj1, nil);
EXPECT_CONTAINS(obj2, ptr1);
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CONTAINS(obj2, ptr1);
EXPECT_CALL(destructorHook, Call(ptr1.impl));
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
}
TEST(ObjectPtrTest, SwapEmptyEmpty) {
objc_support::object_ptr<WithDestructorHookObjC> obj1;
objc_support::object_ptr<WithDestructorHookObjC> obj2;
obj1.swap(obj2);
EXPECT_CONTAINS(obj1, nil);
EXPECT_CONTAINS(obj2, nil);
}
TEST(ObjectPtrTest, SwapEmptySet) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr2 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
objc_support::object_ptr<WithDestructorHookObjC> obj1;
objc_support::object_ptr<WithDestructorHookObjC> obj2(ptr2);
obj1.swap(obj2);
EXPECT_CONTAINS(obj1, ptr2);
EXPECT_CONTAINS(obj2, nil);
EXPECT_CALL(destructorHook, Call(ptr2.impl));
}
TEST(ObjectPtrTest, SwapSetEmpty) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr1 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
objc_support::object_ptr<WithDestructorHookObjC> obj1(ptr1);
objc_support::object_ptr<WithDestructorHookObjC> obj2;
obj1.swap(obj2);
EXPECT_CONTAINS(obj1, nil);
EXPECT_CONTAINS(obj2, ptr1);
EXPECT_CALL(destructorHook, Call(ptr1.impl));
}
TEST(ObjectPtrTest, SwapSetSet) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr1 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr2 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
objc_support::object_ptr<WithDestructorHookObjC> obj1(ptr1);
objc_support::object_ptr<WithDestructorHookObjC> obj2(ptr2);
obj1.swap(obj2);
EXPECT_CONTAINS(obj1, ptr2);
EXPECT_CONTAINS(obj2, ptr1);
EXPECT_CALL(destructorHook, Call(ptr1.impl));
EXPECT_CALL(destructorHook, Call(ptr2.impl));
}
TEST(ObjectPtrTest, ResetEmptyFromEmpty) {
objc_support::object_ptr<WithDestructorHookObjC> obj;
obj.reset();
EXPECT_CONTAINS(obj, nil);
}
TEST(ObjectPtrTest, ResetEmptyFromSet) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
objc_support::object_ptr<WithDestructorHookObjC> obj(ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl));
obj.reset();
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CONTAINS(obj, nil);
}
TEST(ObjectPtrTest, ResetObjectFromEmpty) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
objc_support::object_ptr<WithDestructorHookObjC> obj;
obj.reset(ptr);
EXPECT_CONTAINS(obj, ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl));
}
TEST(ObjectPtrTest, ResetObjectFromSet) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr1 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr2 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
objc_support::object_ptr<WithDestructorHookObjC> obj(ptr1);
EXPECT_CALL(destructorHook, Call(ptr1.impl));
obj.reset(ptr2);
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CONTAINS(obj, ptr2);
EXPECT_CALL(destructorHook, Call(ptr2.impl));
}
#define EXPECT_EQUAL(expr1, expr2) \
do { \
EXPECT_TRUE((expr1) == (expr2)); \
EXPECT_FALSE((expr1) != (expr2)); \
EXPECT_FALSE((expr1) < (expr2)); \
EXPECT_TRUE((expr1) <= (expr2)); \
EXPECT_FALSE((expr1) > (expr2)); \
EXPECT_TRUE((expr1) >= (expr2)); \
} while (false)
#define EXPECT_LESS(expr1, expr2) \
do { \
EXPECT_FALSE((expr1) == (expr2)); \
EXPECT_TRUE((expr1) != (expr2)); \
EXPECT_TRUE((expr1) < (expr2)); \
EXPECT_TRUE((expr1) <= (expr2)); \
EXPECT_FALSE((expr1) > (expr2)); \
EXPECT_FALSE((expr1) >= (expr2)); \
} while (false)
#define EXPECT_GREATER(expr1, expr2) \
do { \
EXPECT_FALSE((expr1) == (expr2)); \
EXPECT_TRUE((expr1) != (expr2)); \
EXPECT_FALSE((expr1) < (expr2)); \
EXPECT_FALSE((expr1) <= (expr2)); \
EXPECT_TRUE((expr1) > (expr2)); \
EXPECT_TRUE((expr1) >= (expr2)); \
} while (false)
TEST(ObjectPtrTest, ComparisonsEmpty) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
objc_support::object_ptr<WithDestructorHookObjC> empty;
objc_support::object_ptr<WithDestructorHookObjC> reset(ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl));
reset.reset();
testing::Mock::VerifyAndClearExpectations(&destructorHook);
objc_support::object_ptr<NSObject> emptyOtherType;
EXPECT_EQUAL(empty, empty);
EXPECT_EQUAL(empty, objc_support::object_ptr<WithDestructorHookObjC>());
EXPECT_EQUAL(empty, reset);
EXPECT_EQUAL(empty, emptyOtherType);
EXPECT_EQUAL(reset, empty);
EXPECT_EQUAL(reset, reset);
EXPECT_EQUAL(reset, emptyOtherType);
}
TEST(ObjectPtrTest, ComparisonsEmptySet) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr1 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr2 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr3 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
objc_support::object_ptr<WithDestructorHookObjC> obj(ptr1);
objc_support::object_ptr<WithDestructorHookObjC> empty;
objc_support::object_ptr<WithDestructorHookObjC> reset(ptr2);
EXPECT_CALL(destructorHook, Call(ptr2.impl));
reset.reset();
testing::Mock::VerifyAndClearExpectations(&destructorHook);
objc_support::object_ptr<NSObject> emptyOtherType;
objc_support::object_ptr<WithDestructorHookObjC> objOtherType(ptr3);
EXPECT_GREATER(obj, empty);
EXPECT_GREATER(obj, reset);
EXPECT_GREATER(obj, emptyOtherType);
EXPECT_LESS(empty, obj);
EXPECT_LESS(empty, objOtherType);
EXPECT_LESS(reset, obj);
EXPECT_LESS(reset, objOtherType);
EXPECT_GREATER(objOtherType, empty);
EXPECT_GREATER(objOtherType, reset);
EXPECT_CALL(destructorHook, Call(ptr1.impl));
EXPECT_CALL(destructorHook, Call(ptr3.impl));
}
TEST(ObjectPtrTest, ComparisonsSetSet) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr1 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr2 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
if (ptr2 < ptr1) {
std::swap(ptr1, ptr2);
}
ASSERT_LT(ptr1, ptr2);
objc_support::object_ptr<WithDestructorHookObjC> obj1(ptr1);
objc_support::object_ptr<WithDestructorHookObjC> obj2(ptr2);
objc_support::object_ptr<WithDestructorHookObjC> obj3(obj1);
objc_support::object_ptr<NSObject> objOtherType([ptr1 retain]);
EXPECT_EQUAL(obj1, obj1);
EXPECT_LESS(obj1, obj2);
EXPECT_EQUAL(obj1, obj3);
EXPECT_EQUAL(obj1, objOtherType);
EXPECT_GREATER(obj2, obj1);
EXPECT_EQUAL(obj2, obj2);
EXPECT_GREATER(obj2, obj3);
EXPECT_GREATER(obj2, objOtherType);
EXPECT_EQUAL(obj3, obj1);
EXPECT_LESS(obj3, obj2);
EXPECT_EQUAL(obj3, obj3);
EXPECT_EQUAL(obj3, objOtherType);
EXPECT_EQUAL(objOtherType, obj1);
EXPECT_LESS(objOtherType, obj2);
EXPECT_EQUAL(objOtherType, obj3);
EXPECT_EQUAL(objOtherType, objOtherType);
EXPECT_CALL(destructorHook, Call(ptr1.impl));
EXPECT_CALL(destructorHook, Call(ptr2.impl));
}
TEST(ObjectPtrTest, Hashing) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr1 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr2 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
objc_support::object_ptr<WithDestructorHookObjC> obj(ptr1);
objc_support::object_ptr<WithDestructorHookObjC> empty;
objc_support::object_ptr<WithDestructorHookObjC> reset(ptr2);
EXPECT_CALL(destructorHook, Call(ptr2.impl));
reset.reset();
testing::Mock::VerifyAndClearExpectations(&destructorHook);
using Hash = std::hash<objc_support::object_ptr<WithDestructorHookObjC>>;
using HashImpl = std::hash<NSObject*>;
EXPECT_THAT(Hash()(obj), HashImpl()(ptr1));
EXPECT_THAT(Hash()(empty), HashImpl()(nullptr));
EXPECT_THAT(Hash()(reset), HashImpl()(nullptr));
EXPECT_CALL(destructorHook, Call(ptr1.impl));
}
#endif