[WASM] throwableExtensions implementation
This commit is contained in:
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user