Skip to content

Commit 113e034

Browse files
committed
Allow CopyableThrowable to modify exception message
1 parent f8a2123 commit 113e034

File tree

3 files changed

+31
-1
lines changed

3 files changed

+31
-1
lines changed

kotlinx-coroutines-core/common/src/Debug.common.kt

+5
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,15 @@ public interface CopyableThrowable<T> where T : Throwable, T : CopyableThrowable
3232

3333
/**
3434
* Creates a copy of the current instance.
35+
*
3536
* For better debuggability, it is recommended to use original exception as [cause][Throwable.cause] of the resulting one.
3637
* Stacktrace of copied exception will be overwritten by stacktrace recovery machinery by [Throwable.setStackTrace] call.
3738
* An exception can opt-out of copying by returning `null` from this function.
3839
* Suppressed exceptions of the original exception should not be copied in order to avoid circular exceptions.
40+
*
41+
* This function is allowed to create a copy with modified [message][Throwable.message], but it should be noted
42+
* that the copy can be later recovered as well and message modification code should handle this situation correctly
43+
* (e.g. by also storing the original message and checking over it) to produce a human-readable result.
3944
*/
4045
public fun createCopy(): T?
4146
}

kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ private fun <E : Throwable> recoverFromStackFrame(exception: E, continuation: Co
8181
private fun <E : Throwable> tryCopyAndVerify(exception: E): E? {
8282
val newException = tryCopyException(exception) ?: return null
8383
// Verify that the new exception has the same message as the original one (bail out if not, see #1631)
84-
if (newException.message != exception.message) return null
84+
// CopyableThrowable has control over its message and thus can modify iit in the way it want
85+
if (exception !is CopyableThrowable<*> && newException.message != exception.message) return null
8586
return newException
8687
}
8788

kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt

+24
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,35 @@ class StackTraceRecoveryCustomExceptionsTest : TestBase() {
9494
throw WrongMessageException("OK")
9595
}
9696
val ex = runCatching {
97+
@Suppress("ControlFlowWithEmptyBody")
9798
for (unit in result) {
9899
// Iterator has a special code path
99100
}
100101
}.exceptionOrNull() ?: error("Expected to fail")
101102
assertTrue(ex is WrongMessageException)
102103
assertEquals("Token OK", ex.message)
103104
}
105+
106+
class CopyableWithCustomMessage(
107+
message: String?,
108+
cause: Throwable? = null
109+
) : RuntimeException(message, cause),
110+
CopyableThrowable<CopyableWithCustomMessage> {
111+
112+
override fun createCopy(): CopyableWithCustomMessage {
113+
return CopyableWithCustomMessage("Recovered: [$message]", cause)
114+
}
115+
}
116+
117+
@Test
118+
fun testCustomCopyableMessage() = runTest {
119+
val result = runCatching {
120+
coroutineScope<Unit> {
121+
throw CopyableWithCustomMessage("OK")
122+
}
123+
}
124+
val ex = result.exceptionOrNull() ?: error("Expected to fail")
125+
assertTrue(ex is CopyableWithCustomMessage)
126+
assertEquals("Recovered: [OK]", ex.message)
127+
}
104128
}

0 commit comments

Comments
 (0)