Forbid suspension points in critical sections
#KT-26480: Fixed
This commit is contained in:
@@ -2432,6 +2432,11 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (resolvedCall.getResultingDescriptor() instanceof FunctionDescriptor &&
|
||||||
|
((FunctionDescriptor) resolvedCall.getResultingDescriptor()).isSuspend()) {
|
||||||
|
state.getGlobalCoroutinesContext().checkSuspendCall(resolvedCall);
|
||||||
|
}
|
||||||
|
|
||||||
boolean isSuspendNoInlineCall =
|
boolean isSuspendNoInlineCall =
|
||||||
CoroutineCodegenUtilKt.isSuspendNoInlineCall(resolvedCall, this, state.getLanguageVersionSettings());
|
CoroutineCodegenUtilKt.isSuspendNoInlineCall(resolvedCall, this, state.getLanguageVersionSettings());
|
||||||
boolean isConstructor = resolvedCall.getResultingDescriptor() instanceof ConstructorDescriptor;
|
boolean isConstructor = resolvedCall.getResultingDescriptor() instanceof ConstructorDescriptor;
|
||||||
|
|||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.codegen.coroutines
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.common.peek
|
||||||
|
import org.jetbrains.kotlin.backend.common.pop
|
||||||
|
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
||||||
|
import org.jetbrains.kotlin.diagnostics.Errors.SUSPENSION_POINT_INSIDE_MONITOR
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||||
|
|
||||||
|
class GlobalCoroutinesContext(private val diagnostics: DiagnosticSink) {
|
||||||
|
private var monitorsDepth = 0
|
||||||
|
|
||||||
|
private val inlineLambdaInsideMonitorSourceArgumentIndexes = arrayListOf<Set<Int>>()
|
||||||
|
|
||||||
|
fun pushArgumentIndexes(indexes: Set<Int>) {
|
||||||
|
inlineLambdaInsideMonitorSourceArgumentIndexes.add(indexes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun popArgumentIndexes() {
|
||||||
|
inlineLambdaInsideMonitorSourceArgumentIndexes.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun enterMonitor() {
|
||||||
|
monitorsDepth++
|
||||||
|
}
|
||||||
|
|
||||||
|
fun enterMonitorIfNeeded(index: Int?) {
|
||||||
|
if (index == null) return
|
||||||
|
if (inlineLambdaInsideMonitorSourceArgumentIndexes.peek()?.contains(index) != true) return
|
||||||
|
enterMonitor()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exitMonitor() {
|
||||||
|
assert(monitorsDepth > 0) {
|
||||||
|
"exitMonitor without corresponding enterMonitor"
|
||||||
|
}
|
||||||
|
monitorsDepth--
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exitMonitorIfNeeded(index: Int?) {
|
||||||
|
if (index == null) return
|
||||||
|
if (inlineLambdaInsideMonitorSourceArgumentIndexes.peek()?.contains(index) != true) return
|
||||||
|
exitMonitor()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkSuspendCall(call: ResolvedCall<*>) {
|
||||||
|
if (monitorsDepth != 0) {
|
||||||
|
diagnostics.report(SUSPENSION_POINT_INSIDE_MONITOR.on(call.call.callElement, call.resultingDescriptor))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.codegen.inline
|
|||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.util.ArrayUtil
|
import com.intellij.util.ArrayUtil
|
||||||
import org.jetbrains.kotlin.backend.common.isBuiltInIntercepted
|
import org.jetbrains.kotlin.backend.common.isBuiltInIntercepted
|
||||||
|
import org.jetbrains.kotlin.backend.common.isTopLevelInPackage
|
||||||
import org.jetbrains.kotlin.builtins.BuiltInsPackageFragment
|
import org.jetbrains.kotlin.builtins.BuiltInsPackageFragment
|
||||||
import org.jetbrains.kotlin.codegen.*
|
import org.jetbrains.kotlin.codegen.*
|
||||||
import org.jetbrains.kotlin.codegen.AsmUtil.getMethodAsmFlags
|
import org.jetbrains.kotlin.codegen.AsmUtil.getMethodAsmFlags
|
||||||
@@ -19,6 +20,8 @@ import org.jetbrains.kotlin.codegen.coroutines.createMethodNodeForSuspendCorouti
|
|||||||
import org.jetbrains.kotlin.codegen.coroutines.isBuiltInSuspendCoroutineUninterceptedOrReturnInJvm
|
import org.jetbrains.kotlin.codegen.coroutines.isBuiltInSuspendCoroutineUninterceptedOrReturnInJvm
|
||||||
import org.jetbrains.kotlin.codegen.intrinsics.bytecode
|
import org.jetbrains.kotlin.codegen.intrinsics.bytecode
|
||||||
import org.jetbrains.kotlin.codegen.intrinsics.classId
|
import org.jetbrains.kotlin.codegen.intrinsics.classId
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.common.ControlFlowGraph
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||||
import org.jetbrains.kotlin.config.isReleaseCoroutines
|
import org.jetbrains.kotlin.config.isReleaseCoroutines
|
||||||
@@ -244,7 +247,18 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val reificationResult = reifiedTypeInliner.reifyInstructions(node)
|
val reificationResult = reifiedTypeInliner.reifyInstructions(node)
|
||||||
generateClosuresBodies()
|
|
||||||
|
val hasMonitor = node.instructions.asSequence().any { it.opcode == Opcodes.MONITORENTER }
|
||||||
|
if (hasMonitor) {
|
||||||
|
state.globalCoroutinesContext.pushArgumentIndexes(findInlineLambdasInsideMonitor(node))
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
generateClosuresBodies()
|
||||||
|
} finally {
|
||||||
|
if (hasMonitor) {
|
||||||
|
state.globalCoroutinesContext.popArgumentIndexes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//through generation captured parameters will be added to invocationParamBuilder
|
//through generation captured parameters will be added to invocationParamBuilder
|
||||||
putClosureParametersOnStack()
|
putClosureParametersOnStack()
|
||||||
@@ -301,6 +315,45 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun findInlineLambdasInsideMonitor(node: MethodNode): Set<Int> {
|
||||||
|
val sources = MethodInliner.analyzeMethodNodeBeforeInline(node)
|
||||||
|
|
||||||
|
val cfg = ControlFlowGraph.build(node)
|
||||||
|
val monitorDepthMap = hashMapOf<AbstractInsnNode, Int>()
|
||||||
|
val result = hashSetOf<Int>()
|
||||||
|
|
||||||
|
fun addMonitorDepthToSuccs(index: Int, depth: Int) {
|
||||||
|
val insn = node.instructions[index]
|
||||||
|
monitorDepthMap[insn] = depth
|
||||||
|
val newDepth = when (insn.opcode) {
|
||||||
|
Opcodes.MONITORENTER -> depth + 1
|
||||||
|
Opcodes.MONITOREXIT -> depth - 1
|
||||||
|
else -> depth
|
||||||
|
}
|
||||||
|
for (succIndex in cfg.getSuccessorsIndices(index)) {
|
||||||
|
if (monitorDepthMap[node.instructions[succIndex]] == null) {
|
||||||
|
addMonitorDepthToSuccs(succIndex, newDepth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addMonitorDepthToSuccs(0, 0)
|
||||||
|
|
||||||
|
for (insn in node.instructions.asSequence()) {
|
||||||
|
if (insn !is MethodInsnNode) continue
|
||||||
|
if (!isInvokeOnLambda(insn.owner, insn.name)) continue
|
||||||
|
if (monitorDepthMap[insn]?.let { it > 0 } != true) continue
|
||||||
|
val frame = sources[node.instructions.indexOf(insn)] ?: continue
|
||||||
|
for (source in frame.getStack(frame.stackSize - Type.getArgumentTypes(insn.desc).size - 1).insns) {
|
||||||
|
if (source.opcode == Opcodes.ALOAD) {
|
||||||
|
result.add((source as VarInsnNode).`var`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
private fun isInlinedToInlineFunInKotlinRuntime(): Boolean {
|
private fun isInlinedToInlineFunInKotlinRuntime(): Boolean {
|
||||||
val codegen = this.codegen as? ExpressionCodegen ?: return false
|
val codegen = this.codegen as? ExpressionCodegen ?: return false
|
||||||
val caller = codegen.context.functionDescriptor
|
val caller = codegen.context.functionDescriptor
|
||||||
@@ -310,8 +363,16 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun generateClosuresBodies() {
|
private fun generateClosuresBodies() {
|
||||||
|
val parameters = invocationParamBuilder.buildParameters()
|
||||||
|
|
||||||
for (info in expressionMap.values) {
|
for (info in expressionMap.values) {
|
||||||
info.generateLambdaBody(sourceCompiler, reifiedTypeInliner)
|
val index = parameters.find { it.lambda == info }?.index
|
||||||
|
state.globalCoroutinesContext.enterMonitorIfNeeded(index)
|
||||||
|
try {
|
||||||
|
info.generateLambdaBody(sourceCompiler, reifiedTypeInliner)
|
||||||
|
} finally {
|
||||||
|
state.globalCoroutinesContext.exitMonitorIfNeeded(index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -945,7 +945,7 @@ class MethodInliner(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun analyzeMethodNodeBeforeInline(node: MethodNode): Array<Frame<SourceValue>?> {
|
fun analyzeMethodNodeBeforeInline(node: MethodNode): Array<Frame<SourceValue>?> {
|
||||||
val analyzer = object : Analyzer<SourceValue>(SourceInterpreter()) {
|
val analyzer = object : Analyzer<SourceValue>(SourceInterpreter()) {
|
||||||
override fun newFrame(nLocals: Int, nStack: Int): Frame<SourceValue> {
|
override fun newFrame(nLocals: Int, nStack: Int): Frame<SourceValue> {
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.codegen.`when`.MappingsClassesForWhenByEnum
|
|||||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||||
import org.jetbrains.kotlin.codegen.context.CodegenContext
|
import org.jetbrains.kotlin.codegen.context.CodegenContext
|
||||||
import org.jetbrains.kotlin.codegen.context.RootContext
|
import org.jetbrains.kotlin.codegen.context.RootContext
|
||||||
|
import org.jetbrains.kotlin.codegen.coroutines.GlobalCoroutinesContext
|
||||||
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
|
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
|
||||||
import org.jetbrains.kotlin.codegen.inline.GlobalInlineContext
|
import org.jetbrains.kotlin.codegen.inline.GlobalInlineContext
|
||||||
import org.jetbrains.kotlin.codegen.inline.InlineCache
|
import org.jetbrains.kotlin.codegen.inline.InlineCache
|
||||||
@@ -202,6 +203,7 @@ class GenerationState private constructor(
|
|||||||
}
|
}
|
||||||
val samWrapperClasses: SamWrapperClasses = SamWrapperClasses(this)
|
val samWrapperClasses: SamWrapperClasses = SamWrapperClasses(this)
|
||||||
val globalInlineContext: GlobalInlineContext = GlobalInlineContext(diagnostics)
|
val globalInlineContext: GlobalInlineContext = GlobalInlineContext(diagnostics)
|
||||||
|
val globalCoroutinesContext: GlobalCoroutinesContext = GlobalCoroutinesContext(diagnostics)
|
||||||
val mappingsClassesForWhenByEnum: MappingsClassesForWhenByEnum = MappingsClassesForWhenByEnum(this)
|
val mappingsClassesForWhenByEnum: MappingsClassesForWhenByEnum = MappingsClassesForWhenByEnum(this)
|
||||||
val jvmRuntimeTypes: JvmRuntimeTypes = JvmRuntimeTypes(module, configuration.languageVersionSettings)
|
val jvmRuntimeTypes: JvmRuntimeTypes = JvmRuntimeTypes(module, configuration.languageVersionSettings)
|
||||||
val factory: ClassFileFactory
|
val factory: ClassFileFactory
|
||||||
|
|||||||
@@ -1037,6 +1037,7 @@ public interface Errors {
|
|||||||
DiagnosticFactory0<KtDeclaration> OVERRIDE_BY_INLINE = DiagnosticFactory0.create(WARNING, DECLARATION_SIGNATURE);
|
DiagnosticFactory0<KtDeclaration> OVERRIDE_BY_INLINE = DiagnosticFactory0.create(WARNING, DECLARATION_SIGNATURE);
|
||||||
DiagnosticFactory0<PsiElement> REIFIED_TYPE_PARAMETER_IN_OVERRIDE = DiagnosticFactory0.create(ERROR);
|
DiagnosticFactory0<PsiElement> REIFIED_TYPE_PARAMETER_IN_OVERRIDE = DiagnosticFactory0.create(ERROR);
|
||||||
DiagnosticFactory1<PsiElement, CallableDescriptor> INLINE_CALL_CYCLE = DiagnosticFactory1.create(ERROR, DEFAULT);
|
DiagnosticFactory1<PsiElement, CallableDescriptor> INLINE_CALL_CYCLE = DiagnosticFactory1.create(ERROR, DEFAULT);
|
||||||
|
DiagnosticFactory1<PsiElement, CallableDescriptor> SUSPENSION_POINT_INSIDE_MONITOR = DiagnosticFactory1.create(ERROR, DEFAULT);
|
||||||
DiagnosticFactory0<PsiElement> NON_LOCAL_RETURN_IN_DISABLED_INLINE = DiagnosticFactory0.create(ERROR);
|
DiagnosticFactory0<PsiElement> NON_LOCAL_RETURN_IN_DISABLED_INLINE = DiagnosticFactory0.create(ERROR);
|
||||||
DiagnosticFactory0<KtDeclaration> INLINE_PROPERTY_WITH_BACKING_FIELD = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE);
|
DiagnosticFactory0<KtDeclaration> INLINE_PROPERTY_WITH_BACKING_FIELD = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE);
|
||||||
DiagnosticFactory0<KtAnnotationEntry> NON_INTERNAL_PUBLISHED_API = DiagnosticFactory0.create(ERROR);
|
DiagnosticFactory0<KtAnnotationEntry> NON_INTERNAL_PUBLISHED_API = DiagnosticFactory0.create(ERROR);
|
||||||
|
|||||||
+1
@@ -934,6 +934,7 @@ public class DefaultErrorMessages {
|
|||||||
//Inline non locals
|
//Inline non locals
|
||||||
MAP.put(NON_LOCAL_RETURN_NOT_ALLOWED, "Can''t inline ''{0}'' here: it may contain non-local returns. Add ''crossinline'' modifier to parameter declaration ''{0}''", ELEMENT_TEXT);
|
MAP.put(NON_LOCAL_RETURN_NOT_ALLOWED, "Can''t inline ''{0}'' here: it may contain non-local returns. Add ''crossinline'' modifier to parameter declaration ''{0}''", ELEMENT_TEXT);
|
||||||
MAP.put(INLINE_CALL_CYCLE, "The ''{0}'' invocation is a part of inline cycle", NAME);
|
MAP.put(INLINE_CALL_CYCLE, "The ''{0}'' invocation is a part of inline cycle", NAME);
|
||||||
|
MAP.put(SUSPENSION_POINT_INSIDE_MONITOR, "The ''{0}'' suspension point is inside a critical section", NAME);
|
||||||
MAP.put(NON_LOCAL_RETURN_IN_DISABLED_INLINE, "Non-local returns are not allowed with inlining disabled");
|
MAP.put(NON_LOCAL_RETURN_IN_DISABLED_INLINE, "Non-local returns are not allowed with inlining disabled");
|
||||||
MAP.put(NON_LOCAL_SUSPENSION_POINT, "Suspension functions can be called only within coroutine body");
|
MAP.put(NON_LOCAL_SUSPENSION_POINT, "Suspension functions can be called only within coroutine body");
|
||||||
MAP.put(ILLEGAL_SUSPEND_FUNCTION_CALL, "Suspend function ''{0}'' should be called only from a coroutine or another suspend function", NAME);
|
MAP.put(ILLEGAL_SUSPEND_FUNCTION_CALL, "Suspend function ''{0}'' should be called only from a coroutine or another suspend function", NAME);
|
||||||
|
|||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
val lock = Any()
|
||||||
|
|
||||||
|
inline fun inlineMe(c: () -> Unit) {
|
||||||
|
synchronized(lock) {
|
||||||
|
c()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun monitorInFinally(a: () -> Unit, b: () -> Unit) {
|
||||||
|
try {
|
||||||
|
a()
|
||||||
|
} finally {
|
||||||
|
synchronized(lock) {
|
||||||
|
b()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
compiler/testData/compileKotlinAgainstCustomBinaries/suspensionPointInMonitor/source.kt:10:13: error: the 'suspensionPoint' suspension point is inside a critical section
|
||||||
|
suspensionPoint()
|
||||||
|
^
|
||||||
|
compiler/testData/compileKotlinAgainstCustomBinaries/suspensionPointInMonitor/source.kt:14:13: error: the 'suspensionPoint' suspension point is inside a critical section
|
||||||
|
suspensionPoint()
|
||||||
|
^
|
||||||
|
compiler/testData/compileKotlinAgainstCustomBinaries/suspensionPointInMonitor/source.kt:19:15: error: the 'suspensionPoint' suspension point is inside a critical section
|
||||||
|
{ suspensionPoint() }
|
||||||
|
^
|
||||||
|
COMPILATION_ERROR
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
fun builder(c: suspend () -> Unit) {}
|
||||||
|
|
||||||
|
private val lock = Any()
|
||||||
|
|
||||||
|
suspend fun suspensionPoint() {}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
builder {
|
||||||
|
synchronized(lock) {
|
||||||
|
suspensionPoint()
|
||||||
|
}
|
||||||
|
|
||||||
|
inlineMe {
|
||||||
|
suspensionPoint()
|
||||||
|
}
|
||||||
|
|
||||||
|
monitorInFinally(
|
||||||
|
{ suspensionPoint() },
|
||||||
|
{ suspensionPoint() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -140,6 +140,15 @@ class CompileKotlinAgainstCustomBinariesTest : AbstractKotlinCompilerIntegration
|
|||||||
compileKotlin("main.kt", tmpdir, listOf(compileLibrary("library")))
|
compileKotlin("main.kt", tmpdir, listOf(compileLibrary("library")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun testSuspensionPointInMonitor() {
|
||||||
|
compileKotlin(
|
||||||
|
"source.kt",
|
||||||
|
tmpdir,
|
||||||
|
listOf(compileLibrary("library", additionalOptions = listOf("-Xskip-metadata-version-check"))),
|
||||||
|
additionalOptions = listOf("-Xskip-metadata-version-check")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun testDuplicateObjectInBinaryAndSources() {
|
fun testDuplicateObjectInBinaryAndSources() {
|
||||||
val allDescriptors = analyzeAndGetAllDescriptors(compileLibrary("library"))
|
val allDescriptors = analyzeAndGetAllDescriptors(compileLibrary("library"))
|
||||||
assertEquals(allDescriptors.toString(), 2, allDescriptors.size)
|
assertEquals(allDescriptors.toString(), 2, allDescriptors.size)
|
||||||
|
|||||||
Reference in New Issue
Block a user