Basic exception stacktrace recovery mechanism

  * Implement CoroutineStackFrame in CancellableContinuationImpl, DispatchedContinuation and ScopeCoroutine
  * On coroutine resumption try to reflectively instantiate exception instance of the same type, but with augmented stacktrace
  * Recover stacktrace by walking over CoroutineStackFrame
  * Recover stacktrace on fast-path exceptions without CoroutineStackFrame walking to provide more context to an exception
  * Unwrap exceptions when doing aggregation in JobSupport
  * Add kill-switch to disable stacktrace recovery, introduce method to recover stacktrace on the exceptional fast-path
  * Add `suspendCoroutineOrReturn` on exceptional fast-path in await in order to provide "real" stacktrace

Design rationale:
All recovery of *suspended* continuations takes place in Dispatched.kt file, the only place where all calls to "resume*" ends up, so we don't have to remember about stacktrace recovery in every primitive we are implementing. But to provide more accurate stacktraces we *have to* recover it on every fast-path for better debuggability.

Fixes #493
diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt
index 152f378..d7b990c 100644
--- a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt
+++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt
@@ -50,10 +50,12 @@
 	public static synthetic fun tryResume$default (Lkotlinx/coroutines/CancellableContinuation;Ljava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;)Ljava/lang/Object;
 }
 
-public class kotlinx/coroutines/CancellableContinuationImpl : java/lang/Runnable, kotlinx/coroutines/CancellableContinuation {
+public class kotlinx/coroutines/CancellableContinuationImpl : java/lang/Runnable, kotlin/coroutines/jvm/internal/CoroutineStackFrame, kotlinx/coroutines/CancellableContinuation {
 	public fun <init> (Lkotlin/coroutines/Continuation;I)V
 	public fun completeResume (Ljava/lang/Object;)V
+	public fun getCallerFrame ()Lkotlin/coroutines/jvm/internal/CoroutineStackFrame;
 	public fun getContext ()Lkotlin/coroutines/CoroutineContext;
+	public fun getStackTraceElement ()Ljava/lang/StackTraceElement;
 	public fun getSuccessfulResult (Ljava/lang/Object;)Ljava/lang/Object;
 	public fun initCancellability ()V
 	protected fun nameString ()Ljava/lang/String;
diff --git a/common/kotlinx-coroutines-core-common/src/CancellableContinuation.kt b/common/kotlinx-coroutines-core-common/src/CancellableContinuation.kt
index b50ca7a..5c5d088 100644
--- a/common/kotlinx-coroutines-core-common/src/CancellableContinuation.kt
+++ b/common/kotlinx-coroutines-core-common/src/CancellableContinuation.kt
@@ -218,10 +218,15 @@
 internal open class CancellableContinuationImpl<in T>(
     delegate: Continuation<T>,
     resumeMode: Int
-) : AbstractContinuation<T>(delegate, resumeMode), CancellableContinuation<T>, Runnable {
+) : AbstractContinuation<T>(delegate, resumeMode), CancellableContinuation<T>, Runnable, CoroutineStackFrame {
 
     public override val context: CoroutineContext = delegate.context
 
+    override val callerFrame: CoroutineStackFrame?
+        get() = delegate as? CoroutineStackFrame
+
+    override fun getStackTraceElement(): StackTraceElement? = null
+
     override fun initCancellability() {
         initParentJobInternal(delegate.context[Job])
     }
diff --git a/common/kotlinx-coroutines-core-common/src/Dispatched.kt b/common/kotlinx-coroutines-core-common/src/Dispatched.kt
index 5de38ef..897b1e1 100644
--- a/common/kotlinx-coroutines-core-common/src/Dispatched.kt
+++ b/common/kotlinx-coroutines-core-common/src/Dispatched.kt
@@ -81,10 +81,12 @@
 internal class DispatchedContinuation<in T>(
     @JvmField val dispatcher: CoroutineDispatcher,
     @JvmField val continuation: Continuation<T>
-) : DispatchedTask<T>(MODE_ATOMIC_DEFAULT), Continuation<T> by continuation {
+) : DispatchedTask<T>(MODE_ATOMIC_DEFAULT), CoroutineStackFrame, Continuation<T> by continuation {
     @JvmField
     @Suppress("PropertyName")
     internal var _state: Any? = UNDEFINED
+    override val callerFrame: CoroutineStackFrame? = continuation as? CoroutineStackFrame
+    override fun getStackTraceElement(): StackTraceElement? = null
     @JvmField // pre-cached value to avoid ctx.fold on every resumption
     internal val countOrElement = threadContextElements(context)
 
@@ -167,7 +169,7 @@
     @Suppress("NOTHING_TO_INLINE") // we need it inline to save us an entry on the stack
     inline fun resumeUndispatchedWithException(exception: Throwable) {
         withCoroutineContext(context, countOrElement) {
-            continuation.resumeWithException(exception)
+            continuation.resumeWithStackTrace(exception)
         }
     }
 
@@ -190,7 +192,7 @@
 
 internal fun <T> Continuation<T>.resumeCancellableWithException(exception: Throwable) = when (this) {
     is DispatchedContinuation -> resumeCancellableWithException(exception)
-    else -> resumeWithException(exception)
+    else -> resumeWithStackTrace(exception)
 }
 
 internal fun <T> Continuation<T>.resumeDirect(value: T) = when (this) {
@@ -199,8 +201,8 @@
 }
 
 internal fun <T> Continuation<T>.resumeDirectWithException(exception: Throwable) = when (this) {
-    is DispatchedContinuation -> continuation.resumeWithException(exception)
-    else -> resumeWithException(exception)
+    is DispatchedContinuation -> continuation.resumeWithStackTrace(exception)
+    else -> resumeWithStackTrace(exception)
 }
 
 internal abstract class DispatchedTask<in T>(
@@ -231,7 +233,7 @@
                 else {
                     val exception = getExceptionalResult(state)
                     if (exception != null)
-                        continuation.resumeWithException(exception)
+                        continuation.resumeWithStackTrace(exception)
                     else
                         continuation.resume(getSuccessfulResult(state))
                 }
@@ -275,3 +277,7 @@
         delegate.resumeMode(getSuccessfulResult(state), useMode)
     }
 }
+
+
+@Suppress("NOTHING_TO_INLINE")
+private inline fun Continuation<*>.resumeWithStackTrace(exception: Throwable) = resumeWith(Result.failure(recoverStackTrace(exception, this)))
diff --git a/common/kotlinx-coroutines-core-common/src/JobSupport.kt b/common/kotlinx-coroutines-core-common/src/JobSupport.kt
index b80ce21..3cdc81e 100644
--- a/common/kotlinx-coroutines-core-common/src/JobSupport.kt
+++ b/common/kotlinx-coroutines-core-common/src/JobSupport.kt
@@ -242,8 +242,9 @@
         val seenExceptions = identitySet<Throwable>(exceptions.size)
         var suppressed = false
         for (exception in exceptions) {
-            if (exception !== rootCause && exception !is CancellationException && seenExceptions.add(exception)) {
-                rootCause.addSuppressedThrowable(exception)
+            val unwrapped = unwrap(exception)
+            if (unwrapped !== rootCause && unwrapped !is CancellationException && seenExceptions.add(exception)) {
+                rootCause.addSuppressedThrowable(unwrapped)
                 suppressed = true
             }
         }
@@ -1078,7 +1079,11 @@
             val state = this.state
             if (state !is Incomplete) {
                 // already complete -- just return result
-                if (state is CompletedExceptionally) throw state.cause
+                if (state is CompletedExceptionally) { // Slow path to recover stacktrace
+                    suspendCoroutineUninterceptedOrReturn<Unit> {
+                        throw recoverStackTrace(state.cause, it)
+                    }
+                }
                 return state
 
             }
diff --git a/common/kotlinx-coroutines-core-common/src/channels/AbstractChannel.kt b/common/kotlinx-coroutines-core-common/src/channels/AbstractChannel.kt
index 395988f..48692f1 100644
--- a/common/kotlinx-coroutines-core-common/src/channels/AbstractChannel.kt
+++ b/common/kotlinx-coroutines-core-common/src/channels/AbstractChannel.kt
@@ -176,8 +176,8 @@
             result === OFFER_SUCCESS -> true
             // We should check for closed token on offer as well, otherwise offer won't be linearizable
             // in the face of concurrent close()
-            result === OFFER_FAILED ->  throw closedForSend?.sendException ?: return false
-            result is Closed<*> -> throw result.sendException
+            result === OFFER_FAILED -> throw closedForSend?.sendException?.let { recoverStackTrace(it) } ?: return false
+            result is Closed<*> -> throw recoverStackTrace(result.sendException)
             else -> error("offerInternal returned $result")
         }
     }
@@ -408,7 +408,7 @@
                 when {
                     enqueueResult === ALREADY_SELECTED -> return
                     enqueueResult === ENQUEUE_FAILED -> {} // retry
-                    enqueueResult is Closed<*> -> throw enqueueResult.sendException
+                    enqueueResult is Closed<*> -> throw recoverStackTrace(enqueueResult.sendException)
                     else -> error("performAtomicIfNotSelected(TryEnqueueSendDesc) returned $enqueueResult")
                 }
             } else {
@@ -420,7 +420,7 @@
                         block.startCoroutineUnintercepted(receiver = this, completion = select.completion)
                         return
                     }
-                    offerResult is Closed<*> -> throw offerResult.sendException
+                    offerResult is Closed<*> -> throw recoverStackTrace(offerResult.sendException)
                     else -> error("offerSelectInternal returned $offerResult")
                 }
             }
@@ -574,7 +574,7 @@
 
     @Suppress("UNCHECKED_CAST")
     private fun receiveResult(result: Any?): E {
-        if (result is Closed<*>) throw result.receiveException
+        if (result is Closed<*>) throw recoverStackTrace(result.receiveException)
         return result as E
     }
 
@@ -620,7 +620,7 @@
     @Suppress("UNCHECKED_CAST")
     private fun receiveOrNullResult(result: Any?): E? {
         if (result is Closed<*>) {
-            if (result.closeCause != null) throw result.closeCause
+            if (result.closeCause != null) throw recoverStackTrace(result.closeCause)
             return null
         }
         return result as E
@@ -759,7 +759,7 @@
                 when {
                     pollResult === ALREADY_SELECTED -> return
                     pollResult === POLL_FAILED -> {} // retry
-                    pollResult is Closed<*> -> throw pollResult.receiveException
+                    pollResult is Closed<*> -> throw recoverStackTrace(pollResult.receiveException)
                     else -> {
                         block.startCoroutineUnintercepted(pollResult as E, select.completion)
                         return
@@ -798,8 +798,9 @@
                             if (select.trySelect(null))
                                 block.startCoroutineUnintercepted(null, select.completion)
                             return
-                        } else
-                            throw pollResult.closeCause
+                        } else {
+                            throw recoverStackTrace(pollResult.closeCause)
+                        }
                     }
                     else -> {
                         // selected successfully
@@ -858,7 +859,7 @@
 
         private fun hasNextResult(result: Any?): Boolean {
             if (result is Closed<*>) {
-                if (result.closeCause != null) throw result.receiveException
+                if (result.closeCause != null) recoverStackTrace(throw result.receiveException)
                 return false
             }
             return true
@@ -892,7 +893,7 @@
         @Suppress("UNCHECKED_CAST")
         override suspend fun next(): E {
             val result = this.result
-            if (result is Closed<*>) throw result.receiveException
+            if (result is Closed<*>) throw recoverStackTrace(result.receiveException)
             if (result !== POLL_FAILED) {
                 this.result = POLL_FAILED
                 return result as E
@@ -944,10 +945,11 @@
         }
 
         override fun resumeReceiveClosed(closed: Closed<*>) {
-            val token = if (closed.closeCause == null)
+            val token = if (closed.closeCause == null) {
                 cont.tryResume(false)
-            else
-                cont.tryResumeWithException(closed.receiveException)
+            } else {
+                cont.tryResumeWithException(recoverStackTrace(closed.receiveException, cont))
+            }
             if (token != null) {
                 iterator.result = closed
                 cont.completeResume(token)
diff --git a/common/kotlinx-coroutines-core-common/src/internal/Exceptions.common.kt b/common/kotlinx-coroutines-core-common/src/internal/Exceptions.common.kt
new file mode 100644
index 0000000..46ac8e2
--- /dev/null
+++ b/common/kotlinx-coroutines-core-common/src/internal/Exceptions.common.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlin.coroutines.*
+
+/**
+ * Tries to recover stacktrace for given [exception] and [continuation].
+ * Stacktrace recovery tries to restore [continuation] stack frames using its debug metadata with [CoroutineStackFrame] API
+ * and then reflectively instantiate exception of given type with original exception as a cause and
+ * sets new stacktrace for wrapping exception.
+ * Some frames may be missing due to tail-call elimination.
+ *
+ * Works only on JVM with enabled debug-mode.
+ */
+internal expect fun <E: Throwable> recoverStackTrace(exception: E, continuation: Continuation<*>): E
+
+/**
+ * Tries to recover stacktrace for given [exception]. Used in non-suspendable points of awaiting.
+ * Stacktrace recovery tries to instantiate exception of given type with original exception as a cause.
+ * Wrapping exception will have proper stacktrace as it's instantiated in the right context.
+ *
+ * Works only on JVM with enabled debug-mode.
+ */
+internal expect fun <E: Throwable> recoverStackTrace(exception: E): E
+
+// Name conflict with recoverStackTrace
+@Suppress("NOTHING_TO_INLINE")
+internal expect suspend inline fun recoverAndThrow(exception: Throwable): Nothing
+
+/**
+ * The opposite of [recoverStackTrace].
+ * It is guaranteed that `unwrap(recoverStackTrace(e)) === e`
+ */
+internal expect fun <E: Throwable> unwrap(exception: E): E
+
+expect class StackTraceElement
+
+internal expect interface CoroutineStackFrame {
+    public val callerFrame: CoroutineStackFrame?
+    public fun getStackTraceElement(): StackTraceElement?
+}
+
+/**
+ * Marker that indicates that stacktrace of the exception should not be recovered.
+ * Currently internal, but may become public in the future
+ */
+internal interface NonRecoverableThrowable
diff --git a/common/kotlinx-coroutines-core-common/src/internal/Scopes.kt b/common/kotlinx-coroutines-core-common/src/internal/Scopes.kt
index 4c4f9dd..efa8f04 100644
--- a/common/kotlinx-coroutines-core-common/src/internal/Scopes.kt
+++ b/common/kotlinx-coroutines-core-common/src/internal/Scopes.kt
@@ -14,7 +14,9 @@
 internal open class ScopeCoroutine<in T>(
     context: CoroutineContext,
     @JvmField val uCont: Continuation<T> // unintercepted continuation
-) : AbstractCoroutine<T>(context, true) {
+) : AbstractCoroutine<T>(context, true), CoroutineStackFrame {
+    final override val callerFrame: CoroutineStackFrame? get() = uCont as CoroutineStackFrame?
+    final override fun getStackTraceElement(): StackTraceElement? = null
     override val defaultResumeMode: Int get() = MODE_DIRECT
 
     @Suppress("UNCHECKED_CAST")
diff --git a/common/kotlinx-coroutines-core-common/test/TestBase.common.kt b/common/kotlinx-coroutines-core-common/test/TestBase.common.kt
index 335c748..7bfcf29 100644
--- a/common/kotlinx-coroutines-core-common/test/TestBase.common.kt
+++ b/common/kotlinx-coroutines-core-common/test/TestBase.common.kt
@@ -5,6 +5,7 @@
 package kotlinx.coroutines
 
 import kotlin.coroutines.*
+import kotlinx.coroutines.internal.*
 
 public expect open class TestBase constructor() {
     public val isStressTest: Boolean
@@ -23,13 +24,13 @@
     )
 }
 
-public class TestException(message: String? = null) : Throwable(message)
-public class TestException1(message: String? = null) : Throwable(message)
-public class TestException2(message: String? = null) : Throwable(message)
-public class TestException3(message: String? = null) : Throwable(message)
-public class TestRuntimeException(message: String? = null) : RuntimeException(message)
+public class TestException(message: String? = null) : Throwable(message), NonRecoverableThrowable
+public class TestException1(message: String? = null) : Throwable(message), NonRecoverableThrowable
+public class TestException2(message: String? = null) : Throwable(message), NonRecoverableThrowable
+public class TestException3(message: String? = null) : Throwable(message), NonRecoverableThrowable
+public class TestRuntimeException(message: String? = null) : RuntimeException(message), NonRecoverableThrowable
+public class RecoverableTestException(message: String? = null) : Throwable(message)
 
-// Wrap context to avoid fast-paths on dispatcher comparison
 public fun wrapperDispatcher(context: CoroutineContext): CoroutineContext {
     val dispatcher = context[ContinuationInterceptor] as CoroutineDispatcher
     return object : CoroutineDispatcher() {
diff --git a/core/kotlinx-coroutines-core/src/Debug.kt b/core/kotlinx-coroutines-core/src/Debug.kt
index fc19fee..3796c40 100644
--- a/core/kotlinx-coroutines-core/src/Debug.kt
+++ b/core/kotlinx-coroutines-core/src/Debug.kt
@@ -13,6 +13,20 @@
 public const val DEBUG_PROPERTY_NAME = "kotlinx.coroutines.debug"
 
 /**
+ * Name of the boolean property that controls stacktrace recovery (enabled by default) on JVM.
+ * Stacktrace recovery is enabled if both debug and stacktrace recovery modes are enabled.
+ *
+ * Stacktrace recovery mode wraps every exception into the exception of the same type with original exception
+ * as cause, but with stacktrace of the current coroutine.
+ * Exception is instantiated using reflection by using no-arg, cause or cause and message constructor.
+ * Stacktrace is not recovered if exception is an instance of [CancellationException] or [NonRecoverableThrowable].
+ *
+ * This mechanism is currently supported for channels, [async], [launch], [coroutineScope], [supervisorScope]
+ * and [withContext] builders.
+ */
+internal const val STACKTRACE_RECOVERY_PROPERTY_NAME = "kotlinx.coroutines.stacktrace.recovery"
+
+/**
  * Automatic debug configuration value for [DEBUG_PROPERTY_NAME]. See [newCoroutineContext][CoroutineScope.newCoroutineContext].
  */
 public const val DEBUG_PROPERTY_VALUE_AUTO = "auto"
@@ -36,6 +50,8 @@
     }
 }
 
+internal val RECOVER_STACKTRACE = systemProp(STACKTRACE_RECOVERY_PROPERTY_NAME, true)
+
 // internal debugging tools
 
 internal actual val Any.hexAddress: String
diff --git a/core/kotlinx-coroutines-core/src/internal/Exceptions.kt b/core/kotlinx-coroutines-core/src/internal/Exceptions.kt
new file mode 100644
index 0000000..cab8e64
--- /dev/null
+++ b/core/kotlinx-coroutines-core/src/internal/Exceptions.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlinx.coroutines.*
+import java.util.*
+import kotlin.coroutines.*
+import kotlin.coroutines.intrinsics.*
+
+internal actual fun <E : Throwable> recoverStackTrace(exception: E): E {
+    if (recoveryDisabled(exception)) {
+        return exception
+    }
+
+    val copy = tryCopyException(exception) ?: return exception
+    return copy.sanitizeStackTrace()
+}
+
+private fun <E : Throwable> E.sanitizeStackTrace(): E {
+    val size = stackTrace.size
+
+    var lastIntrinsic = -1
+    for (i in 0 until size) {
+        val name = stackTrace[i].className
+        if ("kotlinx.coroutines.internal.ExceptionsKt" == name) {
+            lastIntrinsic = i
+        }
+    }
+
+    val startIndex = lastIntrinsic + 1
+    val trace = Array(size - lastIntrinsic) {
+        if (it == 0) {
+            artificialFrame("Current coroutine stacktrace")
+        } else {
+            stackTrace[startIndex + it - 1]
+        }
+    }
+
+    stackTrace = trace
+    return this
+}
+
+internal actual fun <E : Throwable> recoverStackTrace(exception: E, continuation: Continuation<*>): E {
+    if (recoveryDisabled(exception) || continuation !is CoroutineStackFrame) {
+        return exception
+    }
+
+    return recoverFromStackFrame(exception, continuation)
+}
+
+private fun <E : Throwable> recoverFromStackFrame(exception: E, continuation: CoroutineStackFrame): E {
+    val newException = tryCopyException(exception) ?: return exception
+    val stacktrace = createStackTrace(continuation)
+    if (stacktrace.isEmpty()) return exception
+    stacktrace.add(0, artificialFrame("Current coroutine stacktrace"))
+    newException.stackTrace = stacktrace.toTypedArray()
+    return newException
+}
+
+
+@Suppress("NOTHING_TO_INLINE")
+internal actual suspend inline fun recoverAndThrow(exception: Throwable): Nothing {
+    if (recoveryDisabled(exception)) throw exception
+    suspendCoroutineUninterceptedOrReturn<Nothing> {
+        if (it !is CoroutineStackFrame) throw exception
+        throw  recoverFromStackFrame(exception, it)
+    }
+}
+
+internal actual fun <E : Throwable> unwrap(exception: E): E {
+    if (recoveryDisabled(exception)) {
+        return exception
+    }
+
+    val element = exception.stackTrace.firstOrNull() ?: return exception
+    if (element.isArtificial()) {
+        @Suppress("UNCHECKED_CAST")
+        return exception.cause as? E ?: exception
+    } else {
+        return exception
+    }
+}
+
+private fun <E : Throwable> recoveryDisabled(exception: E) =
+    !RECOVER_STACKTRACE || !DEBUG || exception is CancellationException || exception is NonRecoverableThrowable
+
+@Suppress("UNCHECKED_CAST")
+private fun <E : Throwable> tryCopyException(exception: E): E? {
+    /*
+     * Try to reflectively find constructor(), constructor(message, cause) or constructor(cause).
+     * Exceptions are shared among coroutines, so we should copy exception before recovering current stacktrace.
+     */
+    var newException: E? = null
+    try {
+        val constructors = exception.javaClass.constructors.sortedByDescending { it.parameterTypes.size }
+        for (constructor in constructors) {
+            val parameters = constructor.parameterTypes
+            if (parameters.size == 2 && parameters[0] == String::class.java && parameters[1] == Throwable::class.java) {
+                newException = constructor.newInstance(exception.message, exception) as E
+            } else if (parameters.size == 1 && parameters[0] == Throwable::class.java) {
+                newException = constructor.newInstance(exception) as E
+            } else if (parameters.isEmpty()) {
+                newException = (constructor.newInstance() as E).also { it.initCause(exception) }
+            }
+
+            if (newException != null) {
+                break
+            }
+        }
+    } catch (e: Exception) {
+        // Do nothing
+    }
+    return newException
+}
+
+private fun createStackTrace(continuation: CoroutineStackFrame): ArrayList<StackTraceElement> {
+    val stack = ArrayList<StackTraceElement>()
+    continuation.getStackTraceElement()?.let { stack.add(it) }
+
+    var last = continuation
+    while (true) {
+        last = (last as? CoroutineStackFrame)?.callerFrame ?: break
+        last.getStackTraceElement()?.let { stack.add(it) }
+    }
+    return stack
+}
+
+
+internal fun artificialFrame(message: String) = java.lang.StackTraceElement("\b\b\b($message", "\b", "\b", -1)
+internal fun StackTraceElement.isArtificial() = className.startsWith("\b\b\b")
+
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+actual typealias CoroutineStackFrame = kotlin.coroutines.jvm.internal.CoroutineStackFrame
+
+actual typealias StackTraceElement = java.lang.StackTraceElement
diff --git a/core/kotlinx-coroutines-core/test/exceptions/StackTraceRecoveryInHierarchiesTest.kt b/core/kotlinx-coroutines-core/test/exceptions/StackTraceRecoveryInHierarchiesTest.kt
new file mode 100644
index 0000000..f243add
--- /dev/null
+++ b/core/kotlinx-coroutines-core/test/exceptions/StackTraceRecoveryInHierarchiesTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.exceptions
+
+import kotlinx.coroutines.*
+import org.junit.Test
+import kotlin.test.*
+
+class StackTraceRecoveryInHierarchiesTest : TestBase() {
+
+    @Test
+    fun testNestedAsync() = runTest {
+        val rootAsync = async(NonCancellable) {
+            expect(1)
+
+            // Just a noise for unwrapping
+            async {
+                expect(2)
+                delay(Long.MAX_VALUE)
+            }
+
+            // Do not catch, fail on cancellation
+            async {
+                expect(3)
+                async {
+                    expect(4)
+                    delay(Long.MAX_VALUE)
+                }
+
+                async {
+                    expect(5)
+                    // 1) await(), catch, verify and rethrow
+                    try {
+                        val nested = async {
+                            expect(6)
+                            throw RecoverableTestException()
+                        }
+
+                        nested.awaitNested()
+                    } catch (e: RecoverableTestException) {
+                        expect(7)
+                        e.verifyException(
+                            "await\$suspendImpl",
+                            "awaitNested",
+                            "\$testNestedAsync\$1\$rootAsync\$1\$2\$2.invokeSuspend"
+                        )
+                        // Just rethrow it
+                        throw e
+                    }
+                }
+            }
+        }
+
+        try {
+            rootAsync.awaitRootLevel()
+        } catch (e: RecoverableTestException) {
+            e.verifyException("await\$suspendImpl", "awaitRootLevel")
+            finish(8)
+        }
+    }
+
+    private suspend fun Deferred<*>.awaitRootLevel() {
+        await()
+        assertTrue(true)
+    }
+
+    private suspend fun Deferred<*>.awaitNested() {
+        await()
+        assertTrue(true)
+    }
+
+    private fun RecoverableTestException.verifyException(vararg expectedTraceElements: String) {
+        // It is "recovered" only once
+        assertEquals(1, depth())
+        val stacktrace = stackTrace.map { it.methodName }.toSet()
+        assertTrue(expectedTraceElements.all { stacktrace.contains(it) })
+    }
+
+    private fun Throwable.depth(): Int {
+        val cause = cause ?: return 0
+        return 1 + cause.depth()
+    }
+}
diff --git a/core/kotlinx-coroutines-core/test/exceptions/StackTraceRecoveryTest.kt b/core/kotlinx-coroutines-core/test/exceptions/StackTraceRecoveryTest.kt
new file mode 100644
index 0000000..d47ced7
--- /dev/null
+++ b/core/kotlinx-coroutines-core/test/exceptions/StackTraceRecoveryTest.kt
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.exceptions
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import org.junit.Test
+import java.io.*
+import java.util.concurrent.*
+import kotlin.test.*
+
+/*
+ * All stacktrace validation skips line numbers
+ */
+class StackTraceRecoveryTest : TestBase() {
+
+    @Test
+    fun testAsync() = runTest {
+        fun createDeferred(depth: Int): Deferred<*> {
+            return if (depth == 0) {
+                async(coroutineContext + NonCancellable) {
+                    throw ExecutionException(null)
+                }
+            } else {
+                createDeferred(depth - 1)
+            }
+        }
+
+        val deferred = createDeferred(3)
+        val traces = listOf(
+            "java.util.concurrent.ExecutionException\n" +
+                    "\t(Current coroutine stacktrace)\n" +
+                    "\tat kotlinx/coroutines/DeferredCoroutine.await\$suspendImpl(Builders.common.kt:99)\n" +
+                    "\tat kotlinx/coroutines/exceptions/StackTraceRecoveryTest.oneMoreNestedMethod(StackTraceRecoveryTest.kt:49)\n" +
+                    "\tat kotlinx/coroutines/exceptions/StackTraceRecoveryTest.nestedMethod(StackTraceRecoveryTest.kt:44)\n" +
+                    "\tat kotlinx/coroutines/exceptions/StackTraceRecoveryTest\$testAsync\$1.invokeSuspend(StackTraceRecoveryTest.kt:17)\n",
+            "Caused by: java.util.concurrent.ExecutionException\n" +
+                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testAsync\$1\$1\$1.invokeSuspend(StackTraceRecoveryTest.kt:21)\n" +
+                    "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n"
+        )
+        nestedMethod(deferred, traces)
+        deferred.join()
+    }
+
+    @Test
+    fun testCompletedAsync() = runTest {
+        val deferred = async(coroutineContext + NonCancellable) {
+            throw ExecutionException(null)
+        }
+
+        deferred.join()
+        val stacktrace = listOf(
+            "java.util.concurrent.ExecutionException\n" +
+                    "\t(Current coroutine stacktrace)\n" +
+                    "\tat kotlinx/coroutines/DeferredCoroutine.await\$suspendImpl(Builders.common.kt:99)\n" +
+                    "\tat kotlinx/coroutines/exceptions/StackTraceRecoveryTest.oneMoreNestedMethod(StackTraceRecoveryTest.kt:81)\n" +
+                    "\tat kotlinx/coroutines/exceptions/StackTraceRecoveryTest.nestedMethod(StackTraceRecoveryTest.kt:75)\n" +
+                    "\tat kotlinx/coroutines/exceptions/StackTraceRecoveryTest\$testCompletedAsync\$1.invokeSuspend(StackTraceRecoveryTest.kt:71)",
+            "Caused by: java.util.concurrent.ExecutionException\n" +
+                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCompletedAsync\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:44)\n" +
+                    "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)"
+        )
+        nestedMethod(deferred, stacktrace)
+    }
+
+    private suspend fun nestedMethod(deferred: Deferred<*>, traces: List<String>) {
+        oneMoreNestedMethod(deferred, traces)
+        assertTrue(true) // Prevent tail-call optimization
+    }
+
+    private suspend fun oneMoreNestedMethod(deferred: Deferred<*>, traces: List<String>) {
+        try {
+            deferred.await()
+            expectUnreached()
+        } catch (e: ExecutionException) {
+            verifyStackTrace(e, traces)
+        }
+    }
+
+    @Test
+    fun testReceiveFromChannel() = runTest {
+        val channel = Channel<Int>()
+        val job = launch {
+            expect(2)
+            channel.close(IllegalArgumentException())
+        }
+
+        expect(1)
+        channelNestedMethod(
+            channel, listOf(
+                "java.lang.IllegalArgumentException\n" +
+                        "\t(Current coroutine stacktrace)\n" +
+                        "\tat kotlinx/coroutines/exceptions/StackTraceRecoveryTest.channelNestedMethod(StackTraceRecoveryTest.kt:110)\n" +
+                        "\tat kotlinx/coroutines/exceptions/StackTraceRecoveryTest\$testReceiveFromChannel\$1.invokeSuspend(StackTraceRecoveryTest.kt:89)",
+                "Caused by: java.lang.IllegalArgumentException\n" +
+                        "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testReceiveFromChannel\$1\$job\$1.invokeSuspend(StackTraceRecoveryTest.kt:93)\n" +
+                        "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n" +
+                        "\tat kotlinx.coroutines.DispatchedTask\$DefaultImpls.run(Dispatched.kt:152)"
+            )
+        )
+        expect(3)
+        job.join()
+        finish(4)
+    }
+
+    @Test
+    fun testReceiveFromClosedChannel() = runTest {
+        val channel = Channel<Int>()
+        channel.close(IllegalArgumentException())
+        channelNestedMethod(
+            channel, listOf(
+                "java.lang.IllegalArgumentException\n" +
+                        "\t(Current coroutine stacktrace)\n" +
+                        "\tat kotlinx.coroutines.channels.AbstractChannel.receiveResult(AbstractChannel.kt:574)\n" +
+                        "\tat kotlinx.coroutines.channels.AbstractChannel.receive(AbstractChannel.kt:567)\n" +
+                        "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.channelNestedMethod(StackTraceRecoveryTest.kt:117)\n" +
+                        "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testReceiveFromClosedChannel\$1.invokeSuspend(StackTraceRecoveryTest.kt:111)\n" +
+                        "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)",
+                "Caused by: java.lang.IllegalArgumentException\n" +
+                        "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testReceiveFromClosedChannel\$1.invokeSuspend(StackTraceRecoveryTest.kt:110)"
+            )
+        )
+    }
+
+    private suspend fun channelNestedMethod(channel: Channel<Int>, traces: List<String>) {
+        try {
+            channel.receive()
+            expectUnreached()
+        } catch (e: IllegalArgumentException) {
+            verifyStackTrace(e, traces)
+        }
+    }
+
+    @Test
+    fun testWithContext() = runTest {
+        val deferred = async(NonCancellable, start = CoroutineStart.LAZY) {
+            throw RecoverableTestException()
+        }
+
+        outerMethod(deferred, listOf(
+            "kotlinx.coroutines.RecoverableTestException\n" +
+                "\t(Current coroutine stacktrace)\n" +
+                "\tat kotlinx/coroutines/DeferredCoroutine.await\$suspendImpl(Builders.common.kt:99)\n" +
+                "\tat kotlinx/coroutines/exceptions/StackTraceRecoveryTest.innerMethod(StackTraceRecoveryTest.kt:158)\n" +
+                "\tat kotlinx/coroutines/exceptions/StackTraceRecoveryTest\$outerMethod\$2.invokeSuspend(StackTraceRecoveryTest.kt:151)\n" +
+                "\tat kotlinx/coroutines/exceptions/StackTraceRecoveryTest.outerMethod(StackTraceRecoveryTest.kt:150)\n" +
+                "\tat kotlinx/coroutines/exceptions/StackTraceRecoveryTest\$testWithContext\$1.invokeSuspend(StackTraceRecoveryTest.kt:141)\n",
+            "Caused by: kotlinx.coroutines.RecoverableTestException\n" +
+                "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testWithContext\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:143)\n" +
+                "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n"))
+        deferred.join()
+    }
+
+    private suspend fun outerMethod(deferred: Deferred<Nothing>, traces: List<String>) {
+        withContext(Dispatchers.IO) {
+            innerMethod(deferred, traces)
+        }
+
+        assertTrue(true)
+    }
+
+    private suspend fun innerMethod(deferred: Deferred<Nothing>, traces: List<String>) {
+        try {
+            deferred.await()
+        } catch (e: RecoverableTestException) {
+            verifyStackTrace(e, traces)
+        }
+    }
+
+    @Test
+    fun testCoroutineScope() = runTest {
+        val deferred = async(NonCancellable, start = CoroutineStart.LAZY) {
+            throw RecoverableTestException()
+        }
+
+        outerScopedMethod(deferred, listOf(
+            "kotlinx.coroutines.RecoverableTestException\n" +
+                    "\t(Current coroutine stacktrace)\n" +
+                    "\tat kotlinx/coroutines/DeferredCoroutine.await\$suspendImpl(Builders.common.kt:99)\n" +
+                    "\tat kotlinx/coroutines/exceptions/StackTraceRecoveryTest.innerMethod(StackTraceRecoveryTest.kt:158)\n" +
+                    "\tat kotlinx/coroutines/exceptions/StackTraceRecoveryTest\$outerScopedMethod\$2.invokeSuspend(StackTraceRecoveryTest.kt:151)\n" +
+                    "\tat kotlinx/coroutines/exceptions/StackTraceRecoveryTest\$testCoroutineScope\$1.invokeSuspend(StackTraceRecoveryTest.kt:141)\n",
+            "Caused by: kotlinx.coroutines.RecoverableTestException\n" +
+                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCoroutineScope\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:143)\n" +
+                    "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n"))
+        deferred.join()
+    }
+
+    private suspend fun outerScopedMethod(deferred: Deferred<Nothing>, traces: List<String>) = coroutineScope {
+        innerMethod(deferred, traces)
+        assertTrue(true)
+    }
+
+    private fun verifyStackTrace(e: Throwable, traces: List<String>) {
+        val stacktrace = toStackTrace(e)
+        traces.forEach {
+            assertTrue(
+                stacktrace.trimStackTrace().contains(it.trimStackTrace()),
+                "\nExpected trace element:\n$it\n\nActual stacktrace:\n$stacktrace"
+            )
+        }
+
+        val causes = stacktrace.count("Caused by")
+        assertNotEquals(0, causes)
+        assertEquals(causes, traces.map { it.count("Caused by") }.sum())
+    }
+
+    private fun toStackTrace(t: Throwable): String {
+        val sw = StringWriter() as Writer
+        t.printStackTrace(PrintWriter(sw))
+        return sw.toString()
+    }
+
+    private fun String.trimStackTrace(): String {
+        return applyBackspace(trimIndent().replace(Regex(":[0-9]+"), "")
+            .replace("kotlinx_coroutines_core_main", "") // yay source sets
+            .replace("kotlinx_coroutines_core", ""))
+    }
+
+    private fun applyBackspace(line: String): String {
+        val array = line.toCharArray()
+        val stack = CharArray(array.size)
+        var stackSize = -1
+        for (c in array) {
+            if (c != '\b') {
+                stack[++stackSize] = c
+            } else {
+                --stackSize
+            }
+        }
+
+        return String(stack, 0, stackSize)
+    }
+
+    private fun String.count(substring: String): Int = split(substring).size - 1
+}
diff --git a/core/kotlinx-coroutines-core/test/exceptions/SuppresionTests.kt b/core/kotlinx-coroutines-core/test/exceptions/SuppresionTests.kt
index ed021b5..b4527c6 100644
--- a/core/kotlinx-coroutines-core/test/exceptions/SuppresionTests.kt
+++ b/core/kotlinx-coroutines-core/test/exceptions/SuppresionTests.kt
@@ -5,15 +5,11 @@
 package kotlinx.coroutines.exceptions
 
 import kotlinx.coroutines.*
-import kotlinx.coroutines.exceptions.*
-import kotlinx.coroutines.selects.*
+import kotlinx.coroutines.channels.*
 import java.io.*
 import kotlin.coroutines.*
 import kotlin.test.*
 
-/*
- * Set of counterparts to common tests which check suppressed exceptions
- */
 class SuppresionTests : TestBase() {
 
     @Test
@@ -24,11 +20,11 @@
         }
 
         expect(1)
-        deferred.cancel(IOException())
+        deferred.cancel(TestException("Message"))
 
         try {
             deferred.await()
-        } catch (e: IOException) {
+        } catch (e: TestException) {
             checkException<ArithmeticException>(e.suppressed[0])
             finish(3)
         }
@@ -62,13 +58,13 @@
 
         coroutine.invokeOnCompletion(onCancelling = true) {
             assertTrue(it is ArithmeticException)
-            assertTrue(it!!.suppressed.isEmpty())
+            assertTrue(it.suppressed.isEmpty())
             expect(6)
         }
 
         coroutine.invokeOnCompletion {
             assertTrue(it is ArithmeticException)
-            checkException<IOException>(it!!.suppressed[0])
+            checkException<IOException>(it.suppressed[0])
             expect(8)
         }
 
@@ -80,4 +76,28 @@
         coroutine.resumeWithException(IOException())
         finish(10)
     }
+
+    @Test
+    fun testExceptionUnwrapping() = runTest {
+        val channel = Channel<Int>()
+
+        val deferred = async(NonCancellable) {
+            launch {
+                while (true) channel.send(1)
+            }
+
+            launch {
+                val exception = RecoverableTestException()
+                channel.cancel(exception)
+                throw exception
+            }
+        }
+
+        try {
+            deferred.await()
+        } catch (e: RecoverableTestException) {
+            assertTrue(e.suppressed.isEmpty())
+            assertTrue(e.cause!!.suppressed.isEmpty())
+        }
+    }
 }
\ No newline at end of file
diff --git a/core/kotlinx-coroutines-core/test/exceptions/WithContextExceptionHandlingTest.kt b/core/kotlinx-coroutines-core/test/exceptions/WithContextExceptionHandlingTest.kt
index 46f04b0..de9f6ca 100644
--- a/core/kotlinx-coroutines-core/test/exceptions/WithContextExceptionHandlingTest.kt
+++ b/core/kotlinx-coroutines-core/test/exceptions/WithContextExceptionHandlingTest.kt
@@ -26,11 +26,11 @@
     fun testCancellation() = runTest {
         /*
          * context cancelled without cause
-         * code itself throws ISE
-         * Result: ISE
+         * code itself throws TE2
+         * Result: TE2
          */
-        runCancellation(null, IllegalStateException()) { e ->
-            assertTrue(e is IllegalStateException)
+        runCancellation(null, TestException2()) { e ->
+            assertTrue(e is TestException2)
             assertNull(e.cause)
             val suppressed = e.suppressed
             assertTrue(suppressed.isEmpty())
@@ -40,30 +40,30 @@
     @Test
     fun testCancellationWithException() = runTest {
         /*
-         * context cancelled with IOE
-         * block itself throws ISE
-         * Result: IOE with suppressed ISE
+         * context cancelled with TE
+         * block itself throws TE2
+         * Result: TE with suppressed TE2
          */
-        val cancellationCause = IOException()
-        runCancellation(cancellationCause, IllegalStateException()) { e ->
-            assertTrue(e is IOException)
+        val cancellationCause = TestException()
+        runCancellation(cancellationCause, TestException2()) { e ->
+            assertTrue(e is TestException)
             assertNull(e.cause)
             val suppressed = e.suppressed
             assertEquals(suppressed.size, 1)
-            assertTrue(suppressed[0] is IllegalStateException)
+            assertTrue(suppressed[0] is TestException2)
         }
     }
 
     @Test
     fun testSameException() = runTest {
         /*
-         * context cancelled with ISE
-         * block itself throws the same ISE
-         * Result: ISE
+         * context cancelled with TE
+         * block itself throws the same TE
+         * Result: TE
          */
-        val cancellationCause = IllegalStateException()
+        val cancellationCause = TestException()
         runCancellation(cancellationCause, cancellationCause) { e ->
-            assertTrue(e is IllegalStateException)
+            assertTrue(e is TestException)
             assertNull(e.cause)
             val suppressed = e.suppressed
             assertTrue(suppressed.isEmpty())
@@ -89,12 +89,12 @@
     @Test
     fun testSameCancellationWithException() = runTest {
         /*
-         * context cancelled with CancellationException(IOE)
-         * block itself throws the same IOE
-         * Result: IOE
+         * context cancelled with CancellationException(TE)
+         * block itself throws the same TE
+         * Result: TE
          */
         val cancellationCause = CancellationException()
-        val exception = IOException()
+        val exception = TestException()
         cancellationCause.initCause(exception)
         runCancellation(cancellationCause, exception) { e ->
             assertSame(exception, e)
@@ -106,13 +106,13 @@
     @Test
     fun testConflictingCancellation() = runTest {
         /*
-         * context cancelled with ISE
-         * block itself throws CE(IOE)
-         * Result: ISE (because cancellation exception is always ignored and not handled)
+         * context cancelled with TE
+         * block itself throws CE(TE)
+         * Result: TE (because cancellation exception is always ignored and not handled)
          */
-        val cancellationCause = IllegalStateException()
+        val cancellationCause = TestException()
         val thrown = CancellationException()
-        thrown.initCause(IOException())
+        thrown.initCause(TestException())
         runCancellation(cancellationCause, thrown) { e ->
             assertSame(cancellationCause, e)
             assertTrue(e.suppressed.isEmpty())
@@ -122,11 +122,11 @@
     @Test
     fun testConflictingCancellation2() = runTest {
         /*
-         * context cancelled with ISE
+         * context cancelled with TE
          * block itself throws CE
-         * Result: ISE
+         * Result: TE
          */
-        val cancellationCause = IllegalStateException()
+        val cancellationCause = TestException()
         val thrown = CancellationException()
         runCancellation(cancellationCause, thrown) { e ->
             assertSame(cancellationCause, e)
@@ -161,9 +161,9 @@
 
     @Test
     fun testThrowingCancellationWithCause() = runTest {
-        // Exception are never unwrapped, so if CE(IOE) is thrown then it is the cancellation cause
+        // Exception are never unwrapped, so if CE(TE) is thrown then it is the cancellation cause
         val thrown = CancellationException()
-        thrown.initCause(IOException())
+        thrown.initCause(TestException())
         runThrowing(thrown) { e ->
            assertSame(thrown, e)
         }
@@ -179,7 +179,7 @@
 
     @Test
     fun testCancelWithCause() = runTest {
-        val cause = IOException()
+        val cause = TestException()
         runOnlyCancellation(cause) { e ->
             assertSame(cause, e)
             assertTrue(e.suppressed.isEmpty())
@@ -206,7 +206,7 @@
     }
 
     private suspend fun runCancellation(
-        cancellationCause: Exception?,
+        cancellationCause: Throwable?,
         thrownException: Throwable,
         exceptionChecker: (Throwable) -> Unit
     ) {
diff --git a/core/kotlinx-coroutines-core/test/test/TestCoroutineContextTest.kt b/core/kotlinx-coroutines-core/test/test/TestCoroutineContextTest.kt
index c5145da..25b9091 100644
--- a/core/kotlinx-coroutines-core/test/test/TestCoroutineContextTest.kt
+++ b/core/kotlinx-coroutines-core/test/test/TestCoroutineContextTest.kt
@@ -269,7 +269,7 @@
     @Test
     fun testExceptionHandlingWithLaunchingChildCoroutines() = withTestContext(injectedContext) {
         val delay = 1000L
-        val expectedError = IllegalAccessError("hello")
+        val expectedError = TestException("hello")
         val expectedValue = 12
 
         launch {
@@ -299,7 +299,7 @@
     @Test
     fun testExceptionHandlingWithAsyncAndWaitForException() = withTestContext(injectedContext) {
         val delay = 1000L
-        val expectedError = IllegalAccessError("hello")
+        val expectedError = TestException("hello")
         val expectedValue = 12
 
         val result = async {
@@ -330,7 +330,7 @@
     @Test
     fun testExceptionHandlingWithRunBlockingAndWaitForException() = withTestContext(injectedContext) {
         val delay = 1000L
-        val expectedError = IllegalAccessError("hello")
+        val expectedError = TestException("hello")
         val expectedValue = 12
 
         try {
diff --git a/js/kotlinx-coroutines-core-js/src/internal/Exceptions.kt b/js/kotlinx-coroutines-core-js/src/internal/Exceptions.kt
new file mode 100644
index 0000000..e0435af
--- /dev/null
+++ b/js/kotlinx-coroutines-core-js/src/internal/Exceptions.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlin.coroutines.*
+
+internal actual fun <E: Throwable> recoverStackTrace(exception: E, continuation: Continuation<*>): E = exception
+internal actual fun <E: Throwable> recoverStackTrace(exception: E): E = exception
+internal actual suspend inline fun recoverAndThrow(exception: Throwable): Nothing = throw exception
+
+internal actual fun <E : Throwable> unwrap(exception: E): E = exception
+
+@Suppress("unused")
+internal actual interface CoroutineStackFrame {
+    public actual val callerFrame: CoroutineStackFrame?
+    public actual fun getStackTraceElement(): StackTraceElement?
+}
+
+actual typealias StackTraceElement = Any
diff --git a/native/kotlinx-coroutines-core-native/src/internal/Exceptions.kt b/native/kotlinx-coroutines-core-native/src/internal/Exceptions.kt
new file mode 100644
index 0000000..923a9b1
--- /dev/null
+++ b/native/kotlinx-coroutines-core-native/src/internal/Exceptions.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlin.coroutines.*
+
+internal actual fun <E: Throwable> recoverStackTrace(exception: E, continuation: Continuation<*>): E = exception
+internal actual fun <E: Throwable> recoverStackTrace(exception: E): E = exception
+internal actual fun <E : Throwable> unwrap(exception: E): E = exception
+internal actual suspend inline fun recoverAndThrow(exception: Throwable): Nothing = throw exception
+
+@Suppress("unused")
+internal actual interface CoroutineStackFrame {
+    public actual val callerFrame: CoroutineStackFrame?
+    public actual fun getStackTraceElement(): StackTraceElement?
+}
+
+actual typealias StackTraceElement = Any