From 25b8a1b35089eec651efecf67dda6587e79db93a Mon Sep 17 00:00:00 2001 From: Youssef Shoaib Date: Sat, 23 Dec 2023 12:17:42 +0000 Subject: [PATCH 1/3] Fix traced CCE when nested different types. --- arrow-libs/core/arrow-core/api/arrow-core.api | 1 + .../kotlin/arrow/core/raise/Fold.kt | 22 ++++++++++++++----- .../kotlin/arrow/core/raise/Trace.kt | 6 ++--- .../kotlin/arrow/core/raise/TraceSpec.kt | 10 +++++++++ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/arrow-libs/core/arrow-core/api/arrow-core.api b/arrow-libs/core/arrow-core/api/arrow-core.api index 0a7d5b8a633..dd80c74b858 100644 --- a/arrow-libs/core/arrow-core/api/arrow-core.api +++ b/arrow-libs/core/arrow-core/api/arrow-core.api @@ -3639,6 +3639,7 @@ public final class arrow/core/raise/RaiseKt { public static final fun toValidated (Lkotlin/jvm/functions/Function1;)Larrow/core/Validated; public static final fun toValidated (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun traced (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; + public static final fun withCause (Ljava/util/concurrent/CancellationException;Ljava/util/concurrent/CancellationException;)Ljava/util/concurrent/CancellationException; public static final fun withError (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun zipOrAccumulate (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function9;)Ljava/lang/Object; public static final fun zipOrAccumulate (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function8;)Ljava/lang/Object; diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Fold.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Fold.kt index 619f5d4cf6a..7ab981cc652 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Fold.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Fold.kt @@ -224,17 +224,29 @@ public inline fun Raise.traced( @BuilderInference block: Raise.() -> A, trace: (trace: Trace, error: Error) -> Unit ): A { - val isOuterTraced = this is DefaultRaise && isTraced - val nested: DefaultRaise = if (isOuterTraced) this as DefaultRaise else DefaultRaise(true) + val nested = DefaultRaise(true) return try { - block.invoke(nested) + block(nested).also { nested.complete() } } catch (e: CancellationException) { + nested.complete() val r: Error = e.raisedOrRethrow(nested) trace(Trace(e), r) - if (isOuterTraced) throw e else raise(r) + // If our outer Raise happens to be traced + // Then we want the stack trace to match the inner one + try { + raise(r) + } catch (rethrown: CancellationException) { + throw rethrown.withCause(e) + } } } +@PublishedApi +internal fun CancellationException.withCause(cause: CancellationException): CancellationException = when (this) { + is RaiseCancellationException -> RaiseCancellationException(raised, raise, cause.cause ?: cause) + else -> this +} + /** Returns the raised value, rethrows the CancellationException if not our scope */ @PublishedApi @Suppress("UNCHECKED_CAST") @@ -262,7 +274,7 @@ internal class DefaultRaise(@PublishedApi internal val isTraced: Boolean) : Rais private class RaiseCancellationExceptionNoTrace(val raised: Any?, val raise: Raise) : CancellationExceptionNoTrace() -private class RaiseCancellationException(val raised: Any?, val raise: Raise) : CancellationException() +private class RaiseCancellationException(val raised: Any?, val raise: Raise, override val cause: Throwable? = null) : CancellationException() internal expect open class CancellationExceptionNoTrace() : CancellationException diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt index 33d303bb41a..29b036a3818 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Trace.kt @@ -20,7 +20,7 @@ public value class Trace(private val exception: CancellationException) { * Note, the first line in the stacktrace will be the `RaiseCancellationException`. * The users call to `raise` can found in the_second line of the stacktrace. */ - public fun stackTraceToString(): String = exception.stackTraceToString() + public fun stackTraceToString(): String = (exception.cause ?: exception).stackTraceToString() /** * Prints the stacktrace. @@ -29,7 +29,7 @@ public value class Trace(private val exception: CancellationException) { * The users call to `raise` can found in the_second line of the stacktrace. */ public fun printStackTrace(): Unit = - exception.printStackTrace() + (exception.cause ?: exception).printStackTrace() /** * Returns the suppressed exceptions that occurred during cancellation of the surrounding coroutines, @@ -39,5 +39,5 @@ public value class Trace(private val exception: CancellationException) { * if the finalizer then results in a `Throwable` it will be added as a `suppressedException` to the [CancellationException]. */ public fun suppressedExceptions(): List = - exception.suppressedExceptions + exception.cause?.suppressedExceptions.orEmpty() + exception.suppressedExceptions } diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/TraceSpec.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/TraceSpec.kt index 7a488a1f2f7..1e3058acbfb 100644 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/TraceSpec.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/TraceSpec.kt @@ -43,4 +43,14 @@ class TraceSpec : StringSpec({ } } } + + "nested tracing - different types" { + either { + traced({ + traced ({ + raise(Unit) + }) { _, _ -> unreachable() } + }) { _, unit -> unit shouldBe Unit } + } + } }) From 5fe16bb1c84287002963fa45c0e53dab335f3770 Mon Sep 17 00:00:00 2001 From: Youssef Shoaib Date: Tue, 23 Jan 2024 00:10:14 +0000 Subject: [PATCH 2/3] Update Fold.kt to match changes from #3349 --- .../kotlin/arrow/core/raise/Fold.kt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Fold.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Fold.kt index a55d6f8ff54..a4729445bf5 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Fold.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Fold.kt @@ -155,6 +155,7 @@ public inline fun fold( * [Raise] context barrier. */ @JvmName("_foldUnsafe") +@OptIn(DelicateRaiseApi::class) public inline fun foldUnsafe( @BuilderInference block: Raise.() -> A, catch: (throwable: Throwable) -> B, @@ -171,7 +172,7 @@ public inline fun foldUnsafe( val res = block(raise) raise.complete() transform(res) - } catch (e: CancellationException) { + } catch (e: RaiseCancellationException) { raise.complete() recover(e.raisedOrRethrow(raise)) } catch (e: Throwable) { @@ -219,6 +220,7 @@ public inline fun foldUnsafe( * but **this only occurs** when composing `traced`. * The stacktrace creation is disabled if no `traced` calls are made within the function composition. */ +@OptIn(DelicateRaiseApi::class) @ExperimentalTraceApi public inline fun Raise.traced( @BuilderInference block: Raise.() -> A, @@ -227,7 +229,7 @@ public inline fun Raise.traced( val nested = DefaultRaise(true) return try { block(nested).also { nested.complete() } - } catch (e: CancellationException) { + } catch (e: Traced) { nested.complete() val r: Error = e.raisedOrRethrow(nested) trace(Trace(e), r) @@ -235,20 +237,20 @@ public inline fun Raise.traced( // Then we want the stack trace to match the inner one try { raise(r) - } catch (rethrown: CancellationException) { + } catch (rethrown: Traced) { throw rethrown.withCause(e) } } } @PublishedApi -internal fun CancellationException.withCause(cause: CancellationException): CancellationException = when (this) { - is RaiseCancellationException -> RaiseCancellationException(raised, raise, cause.cause ?: cause) - else -> this -} +@DelicateRaiseApi +internal fun Traced.withCause(cause: Traced): Traced = + Traced(raised, raise, cause) /** Returns the raised value, rethrows the CancellationException if not our scope */ @PublishedApi +@DelicateRaiseApi @Suppress("UNCHECKED_CAST") internal fun CancellationException.raisedOrRethrow(raise: DefaultRaise): R = when { @@ -263,6 +265,7 @@ internal class DefaultRaise(@PublishedApi internal val isTraced: Boolean) : Rais @PublishedApi internal fun complete(): Boolean = isActive.getAndSet(false) + @OptIn(DelicateRaiseApi::class) override fun raise(r: Any?): Nothing = when { isActive.value -> throw if (isTraced) Traced(r, this) else NoTrace(r, this) else -> throw RaiseLeakedException() @@ -289,7 +292,7 @@ public sealed class RaiseCancellationException( internal expect class NoTrace(raised: Any?, raise: Raise) : RaiseCancellationException @DelicateRaiseApi -internal class Traced(raised: Any?, raise: Raise): RaiseCancellationException(raised, raise) +internal class Traced(raised: Any?, raise: Raise, override val cause: Traced? = null): RaiseCancellationException(raised, raise) private class RaiseLeakedException : IllegalStateException( """ From 12d2cee7e5c565f8300ac432d4350347a7a9ad0a Mon Sep 17 00:00:00 2001 From: Youssef Shoaib Date: Tue, 23 Jan 2024 00:12:03 +0000 Subject: [PATCH 3/3] Update api dump --- arrow-libs/core/arrow-core/api/arrow-core.api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arrow-libs/core/arrow-core/api/arrow-core.api b/arrow-libs/core/arrow-core/api/arrow-core.api index d0ec3a3bb24..e9212941bfe 100644 --- a/arrow-libs/core/arrow-core/api/arrow-core.api +++ b/arrow-libs/core/arrow-core/api/arrow-core.api @@ -3647,7 +3647,7 @@ public final class arrow/core/raise/RaiseKt { public static final fun toValidated (Lkotlin/jvm/functions/Function1;)Larrow/core/Validated; public static final fun toValidated (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun traced (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; - public static final fun withCause (Ljava/util/concurrent/CancellationException;Ljava/util/concurrent/CancellationException;)Ljava/util/concurrent/CancellationException; + public static final fun withCause (Larrow/core/raise/Traced;Larrow/core/raise/Traced;)Larrow/core/raise/Traced; public static final fun withError (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun zipOrAccumulate (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function9;)Ljava/lang/Object; public static final fun zipOrAccumulate (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function8;)Ljava/lang/Object;