Refactor stack trace builder
- omit common stack frames of cause and suppressed exceptions - reuse stringbuilder when building lines (cherry picked from commit c777783a584af0090e8d423802ea86fc3bdb082b)
This commit is contained in:
committed by
Vasily Levchenko
parent
b80b1d65ca
commit
43075acc51
@@ -27,7 +27,7 @@ public open class Throwable(open val message: String?, open val cause: Throwable
|
||||
constructor() : this(null, null)
|
||||
|
||||
@get:ExportForCppRuntime("Kotlin_Throwable_getStackTrace")
|
||||
private val stackTrace = getCurrentStackTrace()
|
||||
private val stackTrace: NativePtrArray = getCurrentStackTrace()
|
||||
|
||||
private val stackTraceStrings: Array<String> by lazy {
|
||||
getStackTraceStrings(stackTrace).freeze()
|
||||
@@ -45,40 +45,83 @@ public open class Throwable(open val message: String?, open val cause: Throwable
|
||||
/**
|
||||
* Prints the [detailed description][Throwable.stackTraceToString] of this throwable to the standard output.
|
||||
*/
|
||||
public fun printStackTrace(): Unit = dumpStackTrace("", "", { println(it) }, mutableSetOf())
|
||||
public fun printStackTrace(): Unit = ExceptionTraceBuilder(this).print()
|
||||
|
||||
internal fun dumpStackTrace(): String = buildString {
|
||||
dumpStackTrace("", "", { appendln(it) }, mutableSetOf())
|
||||
}
|
||||
internal fun dumpStackTrace(): String = ExceptionTraceBuilder(this).build()
|
||||
|
||||
private fun Throwable.dumpStackTrace(indent: String, qualifier: String, writeln: (String) -> Unit, visited: MutableSet<Throwable>) {
|
||||
this.dumpSelfTrace(indent, qualifier, writeln, visited) || return
|
||||
private class ExceptionTraceBuilder(private val top: Throwable) {
|
||||
private val target = StringBuilder()
|
||||
private var printOut = false
|
||||
private val visited = mutableSetOf<Throwable>()
|
||||
|
||||
var cause = this.cause
|
||||
while (cause != null) {
|
||||
// TODO: should skip common stack frames
|
||||
cause.dumpSelfTrace(indent, "Caused by: ", writeln, visited)
|
||||
cause = cause.cause
|
||||
fun build(): String {
|
||||
top.dumpFullTrace("", "")
|
||||
return target.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Throwable.dumpSelfTrace(indent: String, qualifier: String, writeln: (String) -> Unit, visited: MutableSet<Throwable>): Boolean {
|
||||
if (!visited.add(this)) {
|
||||
writeln(indent + qualifier + "[CIRCULAR REFERENCE, SEE ABOVE: $this]")
|
||||
return false
|
||||
fun print() {
|
||||
printOut = true
|
||||
top.dumpFullTrace("", "")
|
||||
}
|
||||
writeln(indent + qualifier + this.toString())
|
||||
for (element in stackTraceStrings) {
|
||||
writeln("$indent\tat $element")
|
||||
}
|
||||
val suppressed = suppressedExceptionsList
|
||||
if (!suppressed.isNullOrEmpty()) {
|
||||
val suppressedIndent = indent + '\t'
|
||||
for (s in suppressed) {
|
||||
s.dumpStackTrace(suppressedIndent, "Suppressed: ", writeln, visited)
|
||||
|
||||
private fun StringBuilder.endln() {
|
||||
if (printOut) {
|
||||
println(this)
|
||||
clear()
|
||||
} else {
|
||||
appendln()
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
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: ")
|
||||
cause = cause.cause
|
||||
}
|
||||
}
|
||||
|
||||
private fun Throwable.dumpSelfTrace(indent: String, qualifier: String): Boolean {
|
||||
if (!visited.add(this)) {
|
||||
target.append(indent).append(qualifier).append("[CIRCULAR REFERENCE, SEE ABOVE: ").append(this).append("]").endln()
|
||||
return false
|
||||
}
|
||||
target.append(indent).append(qualifier).append(this).endln()
|
||||
// leave 1 common frame to ease matching with the top exception stack
|
||||
val commonFrames = (commonStackFrames() - 1).coerceAtLeast(0)
|
||||
for (frameIndex in 0 until stackTraceStrings.size - commonFrames) {
|
||||
val element = stackTraceStrings[frameIndex]
|
||||
target.append(indent).append(" at ").append(element).endln()
|
||||
}
|
||||
if (commonFrames > 0) {
|
||||
target.append(indent).append(" ... and ").append(commonFrames).append(" more common stack frames skipped").endln()
|
||||
}
|
||||
val suppressed = suppressedExceptionsList
|
||||
if (!suppressed.isNullOrEmpty()) {
|
||||
val suppressedIndent = indent + " "
|
||||
for (s in suppressed) {
|
||||
s.dumpFullTrace(suppressedIndent, "Suppressed: ")
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun Throwable.commonStackFrames(): Int {
|
||||
if (top === this) return 0
|
||||
val topStack = top.stackTrace
|
||||
val topSize = topStack.size
|
||||
val thisStack = this.stackTrace
|
||||
val thisSize = thisStack.size
|
||||
val maxSize = minOf(topSize, thisSize)
|
||||
var frame = 0
|
||||
while (frame < maxSize) {
|
||||
if (thisStack[thisSize - 1 - frame] != topStack[topSize - 1 - frame]) break
|
||||
frame++
|
||||
}
|
||||
return frame
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user