[WASM] throwableExtensions implementation

This commit is contained in:
Igor Yakovlev
2022-01-20 17:17:17 +01:00
parent 28b1f1c50f
commit e32f9eb2ce
5 changed files with 149 additions and 11 deletions
@@ -118,7 +118,11 @@ fun WasmCompiledModuleFragment.generateJs(): String {
println(valueAddr) {
console.log(">>> " + importStringFromWasm(valueAddr));
}
},
printError(valueAddr) {
console.error(">>> " + importStringFromWasm(valueAddr));
},
};
function importStringFromWasm(addr) {
@@ -0,0 +1,34 @@
/*
* 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 kotlin
import kotlin.wasm.internal.ExternalInterfaceType
import kotlin.wasm.internal.jsToKotlinStringAdapter
/**
* The base class for all errors and exceptions. Only instances of this class can be thrown or caught.
*
* @param message the detail message string.
* @param cause the cause of this throwable.
*/
public open class Throwable(open val message: String?, open val cause: kotlin.Throwable?) {
constructor(message: String?) : this(message, null)
constructor(cause: Throwable?) : this(cause?.toString(), cause)
constructor() : this(null, null)
private val jsStack: ExternalInterfaceType = captureStackTrace()
internal val stack: String by lazy {
jsToKotlinStringAdapter(jsStack).removePrefix("Error\n")
}
internal var suppressedExceptionsList: MutableList<Throwable>? = null
}
@JsFun("() => new Error().stack")
private external fun captureStackTrace(): ExternalInterfaceType
+8
View File
@@ -35,3 +35,11 @@ internal actual interface Serializable
@WasmImport("runtime", "println")
private fun printlnImpl(messageAddr: Int): Unit =
implementedAsIntrinsic
internal fun printError(error: Any?) {
printErrorImpl(exportString(error.toString()))
}
@WasmImport("runtime", "printError")
private fun printErrorImpl(errorAddr: Int): Unit =
implementedAsIntrinsic
@@ -15,15 +15,14 @@ package kotlin
* - the detailed description of each throwable in the [Throwable.cause] chain.
*/
@SinceKotlin("1.4")
public actual fun Throwable.stackTraceToString(): String =
TODO("Implement stackTraceToString")
public actual fun Throwable.stackTraceToString(): String = ExceptionTraceBuilder().buildFor(this)
/**
* Prints the [detailed description][Throwable.stackTraceToString] of this throwable to console error output.
*/
@SinceKotlin("1.4")
public actual fun Throwable.printStackTrace() {
TODO("Implement printStackTrace")
printError(this.stackTraceToString())
}
/**
@@ -32,14 +31,108 @@ public actual fun Throwable.printStackTrace() {
*/
@SinceKotlin("1.4")
public actual fun Throwable.addSuppressed(exception: Throwable) {
TODO("Implement Throwable.addSuppressed")
if (this !== exception) {
val supressed = suppressedExceptionsList
if (supressed == null) {
suppressedExceptionsList = mutableListOf<Throwable>(exception)
} else {
supressed.add(exception)
}
}
}
/**
* Returns a list of all exceptions that were suppressed in order to deliver this exception.
*/
@SinceKotlin("1.4")
public actual val Throwable.suppressedExceptions: List<Throwable>
get() {
TODO("Implement Throwable.suppressedExceptions")
}
public actual val Throwable.suppressedExceptions: List<Throwable> get() {
return this.suppressedExceptionsList ?: emptyList()
}
private class ExceptionTraceBuilder {
private val target = StringBuilder()
private val visited = mutableListOf<Throwable>()
private var topStack: String = ""
private var topStackStart: Int = 0
fun buildFor(exception: Throwable): String {
exception.dumpFullTrace("", "")
return target.toString()
}
private fun hasSeen(exception: Throwable): Boolean = visited.any { it === exception }
private fun Throwable.dumpFullTrace(indent: String, qualifier: String) {
this.dumpSelfTrace(indent, qualifier) || return
var cause = this.cause
while (cause != null) {
cause.dumpSelfTrace(indent, "Caused by: ") || return
cause = cause.cause
}
}
private fun Throwable.dumpSelfTrace(indent: String, qualifier: String): Boolean {
target.append(indent).append(qualifier)
val shortInfo = this.toString()
if (hasSeen(this)) {
target.append("[CIRCULAR REFERENCE, SEE ABOVE: ").append(shortInfo).append("]\n")
return false
}
visited.add(this)
var stack = this.stack as String?
if (stack != null) {
val stackStart = stack.indexOf(shortInfo).let { if (it < 0) 0 else it + shortInfo.length }
if (stackStart == 0) target.append(shortInfo).append("\n")
if (topStack.isEmpty()) {
topStack = stack
topStackStart = stackStart
} else {
stack = dropCommonFrames(stack, stackStart)
}
if (indent.isNotEmpty()) {
// indent stack, but avoid indenting exception message lines
val messageLines = if (stackStart == 0) 0 else 1 + shortInfo.count { c -> c == '\n' }
stack.lineSequence().forEachIndexed { index: Int, line: String ->
if (index >= messageLines) target.append(indent)
target.append(line).append("\n")
}
} else {
target.append(stack).append("\n")
}
} else {
target.append(shortInfo).append("\n")
}
val suppressed = suppressedExceptions
if (suppressed.isNotEmpty()) {
val suppressedIndent = indent + " "
for (s in suppressed) {
s.dumpFullTrace(suppressedIndent, "Suppressed: ")
}
}
return true
}
private fun dropCommonFrames(stack: String, stackStart: Int): String {
var commonFrames: Int = 0
var lastBreak: Int = 0
var preLastBreak: Int = 0
for (pos in 0 until minOf(topStack.length - topStackStart, stack.length - stackStart)) {
val c = stack[stack.lastIndex - pos]
if (c != topStack[topStack.lastIndex - pos]) break
if (c == '\n') {
commonFrames += 1
preLastBreak = lastBreak
lastBreak = pos
}
}
if (commonFrames <= 1) return stack
while (preLastBreak > 0 && stack[stack.lastIndex - (preLastBreak - 1)] == ' ')
preLastBreak -= 1
// leave 1 common frame to ease matching with the top exception stack
return stack.dropLast(preLastBreak) + "... and ${commonFrames - 1} more common stack frames skipped"
}
}
+1 -2
View File
@@ -26,8 +26,7 @@ public actual fun testOnJs(action: () -> Unit) { }
// TODO: See KT-24975
public actual val isFloat32RangeEnforced: Boolean = false
// TODO: We need to implement this on wasm
actual val supportsSuppressedExceptions: Boolean get() = false
actual val supportsSuppressedExceptions: Boolean get() = true
// TODO: implement named group reference in replacement expression
public actual val supportsNamedCapturingGroup: Boolean get() = false