diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CheckExternalCalls.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CheckExternalCalls.kt new file mode 100644 index 00000000000..e3790cd3cf1 --- /dev/null +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CheckExternalCalls.kt @@ -0,0 +1,201 @@ +/* + * Copyright 2010-2021 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 + +import kotlinx.cinterop.toCValues +import llvm.* +import org.jetbrains.kotlin.backend.konan.llvm.* + +private fun getBasicBlocks(function: LLVMValueRef) = + generateSequence(LLVMGetFirstBasicBlock(function)) { LLVMGetNextBasicBlock(it) } + +private fun getInstructions(function: LLVMBasicBlockRef) = + generateSequence(LLVMGetFirstInstruction(function)) { LLVMGetNextInstruction(it) } + +private fun LLVMValueRef.isFunctionCall() = LLVMIsACallInst(this) != null || LLVMIsAInvokeInst(this) != null + +private fun LLVMValueRef.isExternalFunction() = LLVMGetFirstBasicBlock(this) == null + + +private fun LLVMValueRef.isLLVMBuiltin(): Boolean { + val name = this.name ?: return false + return name.startsWith("llvm.") +} + + +private class CallsChecker(val context: Context, goodFunctions: List) { + private val goodFunctionsExact = goodFunctions.filterNot { it.endsWith("*") }.toSet() + private val goodFunctionsByPrefix = goodFunctions.filter { it.endsWith("*") }.map { it.substring(0, it.length - 1) }.sorted() + + private fun isGoodFunction(name: String) : Boolean { + if (name in goodFunctionsExact) return true + val insertionPoint = goodFunctionsByPrefix.binarySearch(name).let { if (it < 0) it.inv() else it } + if (insertionPoint < goodFunctionsByPrefix.size && name.startsWith(goodFunctionsByPrefix[insertionPoint])) return true + if (insertionPoint > 0 && name.startsWith(goodFunctionsByPrefix[insertionPoint - 1])) return true + return false + } + + private fun externalFunction(name: String, type: LLVMTypeRef) = + context.llvm.externalFunction(name, type, context.stdlibModule.llvmSymbolOrigin) + + private fun moduleFunction(name: String) = + LLVMGetNamedFunction(context.llvmModule, name) ?: throw IllegalStateException("$name function is not available") + + val getMethodImpl = externalFunction("class_getMethodImplementation", functionType(pointerType(functionType(voidType, false)), false, int8TypePtr, int8TypePtr)) + val getClass = externalFunction("object_getClass", functionType(int8TypePtr, false, int8TypePtr)) + val getSuperClass = externalFunction("class_getSuperclass", functionType(int8TypePtr, false, int8TypePtr)) + val checkerFunction = moduleFunction("Kotlin_mm_checkStateAtExternalFunctionCall") + + private data class ExternalCallInfo(val name: String?, val calledPtr: LLVMValueRef) + + private fun LLVMValueRef.getPossiblyExternalCalledFunction(): ExternalCallInfo? { + fun isIndirectCallArgument(value: LLVMValueRef) = LLVMIsALoadInst(value) != null || LLVMIsAArgument(value) != null || + LLVMIsAPHINode(value) != null || LLVMIsASelectInst(value) != null || LLVMIsACallInst(value) != null + + fun cleanCalledFunction(value: LLVMValueRef): ExternalCallInfo? { + return when { + LLVMIsAFunction(value) != null -> { + val valueOrSpecial = value.takeIf { !it.isLLVMBuiltin() } + ?: LLVMConstIntToPtr(Int64(CALLED_LLVM_BUILTIN).llvm, int8TypePtr)!! + ExternalCallInfo(value.name!!, valueOrSpecial).takeIf { value.isExternalFunction() } + } + LLVMIsACastInst(value) != null -> cleanCalledFunction(LLVMGetOperand(value, 0)!!) + isIndirectCallArgument(value) -> ExternalCallInfo(null, value) // this is a callback call + LLVMIsAInlineAsm(value) != null -> null // this is inline assembly call + LLVMIsAConstantExpr(value) != null -> { + when (LLVMGetConstOpcode(value)) { + LLVMOpcode.LLVMBitCast -> cleanCalledFunction(LLVMGetOperand(value, 0)!!) + else -> TODO("not implemented constant type in call") + } + } + LLVMIsAGlobalAlias(value) != null -> cleanCalledFunction(LLVMAliasGetAliasee(value)!!) + else -> { + TODO("not implemented call argument ${llvm2string(value)} called in ${llvm2string(this)}") + } + } + } + + return cleanCalledFunction(LLVMGetCalledValue(this)!!) + } + + private fun processBasicBlock(functionName: String, block: LLVMBasicBlockRef) { + val calls = getInstructions(block) + .filter { it.isFunctionCall() } + .toList() + val builder = LLVMCreateBuilderInContext(llvmContext) + + for (call in calls) { + val calleeInfo = call.getPossiblyExternalCalledFunction() ?: continue + if (calleeInfo.name != null && isGoodFunction(calleeInfo.name)) continue + LLVMPositionBuilderBefore(builder, call) + LLVMBuilderResetDebugLocation(builder) + val callSiteDescription: String + val calledName: String? + val calledPtrLlvm: LLVMValueRef? + when (calleeInfo.name) { + "objc_msgSend" -> { + // objc_msgSend has wrong declaration in header, so generated wrapper is strange, Let's just skip it + if (LLVMGetNumArgOperands(call) < 2) continue + callSiteDescription = "$functionName (over objc_msgSend)" + calledName = null + val firstArgI8Ptr = LLVMBuildBitCast(builder, LLVMGetArgOperand(call, 0), int8TypePtr, "") + val firstArgClassPtr = LLVMBuildCall(builder, getClass, listOf(firstArgI8Ptr).toCValues(), 1, "") + val isNil = LLVMBuildICmp(builder, LLVMIntPredicate.LLVMIntEQ, firstArgI8Ptr, LLVMConstNull(int8TypePtr), "") + val selector = LLVMGetArgOperand(call, 1) + val calledPtrLlvmIfNotNilFunPtr = LLVMBuildCall(builder, getMethodImpl, listOf(firstArgClassPtr, selector).toCValues(), 2, "") + val calledPtrLlvmIfNotNil = LLVMBuildBitCast(builder, calledPtrLlvmIfNotNilFunPtr, int8TypePtr, "") + val calledPtrLlvmIfNil = LLVMConstIntToPtr(Int64(MSG_SEND_TO_NULL).llvm, int8TypePtr) + calledPtrLlvm = LLVMBuildSelect(builder, isNil, calledPtrLlvmIfNil, calledPtrLlvmIfNotNil, "") + } + "objc_msgSendSuper2" -> { + if (LLVMGetNumArgOperands(call) < 2) continue + callSiteDescription = "$functionName (over objc_msgSendSuper2)" + calledName = null + val superStruct = LLVMGetArgOperand(call, 0) + val superClassPtrPtr = LLVMBuildGEP(builder, superStruct, listOf(Int32(0).llvm, Int32(1).llvm).toCValues(), 2, "") + val superClassPtr = LLVMBuildLoad(builder, superClassPtrPtr, "") + val classPtr = LLVMBuildCall(builder, getSuperClass, listOf(superClassPtr).toCValues(), 1, "") + val calledPtrLlvmFunPtr = LLVMBuildCall(builder, getMethodImpl, listOf(classPtr, LLVMGetArgOperand(call, 1)).toCValues(), 2, "") + calledPtrLlvm = LLVMBuildBitCast(builder, calledPtrLlvmFunPtr, int8TypePtr, "") + } + else -> { + callSiteDescription = functionName + calledName = calleeInfo.name + calledPtrLlvm = LLVMBuildBitCast(builder, calleeInfo.calledPtr, int8TypePtr, "") + } + } + val callSiteDescriptionLlvm = context.llvm.staticData.cStringLiteral(callSiteDescription).llvm + val calledNameLlvm = if (calledName == null) LLVMConstNull(int8TypePtr) else context.llvm.staticData.cStringLiteral(calledName).llvm + LLVMBuildCall(builder, checkerFunction, listOf(callSiteDescriptionLlvm, calledNameLlvm, calledPtrLlvm).toCValues(), 3, "") + } + LLVMDisposeBuilder(builder) + } + + fun processFunction(function: LLVMValueRef) { + if (function == checkerFunction) return + getBasicBlocks(function).forEach { + processBasicBlock(function.name!!, it) + } + } + + companion object { + const val MSG_SEND_TO_NULL: Long = -1 + const val CALLED_LLVM_BUILTIN: Long = -2 + } +} + +private const val functionListGlobal = "Kotlin_callsCheckerKnownFunctions" +private const val functionListSizeGlobal = "Kotlin_callsCheckerKnownFunctionsCount" + +internal fun checkLlvmModuleExternalCalls(context: Context) { + val staticData = context.llvm.staticData + + val annotations = staticData.getGlobal("llvm.global.annotations")?.getInitializer() + + val ignoredFunctions = annotations?.run { + getOperands(this).mapNotNull { + val annotationName = LLVMGetInitializer(LLVMGetOperand(LLVMGetOperand(it, 1), 0))?.getAsCString() + if (annotationName == "no_external_calls_check") { + LLVMGetOperand(LLVMGetOperand(it, 0), 0)!!.name + } else { + null + } + }.toSet() + } ?: emptySet() + + val goodFunctions = staticData.getGlobal("Kotlin_callsCheckerGoodFunctionNames")?.getInitializer()?.run { + getOperands(this).map { + LLVMGetInitializer(LLVMGetOperand(it, 0))!!.getAsCString() + }.toList() + } ?: emptyList() + + val checker = CallsChecker(context, goodFunctions) + getFunctions(context.llvmModule!!) + .filter { !it.isExternalFunction() && it.name !in ignoredFunctions } + .forEach(checker::processFunction) + // otherwise optimiser can inline it + staticData.getGlobal(functionListGlobal)?.setExternallyInitialized(true); + staticData.getGlobal(functionListSizeGlobal)?.setExternallyInitialized(true); + context.verifyBitCode() +} + +// this should be a separate pass, to handle DCE correctly +internal fun addFunctionsListSymbolForChecker(context: Context) { + val staticData = context.llvm.staticData + + val functions = getFunctions(context.llvmModule!!) + .filter { !it.isExternalFunction() } + .map { constPointer(it).bitcast(int8TypePtr) } + .toList() + val functionsArray = staticData.placeGlobalConstArray("", int8TypePtr, functions) + staticData.getGlobal(functionListGlobal) + ?.setInitializer(functionsArray) + ?: throw IllegalStateException("$functionListGlobal global not found") + staticData.getGlobal(functionListSizeGlobal) + ?.setInitializer(Int32(functions.size)) + ?: throw IllegalStateException("$functionListSizeGlobal global not found") + context.verifyBitCode() +} \ No newline at end of file diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt index dfc92e72011..37ea3d6f1e6 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt @@ -404,7 +404,9 @@ private val backendCodegen = namedUnitPhase( verifyBitcodePhase then printBitcodePhase then linkBitcodeDependenciesPhase then + checkExternalCallsPhase then bitcodeOptimizationPhase then + rewriteExternalCallsCheckerGlobals then unitSink() ) @@ -453,6 +455,8 @@ internal fun PhaseConfig.konanPhasesConfig(config: KonanConfig) { disableIf(backendCodegen, config.produce == CompilerOutputKind.LIBRARY) disableUnless(bitcodeOptimizationPhase, config.produce.involvesLinkStage) disableUnless(linkBitcodeDependenciesPhase, config.produce.involvesLinkStage) + disableUnless(checkExternalCallsPhase, config.produce.involvesLinkStage && getBoolean(KonanConfigKeys.CHECK_EXTERNAL_CALLS)) + disableUnless(rewriteExternalCallsCheckerGlobals, config.produce.involvesLinkStage && getBoolean(KonanConfigKeys.CHECK_EXTERNAL_CALLS)) disableUnless(objectFilesPhase, config.produce.involvesLinkStage) disableUnless(linkerPhase, config.produce.involvesLinkStage) disableIf(testProcessorPhase, getNotNull(KonanConfigKeys.GENERATE_TEST_RUNNER) == TestRunnerKind.NONE) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BitcodePhases.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BitcodePhases.kt index eca03758f86..4a620c0c162 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BitcodePhases.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BitcodePhases.kt @@ -300,6 +300,20 @@ internal val linkBitcodeDependenciesPhase = makeKonanModuleOpPhase( op = { context, _ -> linkBitcodeDependencies(context) } ) +internal val checkExternalCallsPhase = makeKonanModuleOpPhase( + name = "CheckExternalCalls", + description = "Check external calls", + op = { context, _ -> checkLlvmModuleExternalCalls(context) } +) + +internal val rewriteExternalCallsCheckerGlobals = makeKonanModuleOpPhase( + name = "RewriteExternalCallsCheckerGlobals", + description = "Rewrite globals for external calls checker after optimizer run", + op = { context, _ -> addFunctionsListSymbolForChecker(context) } +) + + + internal val bitcodeOptimizationPhase = makeKonanModuleOpPhase( name = "BitcodeOptimization", description = "Optimize bitcode", diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt index 29d536b4e8a..75fae809bff 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt @@ -230,6 +230,16 @@ internal fun RuntimeAware.isObjectType(type: LLVMTypeRef): Boolean { internal fun CArrayPointer.getBytes(size: Long) = (0 .. size-1).map { this[it] }.toByteArray() +internal fun LLVMValueRef.getAsCString() : String { + memScoped { + val lengthPtr = alloc() + val data = LLVMGetAsString(this@getAsCString, lengthPtr.ptr)!! + require(lengthPtr.value >= 1 && data[lengthPtr.value - 1] == 0.toByte()) { "Expected null-terminated string from llvm"} + return data.toKString() + } +} + + internal fun getFunctionType(ptrToFunction: LLVMValueRef): LLVMTypeRef { return getGlobalType(ptrToFunction) } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt index 503b5dba3c3..cb32c839d80 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt @@ -60,6 +60,8 @@ internal class StaticData(override val context: Context): ContextUtils { val type get() = getGlobalType(this.llvmGlobal) + fun getInitializer() = LLVMGetInitializer(llvmGlobal) + fun setInitializer(value: ConstValue) { LLVMSetInitializer(llvmGlobal, value.llvm) } @@ -84,6 +86,10 @@ internal class StaticData(override val context: Context): ContextUtils { LLVMSetSection(llvmGlobal, name) } + fun setExternallyInitialized(value: Boolean) { + LLVMSetExternallyInitialized(llvmGlobal, if (value) 1 else 0) + } + val pointer = Pointer.to(this) } diff --git a/kotlin-native/runtime/src/main/cpp/Common.h b/kotlin-native/runtime/src/main/cpp/Common.h index 71b44af940e..3f3eb20fd61 100644 --- a/kotlin-native/runtime/src/main/cpp/Common.h +++ b/kotlin-native/runtime/src/main/cpp/Common.h @@ -27,6 +27,8 @@ #define ALWAYS_INLINE __attribute__((always_inline)) #define NO_INLINE __attribute__((noinline)) +#define NO_EXTERNAL_CALLS_CHECK __attribute__((annotate("no_external_calls_check"))) + #if KONAN_NO_THREADS #define THREAD_LOCAL_VARIABLE #else diff --git a/kotlin-native/runtime/src/main/cpp/ObjCInterop.mm b/kotlin-native/runtime/src/main/cpp/ObjCInterop.mm index 4991acf3bcb..ffd73c3ff58 100644 --- a/kotlin-native/runtime/src/main/cpp/ObjCInterop.mm +++ b/kotlin-native/runtime/src/main/cpp/ObjCInterop.mm @@ -256,7 +256,7 @@ static void AddMethods(Class clazz, const struct ObjCMethodDescription* methods, static kotlin::SpinLock classCreationMutex; static int anonymousClassNextId = 0; -static Class allocateClass(const KotlinObjCClassInfo* info) { +NO_EXTERNAL_CALLS_CHECK static Class allocateClass(const KotlinObjCClassInfo* info) { Class superclass = Kotlin_Interop_getObjCClass(info->superclassName); if (info->exported) { diff --git a/kotlin-native/runtime/src/main/cpp/Porting.cpp b/kotlin-native/runtime/src/main/cpp/Porting.cpp index 4253d1c46a7..4b58dc57dc6 100644 --- a/kotlin-native/runtime/src/main/cpp/Porting.cpp +++ b/kotlin-native/runtime/src/main/cpp/Porting.cpp @@ -68,7 +68,7 @@ void consoleWriteUtf8(const char* utf8, uint32_t sizeBytes) { #endif } -void consoleErrorUtf8(const char* utf8, uint32_t sizeBytes) { +NO_EXTERNAL_CALLS_CHECK void consoleErrorUtf8(const char* utf8, uint32_t sizeBytes) { #ifdef KONAN_ANDROID // TODO: use sizeBytes! __android_log_print(ANDROID_LOG_ERROR, "Konan_main", "%s", utf8); @@ -148,7 +148,7 @@ extern "C" int rpl_vsnprintf(char *, size_t, const char *, va_list); #define vsnprintf_impl ::vsnprintf #endif -void consolePrintf(const char* format, ...) { +NO_EXTERNAL_CALLS_CHECK void consolePrintf(const char* format, ...) { char buffer[1024]; va_list args; va_start(args, format); @@ -160,7 +160,7 @@ void consolePrintf(const char* format, ...) { } // TODO: Avoid code duplication. -void consoleErrorf(const char* format, ...) { +NO_EXTERNAL_CALLS_CHECK void consoleErrorf(const char* format, ...) { char buffer[1024]; va_list args; va_start(args, format); @@ -201,7 +201,7 @@ static void onThreadExitCallback(void* value) { } } -bool isOnThreadExitNotSetOrAlreadyStarted() { +NO_EXTERNAL_CALLS_CHECK bool isOnThreadExitNotSetOrAlreadyStarted() { return terminationKey != 0 && pthread_getspecific(terminationKey) == nullptr; } diff --git a/kotlin-native/runtime/src/mm/cpp/CallsChecker.cpp b/kotlin-native/runtime/src/mm/cpp/CallsChecker.cpp new file mode 100644 index 00000000000..1d6ad0f7b2a --- /dev/null +++ b/kotlin-native/runtime/src/mm/cpp/CallsChecker.cpp @@ -0,0 +1,378 @@ +/* + * Copyright 2010-2020 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. + */ + +#include +#include + +#include "KAssert.h" +#include "Memory.h" +#include "Porting.h" +#include "ThreadData.hpp" +#include "ThreadRegistry.hpp" +#include "ExecFormat.h" + +using namespace kotlin; + +// this values will be substituted by compiler +extern "C" const void **Kotlin_callsCheckerKnownFunctions = nullptr; +extern "C" int Kotlin_callsCheckerKnownFunctionsCount = 0; + +extern "C" const char* Kotlin_callsCheckerGoodFunctionNames[] = { + "\x01_close", + "\x01_mprotect", + "close", + "mprotect", + + "_ZL15_objc_terminatev", // _objc_terminate() + "_ZNKSt8__detail20_Prime_rehash_policy14_M_need_rehashEmmm", // std::__detail::_Prime_rehash_policy::_M_need_rehash(unsigned long, unsigned long, unsigned long) const + "_ZNKSt8__detail20_Prime_rehash_policy14_M_need_rehashEyyy", // std::__detail::_Prime_rehash_policy::_M_need_rehash(unsigned long long, unsigned long long, unsigned long long) const + "_ZNSaIcED2Ev", // std::allocator::~allocator() + "_ZNSt13exception_ptrC1ERKS_", // std::exception_ptr::exception_ptr(std::exception_ptr const&) + "_ZNSt13exception_ptrD1Ev", // std::exception_ptr::~exception_ptr() + "_ZNSt15__exception_ptr13exception_ptrC1ERKS0_", // std::__exception_ptr::exception_ptr::exception_ptr(std::__exception_ptr::exception_ptr const&) + "_ZNSt15__exception_ptr13exception_ptrD1Ev", // std::__exception_ptr::exception_ptr::~exception_ptr() + "_ZNSt18condition_variableD1Ev", // std::condition_variable::~condition_variable() + "_ZNSt3__112__next_primeEm", // std::__1::__next_prime(unsigned long) + "_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED1Ev", // std::__1::basic_string, std::__1::allocator >::~basic_string() + "_ZNSt3__16chrono12steady_clock3nowEv", // std::__1::chrono::steady_clock::now() + "_ZNSt3__19to_stringEi", // std::__1::to_string(int) + "_ZNSt6chrono3_V212steady_clock3nowEv", // std::chrono::_V2::steady_clock::now() + "_ZNSt8__detail15_List_node_base7_M_hookEPS0_", // std::__detail::_List_node_base::_M_hook(std::__detail::_List_node_base*) + "_ZNSt8__detail15_List_node_base9_M_unhookEv", // std::__detail::_List_node_base::_M_unhook() + "_ZNSt9exceptionD2Ev", // std::exception::~exception() + "_ZSt17current_exceptionv", // std::current_exception() + "_ZSt17rethrow_exceptionSt13exception_ptr", // std::rethrow_exception(std::exception_ptr) + "_ZSt29_Rb_tree_insert_and_rebalancebPSt18_Rb_tree_node_baseS0_RS_", // std::_Rb_tree_insert_and_rebalance(bool, std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, std::_Rb_tree_node_base&) + "_ZSt9terminatev", // std::terminate() + "_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEED1Ev", // std::__cxx11::basic_string, std::allocator >::~basic_string() + "_ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE", // std::rethrow_exception(std::__exception_ptr::exception_ptr) + "_ZSt28_Rb_tree_rebalance_for_erasePSt18_Rb_tree_node_baseRS_", // std::_Rb_tree_rebalance_for_erase(std::_Rb_tree_node_base*, std::_Rb_tree_node_base&) + "_ZN9__gnu_cxx27__verbose_terminate_handlerEv", // __gnu_cxx::__verbose_terminate_handler() + "__mingw_vsnprintf", + "__cxa_allocate_exception", + "__cxa_begin_catch", + "__cxa_end_catch", + "__cxa_throw", + "__memset_chk", + + "abort", + "acos", + "acosf", + "acosh", + "acoshf", + "asin", + "asinf", + "asinh", + "asinhf", + "atan", + "atanf", + "atan2", + "atan2f", + "atanf", + "atanh", + "atanhf", + "calloc", + "clock_gettime", + "cos", + "cosf", + "cosh", + "cosh", + "coshf", + "coshf", + "exit", + "exp", + "expf", + "expm1", + "expm1f", + "free", + "getrusage", + "hypot", + "hypotf", + "isinf", + "isnan", + "log", + "logf", + "log1p", + "log1pf", + "log10", + "log10f", + "log2", + "log2f", + "malloc", + "memcmp", + "memmem", + "nextafter", + "nextafterf", + "pow", + "powf", + "remainder", + "remainderf", + "sin", + "sinf", + "sinh", + "sinhf", + "snprintf", + "sqrt", + "sqrtf", + "strcmp", + "strlen", + "strnlen", + "tan", + "tanf", + "tanh", + "tanhf", + "vsnprintf", + + "dispatch_once", + "\x01_pthread_cond_init", + "_pthread_cond_init", + "pthread_cond_broadcast", + "pthread_cond_destroy", + "pthread_cond_signal", + "pthread_cond_init", + "pthread_create", + "pthread_equal", + "pthread_main_np", + "pthread_mutex_destroy", + "pthread_mutex_init", + "pthread_mutex_unlock", + "pthread_self", + + "+[NSError errorWithDomain:code:userInfo:]", + "+[NSMethodSignature signatureWithObjCTypes:]", + "+[NSNull null]", + "+[NSObject allocWithZone:]", + "+[NSObject class]", + "+[NSObject conformsToProtocol:]", + "+[NSObject isKindOfClass:]", + "+[NSObject new]", + "+[NSString stringWithFormat:]", + "+[NSString stringWithUTF8String:]", + "+[NSValue valueWithPointer:]", + "-[NSDictionary objectForKeyedSubscript:]", + "-[NSError localizedDescription]", + "-[NSError userInfo]", + "-[NSException name]", + "-[NSException reason]", + "-[NSMethodSignature getArgumentTypeAtIndex:]", + "-[NSMethodSignature methodReturnType]", + "-[NSMethodSignature numberOfArguments]", + "-[NSObject conformsToProtocol:]", + "-[NSObject init]", + "-[NSObject isKindOfClass:]", + "-[NSObject retain]", + "-[NSPlaceholderString initWithBytes:length:encoding:]", + "-[NSPlaceholderString initWithBytesNoCopy:length:encoding:freeWhenDone:]", + "-[NSValue pointerValue]", + "-[__NSArray0 count]", + "-[__NSArrayI count]", + "-[__NSArrayI objectAtIndex:]", + "-[__NSArrayM count]", + "-[__NSArrayM objectAtIndex:]", + "-[__NSCFBoolean boolValue]", + "-[__NSCFNumber doubleValue]", + "-[__NSCFNumber floatValue]", + "-[__NSCFNumber intValue]", + "-[__NSCFNumber longLongValue]", + "-[__NSCFNumber objCType]", + "-[__NSCFString isEqual:]", + "-[__NSDictionaryM setObject:forKeyedSubscript:]", + "-[__NSFrozenDictionaryM objectForKeyedSubscript:]", + "-[__SwiftNativeNSError userInfo]", + "CFStringCreateCopy", + "CFStringGetCharacters", + "CFStringGetLength", + "class_addIvar", + "class_addMethod", + "class_addProtocol", + "class_copyMethodList", + "class_copyProtocolList", + "class_getClassMethod", + "class_getInstanceMethod", + "class_getInstanceVariable", + "class_getName", + "class_getSuperclass", + "class_isMetaClass", + "ivar_getOffset", + "method_getName", + "method_getTypeEncoding", + "objc_alloc", + "objc_allocateClassPair", + "objc_autorelease", + "objc_autoreleasePoolPush", + "objc_autoreleaseReturnValue", + "objc_getAssociatedObject", + "objc_getClass", + "objc_getProtocol", + "objc_lookUpClass", + "objc_registerClassPair", + "objc_retain", + "objc_retainAutoreleaseReturnValue", + "objc_retainBlock", + "objc_setAssociatedObject", + "objc_storeWeak", + "object_getClass", + "object_isClass", + "protocol_copyProtocolList", + "protocol_getMethodDescription", + "protocol_getName", + "sel_registerName", + + + // @objc Swift._ContiguousArrayStorage.count.getter : Swift.Int + "$ss23_ContiguousArrayStorageC5countSivgTo", + // @objc Swift.__SwiftDeferredNSArray.count.getter : Swift.Int + "$ss22__SwiftDeferredNSArrayC5countSivgTo", + // @objc Swift._ContiguousArrayStorage.objectAt(Swift.Int) -> Swift.Unmanaged + "$ss23_ContiguousArrayStorageC8objectAtys9UnmanagedVyyXlGSiFTo", + // @objc Swift.__SwiftNativeNSArrayWithContiguousStorage.objectAt(Swift.Int) -> Swift.Unmanaged + "$ss41__SwiftNativeNSArrayWithContiguousStorageC8objectAtys9UnmanagedVyyXlGSiFTo", + // @objc Swift.__EmptyDictionarySingleton.count.getter : Swift.Int + "$ss26__EmptyDictionarySingletonC5countSivgTo", + // @objc Swift._SwiftDeferredNSDictionary.count.getter : Swift.Int + "$ss26_SwiftDeferredNSDictionaryC5countSivgTo", + // @objc Swift._SwiftDeferredNSDictionary.keyEnumerator() -> Swift._NSEnumerator + "$ss26_SwiftDeferredNSDictionaryC13keyEnumerators13_NSEnumerator_pyFTo", + // @objc Swift._SwiftDictionaryNSEnumerator.nextObject() -> Swift.AnyObject? + "$ss28_SwiftDictionaryNSEnumeratorC10nextObjectyXlSgyFTo", + + "llvm.assume", + "llvm.ceil.*", + "llvm.copysign.*", + "llvm.cos.*", + "llvm.ctlz.*", + "llvm.ctpop.*", + "llvm.cttz.*", + "llvm.dbg.*", + "llvm.eh.typeid.for", + "llvm.exp.*", + "llvm.fabs.*", + "llvm.fabs.*", + "llvm.floor.*", + "llvm.instrprof.*", + "llvm.lifetime.*", + "llvm.log.*", + "llvm.log10.*", + "llvm.log2.*", + "llvm.memcpy.*", + "llvm.memmove.*", + "llvm.memset.*", + "llvm.objc.autoreleaseReturnValue", + "llvm.objc.retain", + "llvm.objectsize.*", + "llvm.pow.*", + "llvm.rint.*", + "llvm.sin.*", + "llvm.sqrt.*", + "llvm.umul.*", + "llvm.va_end", + "llvm.va_start", + "llvm.x86.avx2.*", + "llvm.x86.ssse3.*", + + "SetConsoleOutputCP", + "SetConsoleCP", + "QueryPerformanceCounter", + "VirtualAlloc", + "FlsSetValue", + "GetCurrentProcess", + "FlsFree", + "K32GetProcessMemoryInfo", +}; + +namespace { + +class KnownFunctionChecker { +public: + KnownFunctionChecker() { + for (int i = 0; i < Kotlin_callsCheckerKnownFunctionsCount; i++) { + known_functions_.insert(Kotlin_callsCheckerKnownFunctions[i]); + } + std::copy(std::begin(Kotlin_callsCheckerGoodFunctionNames), std::end(Kotlin_callsCheckerGoodFunctionNames), std::begin(good_names_copy_)); + std::sort(std::begin(good_names_copy_), std::end(good_names_copy_)); + } + + bool isKnown(const void* fun) const noexcept { + return known_functions_.find(fun) != known_functions_.end(); + } + + bool isSafeByName(std::string_view name) const noexcept { + auto it = std::lower_bound(std::begin(good_names_copy_), std::end(good_names_copy_), name); + auto check = [&](std::string_view banned) { + if (banned.back() != '*') { + return banned == name; + } + return name.substr(0, banned.size() - 1) == banned.substr(0, banned.size() - 1); + }; + if (it != std::end(good_names_copy_) && check(*it)) { + return true; + } + if (it != std::begin(good_names_copy_) && check(*std::prev(it))) { + return true; + } + return false; + } + + ~KnownFunctionChecker() = delete; + +private: + KStdUnorderedSet known_functions_; + std::string_view good_names_copy_[sizeof(Kotlin_callsCheckerGoodFunctionNames) / sizeof(Kotlin_callsCheckerGoodFunctionNames[0])]; +}; + +[[clang::no_destroy]] const KnownFunctionChecker checker; + + +constexpr int MSG_SEND_TO_NULL = -1; +constexpr int CALLED_LLVM_BUILTIN = -2; + +} + +/** + * This function calls is inserted to llvm bitcode automatically, so it can be called almost anywhre. + * + * Although, function itself is excluded, it can call itself indirectly, from other called functions. + * Because of this, thread_local guard is used to avoid recursive calls. + * + * Unfortunately, function can be called in thread constructors or destructors, where thread local data + * should not be accessed. So before guard checking we need to check is thread destructor is running, + * which requires special handling of recursive calls from this check. + */ +extern "C" RUNTIME_NOTHROW void Kotlin_mm_checkStateAtExternalFunctionCall(const char* caller, const char *callee, const void *calleePtr) noexcept { + if (reinterpret_cast(calleePtr) == MSG_SEND_TO_NULL) return; // objc_sendMsg called on nil, it does nothing, so it's ok + if (konan::isOnThreadExitNotSetOrAlreadyStarted()) return; + static thread_local bool recursiveCallGuard = false; + if (recursiveCallGuard) return; + if (!mm::ThreadRegistry::Instance().IsCurrentThreadRegistered()) return; + struct unlockGuard { + unlockGuard() { recursiveCallGuard = true; } + ~unlockGuard() { recursiveCallGuard = false; } + } guard; + + + auto actualState = GetThreadState(); + if (actualState == ThreadState::kNative) { + return; + } + if (reinterpret_cast(calleePtr) != CALLED_LLVM_BUILTIN && checker.isKnown(calleePtr)) { + return; + } + + char buf[200]; + if (callee == nullptr) { + if (AddressToSymbol(calleePtr, buf, sizeof(buf))) { + callee = buf; + } else { + callee = "unknown function"; + } + } + + if (checker.isSafeByName(callee)) { + return; + } + + RuntimeFail("Expected kNative thread state at call of function %s by function %s", callee, caller); +} + diff --git a/kotlin-native/runtime/src/mm/cpp/Memory.cpp b/kotlin-native/runtime/src/mm/cpp/Memory.cpp index 98c4f49fcd4..3108682f457 100644 --- a/kotlin-native/runtime/src/mm/cpp/Memory.cpp +++ b/kotlin-native/runtime/src/mm/cpp/Memory.cpp @@ -107,6 +107,9 @@ extern "C" void DeinitMemory(MemoryState* state, bool destroyRuntime) { // TODO: Also make sure that finalizers are run. } mm::ThreadRegistry::Instance().Unregister(node); + if (destroyRuntime) { + mm::ThreadRegistry::ClearCurrentThreadData(); + } } extern "C" void RestoreMemory(MemoryState*) { diff --git a/kotlin-native/runtime/src/mm/cpp/ThreadSuspension.cpp b/kotlin-native/runtime/src/mm/cpp/ThreadSuspension.cpp index cd2af06523c..1d3cc90c266 100644 --- a/kotlin-native/runtime/src/mm/cpp/ThreadSuspension.cpp +++ b/kotlin-native/runtime/src/mm/cpp/ThreadSuspension.cpp @@ -44,7 +44,7 @@ std::condition_variable gSuspendsionCondVar; } // namespace -bool kotlin::mm::ThreadSuspensionData::suspendIfRequested() noexcept { +NO_EXTERNAL_CALLS_CHECK bool kotlin::mm::ThreadSuspensionData::suspendIfRequested() noexcept { if (IsThreadSuspensionRequested()) { std::unique_lock lock(gSuspensionMutex); if (IsThreadSuspensionRequested()) {