[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:
committed by
Space
parent
fcfc79aa35
commit
d47193d36f
+17
@@ -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),
|
||||
}
|
||||
+2
@@ -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>(
|
||||
|
||||
+4
@@ -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")
|
||||
|
||||
+1
@@ -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))
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------//
|
||||
|
||||
@@ -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_;
|
||||
|
||||
Reference in New Issue
Block a user