Skip to content

Commit

Permalink
Verify integrity of the recovered exception's message on both code paths
Browse files Browse the repository at this point in the history
Fixes #2749
  • Loading branch information
qwwdfsad committed Jun 21, 2021
1 parent 93f5e0d commit 41e2cf9
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 18 deletions.
13 changes: 9 additions & 4 deletions kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt
Expand Up @@ -29,7 +29,7 @@ private val stackTraceRecoveryClassName = runCatching {
internal actual fun <E : Throwable> recoverStackTrace(exception: E): E {
if (!RECOVER_STACK_TRACES) return exception
// No unwrapping on continuation-less path: exception is not reported multiple times via slow paths
val copy = tryCopyException(exception) ?: return exception
val copy = tryCopyAndVerify(exception) ?: return exception
return copy.sanitizeStackTrace()
}

Expand Down Expand Up @@ -66,9 +66,7 @@ private fun <E : Throwable> recoverFromStackFrame(exception: E, continuation: Co
val (cause, recoveredStacktrace) = exception.causeAndStacktrace()

// Try to create an exception of the same type and get stacktrace from continuation
val newException = tryCopyException(cause) ?: return exception
// Verify that the new exception has the same message as the original one (bail out if not, see #1631)
if (newException.message != cause.message) return exception
val newException = tryCopyAndVerify(cause) ?: return exception
// Update stacktrace
val stacktrace = createStackTrace(continuation)
if (stacktrace.isEmpty()) return exception
Expand All @@ -80,6 +78,13 @@ private fun <E : Throwable> recoverFromStackFrame(exception: E, continuation: Co
return createFinalException(cause, newException, stacktrace)
}

private fun <E : Throwable> tryCopyAndVerify(exception: E): E? {
val newException = tryCopyException(exception) ?: return null
// Verify that the new exception has the same message as the original one (bail out if not, see #1631)
if (newException.message != exception.message) return null
return newException
}

/*
* Here we partially copy original exception stackTrace to make current one much prettier.
* E.g. for
Expand Down
Expand Up @@ -5,6 +5,7 @@
package kotlinx.coroutines.exceptions

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import org.junit.Test
import kotlin.test.*

Expand Down Expand Up @@ -71,4 +72,33 @@ class StackTraceRecoveryCustomExceptionsTest : TestBase() {
assertEquals("custom", cause.message)
}
}

class WrongMessageException(token: String) : RuntimeException("Token $token")

@Test
fun testWrongMessageException() = runTest {
val result = runCatching {
coroutineScope<Unit> {
throw WrongMessageException("OK")
}
}
val ex = result.exceptionOrNull() ?: error("Expected to fail")
assertTrue(ex is WrongMessageException)
assertEquals("Token OK", ex.message)
}

@Test
fun testWrongMessageExceptionInChannel() = runTest {
// Separate code path
val result = produce<Unit>(SupervisorJob() + Dispatchers.Unconfined) {
throw WrongMessageException("OK")
}
val ex = runCatching {
for (unit in result) {
// Iterator has a special code path
}
}.exceptionOrNull() ?: error("Expected to fail")
assertTrue(ex is WrongMessageException)
assertEquals("Token OK", ex.message)
}
}
Expand Up @@ -261,18 +261,4 @@ class StackTraceRecoveryTest : TestBase() {
}
yield() // nop to make sure it is not a tail call
}

@Test
fun testWrongMessageException() = runTest {
val result = runCatching {
coroutineScope<Unit> {
throw WrongMessageException("OK")
}
}
val ex = result.exceptionOrNull() ?: error("Expected to fail")
assertTrue(ex is WrongMessageException)
assertEquals("Token OK", ex.message)
}

public class WrongMessageException(token: String) : RuntimeException("Token $token")
}

0 comments on commit 41e2cf9

Please sign in to comment.