[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.
This commit is contained in:
Sergey Bogolepov
2022-12-06 14:47:43 +02:00
committed by Space Team
parent 256764f012
commit 233b01f341
4 changed files with 95 additions and 5 deletions
@@ -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 <T> 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
}
}
@@ -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",
@@ -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<String>,
runFromDaemon: Boolean,
generated: String, natives: String, manifest: String? = null, cstubsName: String? = null
): Array<String>? {
): 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<String>? {
private fun processCLib(
flavor: KotlinPlatform,
cinteropArguments: CInteropArguments,
additionalArgs: InternalInteropOptions,
runFromDaemon: Boolean,
): Array<String>? = withExceptionPrettifier(cinteropArguments.disableExceptionPrettifier) {
val ktGenRoot = additionalArgs.generated
val nativeLibsDir = additionalArgs.natives
val defFile = cinteropArguments.def?.let { File(it) }
@@ -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()
}