From 233b01f34198005ac4d87106abf36c2c3f219436 Mon Sep 17 00:00:00 2001 From: Sergey Bogolepov Date: Tue, 6 Dec 2022 14:47:43 +0200 Subject: [PATCH] [K/N] CInterop ExceptionPrettifier Add a wrapper around cinterop invocation that tracks exceptions. Wrapper has a set of handlers. Each handler checks exception for some pattern and if it matches, throws another exception with a more user-friendly message. The first such case is compilation error due to missing -fmodules flag. --- .../interop/gen/ExceptionPrettifiers.kt | 56 +++++++++++++++++++ .../native/interop/gen/jvm/CommandLine.kt | 4 ++ .../kotlin/native/interop/gen/jvm/main.kt | 20 +++++-- .../kotlin/konan/util/CinteropMessages.kt | 20 +++++++ 4 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ExceptionPrettifiers.kt create mode 100644 native/utils/src/org/jetbrains/kotlin/konan/util/CinteropMessages.kt diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ExceptionPrettifiers.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ExceptionPrettifiers.kt new file mode 100644 index 00000000000..fb74988d20f --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ExceptionPrettifiers.kt @@ -0,0 +1,56 @@ +/* + * 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.native.interop.gen + +import org.jetbrains.kotlin.konan.util.CInteropHints + +class CInteropPrettyException(message: String) : Exception(message) + +/** + * Pattern matching machinery for making exceptions from + * cinterop nicer. + */ +sealed interface ExceptionPrettifier { + fun matches(throwable: Throwable): Boolean + + fun prettify(throwable: Throwable): CInteropPrettyException +} + +/** + * Suggests to add `-compiler-option -fmodules` if the exception message hints about it. + */ +object ClangModulesDisabledPrettifier : ExceptionPrettifier { + + private val supportedPatterns = listOf( + "use of '@import' when modules are disabled" + ) + + override fun matches(throwable: Throwable): Boolean { + return supportedPatterns.any { throwable.message?.contains(it) == true } + } + + override fun prettify(throwable: Throwable): CInteropPrettyException { + return CInteropPrettyException(CInteropHints.fmodulesHint) + } +} + +/** + * Wraps invocation of [action] into exception handler and makes messages of supported exceptions more user-friendly. + * Can be optionally [disabled] which is useful when one want to find the root cause of the prettified exception. + */ +inline fun withExceptionPrettifier(disabled: Boolean = false, action: () -> T): T { + if (disabled) { + return action() + } + return try { + action() + } catch (throwable: Throwable) { + val prettifiers = listOf( + ClangModulesDisabledPrettifier, + ) + throw prettifiers.firstOrNull { it.matches(throwable) }?.prettify(throwable) ?: throwable + } +} \ No newline at end of file diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/CommandLine.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/CommandLine.kt index d5347f6bcff..d0b0f2a7a93 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/CommandLine.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/CommandLine.kt @@ -33,6 +33,7 @@ const val COMPILE_SOURCES = "Xcompile-source" const val SHORT_MODULE_NAME = "Xshort-module-name" const val FOREIGN_EXCEPTION_MODE = "Xforeign-exception-mode" const val DUMP_BRIDGES = "Xdump-bridges" +const val DISABLE_EXCEPTION_PRETTIFIER = "Xdisable-exception-prettifier" // TODO: unify camel and snake cases. // Possible solution is to accept both cases @@ -132,6 +133,9 @@ open class CInteropArguments(argParser: ArgParser = val dumpBridges by argParser.option(ArgType.Boolean, DUMP_BRIDGES, description = "Dump generated bridges") + + val disableExceptionPrettifier by argParser.option(ArgType.Boolean, DISABLE_EXCEPTION_PRETTIFIER, + description = "Don't hide exceptions with user-friendly ones").default(false) } class JSInteropArguments(argParser: ArgParser = ArgParser("jsinterop", diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt index dc605f0e0e7..99b25bc19e2 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt @@ -71,15 +71,21 @@ class Interop { /** * invoked via reflection from new test system: CompilationToolCallKt.invokeCInterop(), * `interop()` has issues to be invoked directly due to NoSuchMethodError, caused by presence of InternalInteropOptions argtype: - * java.lang.IllegalArgumentException: argument type mismatch + * java.lang.IllegalArgumentException: argument type mismatch. + * Also this method simplifies testing of [CInteropPrettyException] by wrapping the result in Any that acts like a "Result" class. + * Using "Result" directly might be complicated due to signature mangle and different class loaders. */ fun interopViaReflection( flavor: String, args: Array, runFromDaemon: Boolean, generated: String, natives: String, manifest: String? = null, cstubsName: String? = null - ): Array? { + ): Any? { val internalInteropOptions = InternalInteropOptions(generated, natives, manifest, cstubsName) - return interop(flavor, args, internalInteropOptions, runFromDaemon) + return try { + interop(flavor, args, internalInteropOptions, runFromDaemon) + } catch (prettyException: CInteropPrettyException) { + prettyException + } } fun interop( @@ -237,8 +243,12 @@ private fun processCLibSafe(flavor: KotlinPlatform, cinteropArguments: CInteropA } } -private fun processCLib(flavor: KotlinPlatform, cinteropArguments: CInteropArguments, - additionalArgs: InternalInteropOptions, runFromDaemon: Boolean): Array? { +private fun processCLib( + flavor: KotlinPlatform, + cinteropArguments: CInteropArguments, + additionalArgs: InternalInteropOptions, + runFromDaemon: Boolean, +): Array? = withExceptionPrettifier(cinteropArguments.disableExceptionPrettifier) { val ktGenRoot = additionalArgs.generated val nativeLibsDir = additionalArgs.natives val defFile = cinteropArguments.def?.let { File(it) } diff --git a/native/utils/src/org/jetbrains/kotlin/konan/util/CinteropMessages.kt b/native/utils/src/org/jetbrains/kotlin/konan/util/CinteropMessages.kt new file mode 100644 index 00000000000..d8cdbe0de26 --- /dev/null +++ b/native/utils/src/org/jetbrains/kotlin/konan/util/CinteropMessages.kt @@ -0,0 +1,20 @@ +/* + * 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.konan.util + +// We store pretty cinterop messages because we want to access them in test infra as well. +object CInteropHints { + // It would be nice to mark this property as const when `trimIndent` becomes constexpr. + val fmodulesHint = """ + It seems that library is using clang modules. Try adding `-compiler-option -fmodules` to cinterop. + For example, in case of cocoapods plugin: + + pod("PodName") { + // Add these lines + extraOpts += listOf("-compiler-option", "-fmodules") + } + """.trimIndent() +}