Prevent infinite recursion in case of circular suppressed chains
#KT-37603
This commit is contained in:
@@ -15,7 +15,7 @@ package kotlin
|
|||||||
* - the detailed description of each throwable in the [Throwable.cause] chain.
|
* - the detailed description of each throwable in the [Throwable.cause] chain.
|
||||||
*/
|
*/
|
||||||
@SinceKotlin("1.4")
|
@SinceKotlin("1.4")
|
||||||
public actual fun Throwable.toStringWithTrace(): String = buildString { dumpStackTraceTo("", "", this) }
|
public actual fun Throwable.toStringWithTrace(): String = buildString { dumpStackTraceTo("", "", this, arrayOf()) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the specified exception to the list of exceptions that were
|
* Adds the specified exception to the list of exceptions that were
|
||||||
@@ -43,19 +43,27 @@ public actual val Throwable.suppressedExceptions: List<Throwable>
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun Throwable.dumpStackTraceTo(indent: String, qualifier: String, target: StringBuilder) {
|
private fun Array<Throwable>.hasSeen(exception: Throwable): Boolean = any { it === exception }
|
||||||
this.dumpSelfTrace(indent, qualifier, target)
|
|
||||||
|
private fun Throwable.dumpStackTraceTo(indent: String, qualifier: String, target: StringBuilder, visited: Array<Throwable>) {
|
||||||
|
this.dumpSelfTrace(indent, qualifier, target, visited) || return
|
||||||
|
|
||||||
var cause = this.cause
|
var cause = this.cause
|
||||||
while (cause != null) {
|
while (cause != null) {
|
||||||
// TODO: should skip common stack frames
|
// TODO: should skip common stack frames
|
||||||
cause.dumpSelfTrace(indent, "Caused by: ", target)
|
cause.dumpSelfTrace(indent, "Caused by: ", target, visited)
|
||||||
cause = cause.cause
|
cause = cause.cause
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Throwable.dumpSelfTrace(indent: String, qualifier: String, target: StringBuilder) {
|
private fun Throwable.dumpSelfTrace(indent: String, qualifier: String, target: StringBuilder, visited: Array<Throwable>): Boolean {
|
||||||
target.append(indent).append(qualifier)
|
target.append(indent).append(qualifier)
|
||||||
|
if (visited.hasSeen(this)) {
|
||||||
|
target.append("[CIRCULAR REFERENCE, SEE ABOVE: ").append(this).append("]\n")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
visited.asDynamic().push(this)
|
||||||
|
|
||||||
val stack = this.asDynamic().stack as String?
|
val stack = this.asDynamic().stack as String?
|
||||||
if (stack != null) {
|
if (stack != null) {
|
||||||
if (indent.isNotEmpty()) {
|
if (indent.isNotEmpty()) {
|
||||||
@@ -76,7 +84,8 @@ private fun Throwable.dumpSelfTrace(indent: String, qualifier: String, target: S
|
|||||||
if (suppressed.isNotEmpty()) {
|
if (suppressed.isNotEmpty()) {
|
||||||
val suppressedIndent = indent + '\t'
|
val suppressedIndent = indent + '\t'
|
||||||
for (s in suppressed) {
|
for (s in suppressed) {
|
||||||
s.dumpStackTraceTo(suppressedIndent, "Suppressed: ", target)
|
s.dumpStackTraceTo(suppressedIndent, "Suppressed: ", target, visited)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,6 +127,32 @@ class ExceptionTest {
|
|||||||
assertInTrace(causeSuppressed)
|
assertInTrace(causeSuppressed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fail(topLevelTrace) // to dump the entire trace
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun circularSuppressedDetailedTrace() {
|
||||||
|
if (!supportsSuppressedExceptions) return
|
||||||
|
|
||||||
|
// Testing an exception of the following structure
|
||||||
|
// e1
|
||||||
|
// -- suppressed: e3
|
||||||
|
// -- suppressed: e1
|
||||||
|
// Caused by: e2
|
||||||
|
// -- suppressed: e1
|
||||||
|
// Caused by: e3
|
||||||
|
|
||||||
|
val e3 = Exception("e3")
|
||||||
|
val e2 = Error("e2", e3)
|
||||||
|
val e1 = RuntimeException("e1", e2)
|
||||||
|
e1.addSuppressed(e3)
|
||||||
|
e3.addSuppressed(e1)
|
||||||
|
e2.addSuppressed(e1)
|
||||||
|
|
||||||
|
val topLevelTrace = e1.toStringWithTrace()
|
||||||
|
assertEquals(3, Regex.fromLiteral(e1.toString()).findAll(topLevelTrace).count(), topLevelTrace)
|
||||||
|
assertEquals(1, Regex.fromLiteral(e2.toString()).findAll(topLevelTrace).count(), topLevelTrace)
|
||||||
|
assertEquals(2, Regex.fromLiteral(e3.toString()).findAll(topLevelTrace).count(), topLevelTrace)
|
||||||
// fail(topLevelTrace) // to dump the entire trace
|
// fail(topLevelTrace) // to dump the entire trace
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user