Native: prohibit calling suspend functions from autoreleasepool {}
If a suspend function is called from `autoreleasepool {}` block, this
might cause the autoreleasepool frame to be removed earlier than
expected. See https://youtrack.jetbrains.com/issue/KT-50786 for more
details.
^KT-50786 Fixed
This commit is contained in:
committed by
Space
parent
55cfb3af20
commit
860f99482a
+39
@@ -36,6 +36,9 @@ import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
@@ -682,6 +685,42 @@ private class InteropLoweringPart1(val context: Context) : BaseInteropIrTransfor
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitBlock(expression: IrBlock): IrExpression {
|
||||
if (expression is IrReturnableBlock && expression.inlineFunctionSymbol?.isAutoreleasepool() == true) {
|
||||
// Prohibit calling suspend functions from `autoreleasepool {}` block.
|
||||
// See https://youtrack.jetbrains.com/issue/KT-50786 for more details.
|
||||
// Note: we can't easily check this in frontend, because we need to prohibit indirect cases like
|
||||
/// inline fun <T> myAutoreleasepool(block: () -> T) = autoreleasepool(block)
|
||||
/// myAutoreleasepool { suspendHere() }
|
||||
|
||||
expression.acceptVoid(object : IrElementVisitorVoid {
|
||||
override fun visitElement(element: IrElement) {
|
||||
element.acceptChildrenVoid(this)
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall) {
|
||||
super.visitCall(expression)
|
||||
|
||||
if (expression.symbol.owner.isSuspend) {
|
||||
context.reportCompilationError(
|
||||
"Calling suspend functions from `autoreleasepool {}` is prohibited, " +
|
||||
"see https://youtrack.jetbrains.com/issue/KT-50786",
|
||||
currentFile,
|
||||
expression
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return super.visitBlock(expression)
|
||||
}
|
||||
|
||||
private fun IrFunctionSymbol.isAutoreleasepool(): Boolean {
|
||||
return this.owner.name.asString() == "autoreleasepool" && this.owner.parent.let { parent ->
|
||||
parent is IrPackageFragment && parent.fqName == InteropFqNames.packageName
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.callAllocAndInit(
|
||||
classPtr: IrExpression,
|
||||
initMethodInfo: ObjCMethodInfo,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import kotlinx.cinterop.*
|
||||
import kotlin.coroutines.*
|
||||
|
||||
open class EmptyContinuation(override val context: CoroutineContext = EmptyCoroutineContext) : Continuation<Any?> {
|
||||
companion object : EmptyContinuation()
|
||||
override fun resumeWith(result: Result<Any?>) { result.getOrThrow() }
|
||||
}
|
||||
|
||||
lateinit var continuation: Continuation<Unit>
|
||||
|
||||
suspend fun suspendHere(): Unit = suspendCoroutine { cont ->
|
||||
continuation = cont
|
||||
}
|
||||
|
||||
|
||||
fun startCoroutine(block: suspend () -> Unit) {
|
||||
block.startCoroutine(EmptyContinuation)
|
||||
}
|
||||
|
||||
fun main() {
|
||||
autoreleasepool {
|
||||
startCoroutine {
|
||||
autoreleasepool {
|
||||
suspendHere()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continuation.resume(Unit)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import kotlinx.cinterop.*
|
||||
import kotlin.coroutines.*
|
||||
|
||||
open class EmptyContinuation(override val context: CoroutineContext = EmptyCoroutineContext) : Continuation<Any?> {
|
||||
companion object : EmptyContinuation()
|
||||
override fun resumeWith(result: Result<Any?>) { result.getOrThrow() }
|
||||
}
|
||||
|
||||
lateinit var continuation: Continuation<Unit>
|
||||
|
||||
suspend fun suspendHere(): Unit = suspendCoroutine { cont ->
|
||||
continuation = cont
|
||||
}
|
||||
|
||||
|
||||
fun startCoroutine(block: suspend () -> Unit) {
|
||||
block.startCoroutine(EmptyContinuation)
|
||||
}
|
||||
|
||||
inline fun <T> myAutoreleasepool(block: () -> T) = autoreleasepool(block)
|
||||
|
||||
fun main() {
|
||||
autoreleasepool {
|
||||
startCoroutine {
|
||||
myAutoreleasepool {
|
||||
suspendHere()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continuation.resume(Unit)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package kt50786
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import kotlin.coroutines.*
|
||||
|
||||
open class EmptyContinuation(override val context: CoroutineContext = EmptyCoroutineContext) : Continuation<Any?> {
|
||||
companion object : EmptyContinuation()
|
||||
override fun resumeWith(result: Result<Any?>) { result.getOrThrow() }
|
||||
}
|
||||
|
||||
lateinit var continuation: Continuation<Unit>
|
||||
|
||||
suspend fun suspendHere(): Unit = suspendCoroutine { cont ->
|
||||
continuation = cont
|
||||
}
|
||||
|
||||
|
||||
fun startCoroutine(block: suspend () -> Unit) {
|
||||
block.startCoroutine(EmptyContinuation)
|
||||
}
|
||||
|
||||
@kotlin.test.Test
|
||||
fun testSafeSuspensionIsAllowedInAutoreleasepool() {
|
||||
// This test checks that the compiler doesn't prohibit calling suspend functions from `autoreleasepool {}`
|
||||
// if this call is not actually in the block, but in the local declaration inside it.
|
||||
// See https://youtrack.jetbrains.com/issue/KT-50786 for more details.
|
||||
autoreleasepool {
|
||||
startCoroutine {
|
||||
suspendHere()
|
||||
}
|
||||
}
|
||||
|
||||
continuation.resume(Unit)
|
||||
}
|
||||
Reference in New Issue
Block a user