[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
@@ -0,0 +1,17 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.konan
/**
* Controls whether K/N runtime is allowed track application state.
*
* Can be turned off via [BinaryOptions] to workaround bugs in implementation.
*/
// Must match `AppStateTracking` in CompilerConstants.hpp
enum class AppStateTracking(val value: Int) {
DISABLED(0),
ENABLED(1),
}
@@ -35,6 +35,8 @@ object BinaryOptions : BinaryOptionRegistry() {
val bundleId by stringOption()
val bundleShortVersionString by stringOption()
val bundleVersion by stringOption()
val appStateTracking by option<AppStateTracking>()
}
open class BinaryOption<T : Any>(
@@ -163,6 +163,10 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
get() = configuration.get(KonanConfigKeys.VERIFY_COMPILER) ?:
(optimizationsEnabled || CompilerVersion.CURRENT.meta != MetaVersion.RELEASE)
val appStateTracking: AppStateTracking by lazy {
configuration.get(BinaryOptions.appStateTracking) ?: AppStateTracking.DISABLED
}
init {
if (!platformManager.isEnabled(target)) {
error("Target ${target.visibleName} is not available on the ${HostManager.hostName} host")
@@ -2770,6 +2770,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
val programType = configuration.get(BinaryOptions.androidProgramType) ?: AndroidProgramType.Default
overrideRuntimeGlobal("Kotlin_printToAndroidLogcat", Int32(if (programType.consolePrintsToLogcat) 1 else 0))
}
overrideRuntimeGlobal("Kotlin_appStateTracking", Int32(context.config.appStateTracking.value))
}
//-------------------------------------------------------------------------//
+21 -21
View File
@@ -131,7 +131,7 @@ linkerDynamicFlags.macos_x64 = -dylib
osVersionMinFlagLd.macos_x64 = -macosx_version_min
osVersionMin.macos_x64 = 10.13
runtimeDefinitions.macos_x64 = KONAN_OSX=1 KONAN_MACOSX=1 KONAN_X64=1 KONAN_OBJC_INTEROP=1
runtimeDefinitions.macos_x64 = KONAN_OSX=1 KONAN_MACOSX=1 KONAN_X64=1 KONAN_OBJC_INTEROP=1 KONAN_HAS_FOUNDATION_FRAMEWORK=1
dependencies.macos_x64 = \
lldb-4-macos
@@ -170,7 +170,7 @@ linkerDynamicFlags.macos_arm64 = -dylib
osVersionMinFlagLd.macos_arm64 = -macosx_version_min
osVersionMin.macos_arm64 = 11.0
runtimeDefinitions.macos_arm64 = KONAN_OSX=1 KONAN_MACOSX=1 KONAN_ARM64=1 KONAN_OBJC_INTEROP=1
runtimeDefinitions.macos_arm64 = KONAN_OSX=1 KONAN_MACOSX=1 KONAN_ARM64=1 KONAN_OBJC_INTEROP=1 KONAN_HAS_FOUNDATION_FRAMEWORK=1
dependencies.macos_arm64 = \
lldb-4-macos
@@ -203,7 +203,7 @@ llvmInlineThreshold.ios_arm32 = 50
linkerNoDebugFlags.ios_arm32 = -S
stripFlags.ios_arm32 = -S
linkerDynamicFlags.ios_arm32 = -dylib
linkerKonanFlags.ios_arm32 = -lSystem -lc++ -lobjc -framework Foundation -sdk_version 15.0
linkerKonanFlags.ios_arm32 = -lSystem -lc++ -lobjc -framework Foundation -framework UIKit -sdk_version 15.0
linkerOptimizationFlags.ios_arm32 = -dead_strip
osVersionMinFlagLd.ios_arm32 = -iphoneos_version_min
osVersionMin.ios_arm32 = 9.0
@@ -214,7 +214,7 @@ osVersionMin.ios_arm32 = 9.0
# See https://github.com/ktorio/ktor/issues/941 for the context.
runtimeDefinitions.ios_arm32 = KONAN_OBJC_INTEROP=1 KONAN_IOS KONAN_ARM32=1 \
KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG=1 MACHSIZE=32 \
KONAN_NO_64BIT_ATOMIC=1 KONAN_NO_UNALIGNED_ACCESS=1
KONAN_NO_64BIT_ATOMIC=1 KONAN_NO_UNALIGNED_ACCESS=1 KONAN_HAS_FOUNDATION_FRAMEWORK=1 KONAN_HAS_UIKIT_FRAMEWORK=1
# Apple's 64-bit iOS.
targetToolchain.macos_x64-ios_arm64 = target-toolchain-xcode_13_1
@@ -237,12 +237,12 @@ clangDebugFlags.ios_arm64 = -O0 -mllvm -fast-isel=false -mllvm -global-isel=fals
linkerNoDebugFlags.ios_arm64 = -S
stripFlags.ios_arm64 = -S
linkerDynamicFlags.ios_arm64 = -dylib
linkerKonanFlags.ios_arm64 = -lSystem -lc++ -lobjc -framework Foundation -sdk_version 15.0
linkerKonanFlags.ios_arm64 = -lSystem -lc++ -lobjc -framework Foundation -framework UIKit -sdk_version 15.0
linkerOptimizationFlags.ios_arm64 = -dead_strip
osVersionMinFlagLd.ios_arm64 = -iphoneos_version_min
osVersionMin.ios_arm64 = 9.0
runtimeDefinitions.ios_arm64 = KONAN_OBJC_INTEROP=1 KONAN_IOS=1 KONAN_ARM64=1 \
KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG=1 MACHSIZE=64
KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG=1 MACHSIZE=64 KONAN_HAS_FOUNDATION_FRAMEWORK=1 KONAN_HAS_UIKIT_FRAMEWORK=1
additionalCacheFlags.ios_arm64 = -Xembed-bitcode-marker
# Apple's iOS simulator.
@@ -260,14 +260,14 @@ clangNooptFlags.ios_x64 = -O1
clangOptFlags.ios_x64 = -O3
clangDebugFlags.ios_x64 = -O0
linkerKonanFlags.ios_x64 = -lSystem -lc++ -lobjc -framework Foundation -sdk_version 15.0
linkerKonanFlags.ios_x64 = -lSystem -lc++ -lobjc -framework Foundation -framework UIKit -sdk_version 15.0
linkerOptimizationFlags.ios_x64 = -dead_strip
linkerNoDebugFlags.ios_x64 = -S
stripFlags.ios_x64 = -S
linkerDynamicFlags.ios_x64 = -dylib
osVersionMinFlagLd.ios_x64 = -ios_simulator_version_min
osVersionMin.ios_x64 = 9.0
runtimeDefinitions.ios_x64 = KONAN_OBJC_INTEROP=1 KONAN_IOS=1 KONAN_X64=1
runtimeDefinitions.ios_x64 = KONAN_OBJC_INTEROP=1 KONAN_IOS=1 KONAN_X64=1 KONAN_HAS_FOUNDATION_FRAMEWORK=1 KONAN_HAS_UIKIT_FRAMEWORK=1
# iOS simulator on Apple Silicon
@@ -285,14 +285,14 @@ clangNooptFlags.ios_simulator_arm64 = -O1
clangOptFlags.ios_simulator_arm64 = -O3
clangDebugFlags.ios_simulator_arm64 = -O0 -mllvm -fast-isel=false -mllvm -global-isel=false
linkerKonanFlags.ios_simulator_arm64 = -lSystem -lc++ -lobjc -framework Foundation -sdk_version 15.0
linkerKonanFlags.ios_simulator_arm64 = -lSystem -lc++ -lobjc -framework Foundation -framework UIKit -sdk_version 15.0
linkerOptimizationFlags.ios_simulator_arm64 = -dead_strip
linkerNoDebugFlags.ios_simulator_arm64 = -S
stripFlags.ios_simulator_arm64 = -S
linkerDynamicFlags.ios_simulator_arm64 = -dylib
osVersionMinFlagLd.ios_simulator_arm64 = -ios_simulator_version_min
osVersionMin.ios_simulator_arm64 = 9.0
runtimeDefinitions.ios_simulator_arm64 = KONAN_OBJC_INTEROP=1 KONAN_IOS=1 KONAN_ARM64=1
runtimeDefinitions.ios_simulator_arm64 = KONAN_OBJC_INTEROP=1 KONAN_IOS=1 KONAN_ARM64=1 KONAN_HAS_FOUNDATION_FRAMEWORK=1 KONAN_HAS_UIKIT_FRAMEWORK=1
# Apple's tvOS simulator.
targetToolchain.macos_x64-tvos_x64 = target-toolchain-xcode_13_1
@@ -309,14 +309,14 @@ clangNooptFlags.tvos_x64 = -O1
clangOptFlags.tvos_x64 = -O3
clangDebugFlags.tvos_x64 = -O0
linkerKonanFlags.tvos_x64 = -lSystem -lc++ -lobjc -framework Foundation -sdk_version 15.0
linkerKonanFlags.tvos_x64 = -lSystem -lc++ -lobjc -framework Foundation -framework UIKit -sdk_version 15.0
linkerOptimizationFlags.tvos_x64 = -dead_strip
linkerNoDebugFlags.tvos_x64 = -S
stripFlags.tvos_x64 = -S
linkerDynamicFlags.tvos_x64 = -dylib
osVersionMinFlagLd.tvos_x64 = -tvos_simulator_version_min
osVersionMin.tvos_x64 = 9.0
runtimeDefinitions.tvos_x64 = KONAN_OBJC_INTEROP=1 KONAN_TVOS=1 KONAN_X64=1
runtimeDefinitions.tvos_x64 = KONAN_OBJC_INTEROP=1 KONAN_TVOS=1 KONAN_X64=1 KONAN_HAS_FOUNDATION_FRAMEWORK=1 KONAN_HAS_UIKIT_FRAMEWORK=1
# Apple's tvOS simulator.
targetToolchain.macos_x64-tvos_simulator_arm64 = target-toolchain-xcode_13_1
@@ -333,14 +333,14 @@ clangNooptFlags.tvos_simulator_arm64 = -O1
clangOptFlags.tvos_simulator_arm64 = -O3
clangDebugFlags.tvos_simulator_arm64 = -O0 -mllvm -fast-isel=false -mllvm -global-isel=false
linkerKonanFlags.tvos_simulator_arm64 = -lSystem -lc++ -lobjc -framework Foundation -sdk_version 15.0
linkerKonanFlags.tvos_simulator_arm64 = -lSystem -lc++ -lobjc -framework Foundation -framework UIKit -sdk_version 15.0
linkerOptimizationFlags.tvos_simulator_arm64 = -dead_strip
linkerNoDebugFlags.tvos_simulator_arm64 = -S
stripFlags.tvos_simulator_arm64 = -S
linkerDynamicFlags.tvos_simulator_arm64 = -dylib
osVersionMinFlagLd.tvos_simulator_arm64 = -tvos_simulator_version_min
osVersionMin.tvos_simulator_arm64 = 9.0
runtimeDefinitions.tvos_simulator_arm64 = KONAN_OBJC_INTEROP=1 KONAN_TVOS=1 KONAN_ARM64=1
runtimeDefinitions.tvos_simulator_arm64 = KONAN_OBJC_INTEROP=1 KONAN_TVOS=1 KONAN_ARM64=1 KONAN_HAS_FOUNDATION_FRAMEWORK=1 KONAN_HAS_UIKIT_FRAMEWORK=1
# Apple's 64-bit tvOS.
targetToolchain.macos_x64-tvos_arm64 = target-toolchain-xcode_13_1
@@ -360,12 +360,12 @@ clangDebugFlags.tvos_arm64 = -O0 -mllvm -fast-isel=false -mllvm -global-isel=fal
linkerNoDebugFlags.tvos_arm64 = -S
stripFlags.tvos_arm64 = -S
linkerDynamicFlags.tvos_arm64 = -dylib
linkerKonanFlags.tvos_arm64 = -lSystem -lc++ -lobjc -framework Foundation -sdk_version 15.0
linkerKonanFlags.tvos_arm64 = -lSystem -lc++ -lobjc -framework Foundation -framework UIKit -sdk_version 15.0
linkerOptimizationFlags.tvos_arm64 = -dead_strip
osVersionMinFlagLd.tvos_arm64 = -tvos_version_min
osVersionMin.tvos_arm64 = 9.0
runtimeDefinitions.tvos_arm64 = KONAN_OBJC_INTEROP=1 KONAN_TVOS=1 KONAN_ARM64=1 \
KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG=1 MACHSIZE=64
KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG=1 MACHSIZE=64 KONAN_HAS_FOUNDATION_FRAMEWORK=1 KONAN_HAS_UIKIT_FRAMEWORK=1
# watchOS armv7k
targetToolchain.macos_x64-watchos_arm32 = target-toolchain-xcode_13_1
@@ -393,7 +393,7 @@ osVersionMin.watchos_arm32 = 5.0
# Regarding KONAN_NO_64BIT_ATOMIC=1: see explanation for ios_arm32 above.
runtimeDefinitions.watchos_arm32 = KONAN_OBJC_INTEROP=1 KONAN_WATCHOS KONAN_ARM32=1 \
KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG=1 \
MACHSIZE=32 KONAN_NO_64BIT_ATOMIC=1 KONAN_NO_UNALIGNED_ACCESS=1
MACHSIZE=32 KONAN_NO_64BIT_ATOMIC=1 KONAN_NO_UNALIGNED_ACCESS=1 KONAN_HAS_FOUNDATION_FRAMEWORK=1
# watchOS arm64_32
targetToolchain.macos_x64-watchos_arm64 = target-toolchain-xcode_13_1
@@ -418,7 +418,7 @@ osVersionMin.watchos_arm64 = 5.0
# Regarding KONAN_NO_64BIT_ATOMIC=1: see explanation for ios_arm32 above.
runtimeDefinitions.watchos_arm64 = KONAN_OBJC_INTEROP=1 KONAN_WATCHOS KONAN_ARM32=1 \
KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG=1 \
MACHSIZE=32 KONAN_NO_64BIT_ATOMIC=1 KONAN_NO_UNALIGNED_ACCESS=1
MACHSIZE=32 KONAN_NO_64BIT_ATOMIC=1 KONAN_NO_UNALIGNED_ACCESS=1 KONAN_HAS_FOUNDATION_FRAMEWORK=1
# Apple's watchOS i386 simulator.
targetToolchain.macos_x64-watchos_x86 = target-toolchain-xcode_13_1
@@ -443,7 +443,7 @@ stripFlags.watchos_x86 = -S
linkerDynamicFlags.watchos_x86 = -dylib
osVersionMinFlagLd.watchos_x86 = -watchos_simulator_version_min
osVersionMin.watchos_x86 = 5.0
runtimeDefinitions.watchos_x86 = KONAN_OBJC_INTEROP=1 KONAN_WATCHOS=1 KONAN_NO_64BIT_ATOMIC=1 KONAN_X86=1
runtimeDefinitions.watchos_x86 = KONAN_OBJC_INTEROP=1 KONAN_WATCHOS=1 KONAN_NO_64BIT_ATOMIC=1 KONAN_X86=1 KONAN_HAS_FOUNDATION_FRAMEWORK=1
# watchOS x86_64 simulator.
targetToolchain.macos_x64-watchos_x64 = target-toolchain-xcode_13_1
@@ -467,7 +467,7 @@ stripFlags.watchos_x64 = -S
linkerDynamicFlags.watchos_x64 = -dylib
osVersionMinFlagLd.watchos_x64 = -watchos_simulator_version_min
osVersionMin.watchos_x64 = 7.0
runtimeDefinitions.watchos_x64 = KONAN_OBJC_INTEROP=1 KONAN_WATCHOS=1 KONAN_X64=1
runtimeDefinitions.watchos_x64 = KONAN_OBJC_INTEROP=1 KONAN_WATCHOS=1 KONAN_X64=1 KONAN_HAS_FOUNDATION_FRAMEWORK=1
# watchOS Apple Silicon simulator.
targetToolchain.macos_x64-watchos_simulator_arm64 = target-toolchain-xcode_13_1
@@ -491,7 +491,7 @@ stripFlags.watchos_simulator_arm64 = -S
linkerDynamicFlags.watchos_simulator_arm64 = -dylib
osVersionMinFlagLd.watchos_simulator_arm64 = -watchos_simulator_version_min
osVersionMin.watchos_simulator_arm64 = 7.0
runtimeDefinitions.watchos_simulator_arm64 = KONAN_OBJC_INTEROP=1 KONAN_WATCHOS=1 KONAN_ARM64=1
runtimeDefinitions.watchos_simulator_arm64 = KONAN_OBJC_INTEROP=1 KONAN_WATCHOS=1 KONAN_ARM64=1 KONAN_HAS_FOUNDATION_FRAMEWORK=1
# Linux x86-64.
@@ -7,7 +7,9 @@
#include "GCScheduler.hpp"
#include "AppStateTracking.hpp"
#include "Clock.hpp"
#include "GlobalData.hpp"
#include "StackTrace.hpp"
#include "std_support/UnorderedSet.hpp"
@@ -129,10 +131,14 @@ class GCSchedulerDataWithTimer : public gc::GCSchedulerData {
public:
GCSchedulerDataWithTimer(gc::GCSchedulerConfig& config, std::function<void()> scheduleGC) noexcept :
config_(config),
appStateTracking_(mm::GlobalData::Instance().appStateTracking()),
heapGrowthController_(config),
regularIntervalPacer_(config),
scheduleGC_(std::move(scheduleGC)),
timer_("GC Timer thread", config_.regularGcInterval(), [this] {
if (appStateTracking_.state() == mm::AppStateTracking::State::kBackground) {
return;
}
if (regularIntervalPacer_.NeedsGC()) {
scheduleGC_();
}
@@ -155,6 +161,7 @@ public:
private:
gc::GCSchedulerConfig& config_;
mm::AppStateTracking& appStateTracking_;
HeapGrowthController heapGrowthController_;
RegularIntervalPacer<Clock> regularIntervalPacer_;
std::function<void()> scheduleGC_;
@@ -11,6 +11,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "AppStateTrackingTestSupport.hpp"
#include "ClockTestSupport.hpp"
#include "GCSchedulerImpl.hpp"
#include "SingleThreadExecutor.hpp"
@@ -710,6 +711,42 @@ TEST_F(GCSchedulerDataWithTimerTest, TuneTargetHeap) {
EXPECT_THAT(config.targetHeapBytes.load(), 5);
}
TEST_F(GCSchedulerDataWithTimerTest, DoNotCollectOnTimerInBackground) {
constexpr int mutatorsCount = kDefaultThreadCount;
GCSchedulerConfig config;
config.regularGcIntervalMicroseconds = 10;
config.autoTune = false;
config.targetHeapBytes = std::numeric_limits<size_t>::max();
GCSchedulerDataWithTimerTestApi<mutatorsCount> schedulerTestApi(config);
// TODO: Not a global, please.
mm::AppStateTrackingTestSupport appStateTracking(mm::GlobalData::Instance().appStateTracking());
// Wait until the timer is initialized.
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
// Now go into the background.
ASSERT_THAT(mm::GlobalData::Instance().appStateTracking().state(), mm::AppStateTracking::State::kForeground);
appStateTracking.setState(mm::AppStateTracking::State::kBackground);
// Timer works in the background, but does nothing.
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
schedulerTestApi.advance_time(microseconds(10));
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
// Now go back into the foreground.
appStateTracking.setState(mm::AppStateTracking::State::kForeground);
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
schedulerTestApi.advance_time(microseconds(10));
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
schedulerTestApi.OnPerformFullGC();
schedulerTestApi.UpdateAliveSetBytes(0);
}
// These tests require a stack trace to contain call site addresses but
// on Windows a trace contains function addresses instead.
// So skip these tests on Windows.
@@ -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
@@ -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.
*/
#pragma once
#include <atomic>
#include "Utils.hpp"
#include "std_support/Memory.hpp"
namespace kotlin::mm {
class AppStateTracking : private Pinned {
public:
enum class State {
kForeground,
kBackground,
};
AppStateTracking() noexcept;
~AppStateTracking();
State state() const noexcept { return state_; }
private:
friend class AppStateTrackingTestSupport;
void setState(State state) noexcept { state_ = state; }
class Impl;
// TODO: The initial value might be incorrect.
std::atomic<State> state_ = State::kForeground;
std_support::unique_ptr<Impl> impl_;
};
} // namespace kotlin::mm
@@ -0,0 +1,18 @@
/*
* 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_UIKIT_FRAMEWORK
#include "AppStateTracking.hpp"
using namespace kotlin;
class mm::AppStateTracking::Impl {};
mm::AppStateTracking::AppStateTracking() noexcept = default;
mm::AppStateTracking::~AppStateTracking() = default;
#endif
@@ -0,0 +1,24 @@
/*
* 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
#include "AppStateTracking.hpp"
#include "Utils.hpp"
namespace kotlin::mm {
class AppStateTrackingTestSupport : private Pinned {
public:
AppStateTrackingTestSupport(AppStateTracking& appStateTracking) noexcept : appStateTracking_(appStateTracking) {}
void setState(AppStateTracking::State state) noexcept { appStateTracking_.setState(state); }
private:
AppStateTracking& appStateTracking_;
};
} // namespace kotlin::mm
@@ -0,0 +1,48 @@
/*
* 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_UIKIT_FRAMEWORK
#include "AppStateTracking.hpp"
#include <functional>
// Workaround from https://youtrack.jetbrains.com/issue/KT-48807#focus=Comments-27-5210791.0-0
// TODO: remove this once our clang supports NSAttributedString as a valid return from the formatter function.
#define NS_FORMAT_ARGUMENT(x)
#import <UIKit/UIApplication.h>
#undef NS_FORMAT_ARGUMENT
#include "CompilerConstants.hpp"
#include "objc_support/NSNotificationSubscription.hpp"
using namespace kotlin;
class mm::AppStateTracking::Impl : private Pinned {
public:
explicit Impl(std::function<void(State)> handler) noexcept :
handler_(std::move(handler)),
didEnterBackground_(UIApplicationDidEnterBackgroundNotification, [this] { handler_(State::kBackground); }),
willEnterForeground_(UIApplicationWillEnterForegroundNotification, [this] { handler_(State::kForeground); }) {}
private:
std::function<void(State)> handler_;
objc_support::NSNotificationSubscription didEnterBackground_;
objc_support::NSNotificationSubscription willEnterForeground_;
};
mm::AppStateTracking::AppStateTracking() noexcept {
switch (compiler::appStateTracking()) {
case compiler::AppStateTracking::kDisabled:
break;
case compiler::AppStateTracking::kEnabled:
impl_ = std_support::make_unique<Impl>([this](State state) noexcept { setState(state); });
break;
}
}
mm::AppStateTracking::~AppStateTracking() = default;
#endif
@@ -14,6 +14,7 @@
#include "ThreadRegistry.hpp"
#include "Utils.hpp"
#include "ExtraObjectDataFactory.hpp"
#include "AppStateTracking.hpp"
namespace kotlin {
namespace mm {
@@ -28,6 +29,7 @@ public:
StableRefRegistry& stableRefRegistry() noexcept { return stableRefRegistry_; }
ExtraObjectDataFactory& extraObjectDataFactory() noexcept { return extraObjectDataFactory_; }
gc::GC& gc() noexcept { return gc_; }
AppStateTracking& appStateTracking() noexcept { return appStateTracking_; }
private:
GlobalData();
@@ -37,6 +39,7 @@ private:
static GlobalData instance_;
ThreadRegistry threadRegistry_;
AppStateTracking appStateTracking_;
GlobalsRegistry globalsRegistry_;
StableRefRegistry stableRefRegistry_;
ExtraObjectDataFactory extraObjectDataFactory_;