Provide access to 'coroutineContext' inside suspend functions/lambdas (KT-24829)
This commit is contained in:
+16
-1
@@ -12,8 +12,10 @@ import org.jetbrains.kotlin.descriptors.*
|
|||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
||||||
import org.jetbrains.kotlin.diagnostics.Errors
|
import org.jetbrains.kotlin.diagnostics.Errors
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||||
import org.jetbrains.kotlin.psi.KtExpression
|
import org.jetbrains.kotlin.psi.KtExpression
|
||||||
import org.jetbrains.kotlin.psi.KtThisExpression
|
import org.jetbrains.kotlin.psi.KtThisExpression
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.isCallableReference
|
import org.jetbrains.kotlin.resolve.calls.callUtil.isCallableReference
|
||||||
@@ -74,7 +76,20 @@ object CoroutineSuspendCallChecker : CallChecker {
|
|||||||
val callElement = resolvedCall.call.callElement as KtExpression
|
val callElement = resolvedCall.call.callElement as KtExpression
|
||||||
|
|
||||||
if (!InlineUtil.checkNonLocalReturnUsage(enclosingSuspendFunction, callElement, context.resolutionContext)) {
|
if (!InlineUtil.checkNonLocalReturnUsage(enclosingSuspendFunction, callElement, context.resolutionContext)) {
|
||||||
context.trace.report(Errors.NON_LOCAL_SUSPENSION_POINT.on(reportOn))
|
var shouldReport = true
|
||||||
|
|
||||||
|
// Do not report for KtCodeFragment in a suspend function context
|
||||||
|
val containingFile = callElement.containingFile
|
||||||
|
if (containingFile is KtCodeFragment) {
|
||||||
|
val c = containingFile.context?.getParentOfType<KtExpression>(false)
|
||||||
|
if (c != null && InlineUtil.checkNonLocalReturnUsage(enclosingSuspendFunction, c, context.resolutionContext)) {
|
||||||
|
shouldReport = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldReport) {
|
||||||
|
context.trace.report(Errors.NON_LOCAL_SUSPENSION_POINT.on(reportOn))
|
||||||
|
}
|
||||||
} else if (context.scope.parentsWithSelf.any { it.isScopeForDefaultParameterValuesOf(enclosingSuspendFunction) }) {
|
} else if (context.scope.parentsWithSelf.any { it.isScopeForDefaultParameterValuesOf(enclosingSuspendFunction) }) {
|
||||||
context.trace.report(Errors.UNSUPPORTED.on(reportOn, "suspend function calls in a context of default parameter value"))
|
context.trace.report(Errors.UNSUPPORTED.on(reportOn, "suspend function calls in a context of default parameter value"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,16 +21,26 @@ import org.jetbrains.kotlin.codegen.topLevelClassAsmType
|
|||||||
import org.jetbrains.kotlin.idea.debugger.*
|
import org.jetbrains.kotlin.idea.debugger.*
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments
|
import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.checkers.COROUTINE_CONTEXT_1_3_FQ_NAME
|
||||||
import org.jetbrains.kotlin.load.java.JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT
|
import org.jetbrains.kotlin.load.java.JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT
|
||||||
import org.jetbrains.kotlin.load.java.JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION
|
import org.jetbrains.kotlin.load.java.JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION
|
||||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
|
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||||
import com.sun.jdi.Type as JdiType
|
import com.sun.jdi.Type as JdiType
|
||||||
|
|
||||||
class VariableFinder private constructor(private val context: EvaluationContextImpl, private val frameProxy: StackFrameProxyImpl) {
|
class VariableFinder private constructor(private val context: EvaluationContextImpl, private val frameProxy: StackFrameProxyImpl) {
|
||||||
companion object {
|
companion object {
|
||||||
|
private val COROUTINE_CONTEXT_SIMPLE_NAME = COROUTINE_CONTEXT_1_3_FQ_NAME.shortName().asString()
|
||||||
|
private val CONTINUATION_TYPE = AsmType.getType(Continuation::class.java)
|
||||||
|
|
||||||
|
val SUSPEND_LAMBDA_CLASSES = listOf(
|
||||||
|
"kotlin.coroutines.jvm.internal.SuspendLambda",
|
||||||
|
"kotlin.coroutines.jvm.internal.RestrictedSuspendLambda"
|
||||||
|
)
|
||||||
|
|
||||||
fun instance(context: EvaluationContextImpl): VariableFinder? {
|
fun instance(context: EvaluationContextImpl): VariableFinder? {
|
||||||
val frameProxy = context.frameProxy ?: return null
|
val frameProxy = context.frameProxy ?: return null
|
||||||
return VariableFinder(context, frameProxy)
|
return VariableFinder(context, frameProxy)
|
||||||
@@ -248,9 +258,60 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|
|||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
?.let { return Result(it.value()) }
|
?.let { return Result(it.value()) }
|
||||||
|
|
||||||
|
if (name == COROUTINE_CONTEXT_SIMPLE_NAME) {
|
||||||
|
val coroutineContext = findCoroutineContext()?.takeIf { kind.typeMatches(it.type()) }
|
||||||
|
if (coroutineContext != null) {
|
||||||
|
return Result(coroutineContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun findCoroutineContext(): ObjectReference? {
|
||||||
|
val method = frameProxy.location().safeMethod() ?: return null
|
||||||
|
return findCoroutineContextForLambda(method) ?: findCoroutineContextForMethod(method)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findCoroutineContextForLambda(method: Method): ObjectReference? {
|
||||||
|
if (method.name() != "invokeSuspend" || method.signature() != "(Ljava/lang/Object;)Ljava/lang/Object;") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val thisObject = frameProxy.thisObject() ?: return null
|
||||||
|
val thisType = thisObject.referenceType()
|
||||||
|
|
||||||
|
if (SUSPEND_LAMBDA_CLASSES.none { thisType.isSubtype(it) }) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return findCoroutineContextForContinuation(thisObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findCoroutineContextForMethod(method: Method): ObjectReference? {
|
||||||
|
if (CONTINUATION_TYPE.descriptor + ")" !in method.signature()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val continuationVariable = frameProxy.visibleVariableByName("\$continuation") ?: return null
|
||||||
|
val continuation = frameProxy.getValue(continuationVariable) as? ObjectReference ?: return null
|
||||||
|
return findCoroutineContextForContinuation(continuation)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findCoroutineContextForContinuation(continuation: ObjectReference): ObjectReference? {
|
||||||
|
val continuationType = (continuation.referenceType() as? ClassType)
|
||||||
|
?.allInterfaces()?.firstOrNull { it.name() == Continuation::class.java.name }
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
val getContextMethod = continuationType
|
||||||
|
.methodsByName("getContext", "()Lkotlin/coroutines/CoroutineContext;").firstOrNull()
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
val threadReference = frameProxy.threadProxy().threadReference.takeIf { it.isSuspended } ?: return null
|
||||||
|
val invokePolicy = context.suspendContext.getInvokePolicy()
|
||||||
|
return continuation.invokeMethod(threadReference, getContextMethod, emptyList(), invokePolicy) as? ObjectReference
|
||||||
|
}
|
||||||
|
|
||||||
private fun findCapturedVariableInReceiver(variables: List<LocalVariableProxyImpl>, kind: VariableKind): Result? {
|
private fun findCapturedVariableInReceiver(variables: List<LocalVariableProxyImpl>, kind: VariableKind): Result? {
|
||||||
fun isReceiverOrPassedThis(name: String) =
|
fun isReceiverOrPassedThis(name: String) =
|
||||||
name.startsWith(AsmUtil.LABELED_THIS_PARAMETER)
|
name.startsWith(AsmUtil.LABELED_THIS_PARAMETER)
|
||||||
|
|||||||
@@ -30,6 +30,14 @@ fun ReferenceType.safeAllLineLocations(): List<Location> {
|
|||||||
return DebuggerUtilsEx.allLineLocations(this) ?: emptyList()
|
return DebuggerUtilsEx.allLineLocations(this) ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ReferenceType.safeSourceName(): String? {
|
||||||
|
return try {
|
||||||
|
sourceName()
|
||||||
|
} catch (e: AbsentInformationException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Method.safeLocationsOfLine(line: Int): List<Location> {
|
fun Method.safeLocationsOfLine(line: Int): List<Location> {
|
||||||
return try {
|
return try {
|
||||||
locationsOfLine(line)
|
locationsOfLine(line)
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package coroutineContextFun
|
||||||
|
|
||||||
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
|
suspend fun main() {
|
||||||
|
val a = 5
|
||||||
|
foo()
|
||||||
|
//Breakpoint!
|
||||||
|
foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun foo() {}
|
||||||
|
|
||||||
|
// EXPRESSION: coroutineContext
|
||||||
|
// RESULT: instance of kotlin.coroutines.EmptyCoroutineContext(id=ID): Lkotlin/coroutines/EmptyCoroutineContext;
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
LineBreakpoint created at coroutineContextFun.kt:9
|
||||||
|
Run Java
|
||||||
|
Connected to the target VM
|
||||||
|
coroutineContextFun.kt:9
|
||||||
|
Compile bytecode for coroutineContext
|
||||||
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
Vendored
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package coroutineContextLambda
|
||||||
|
|
||||||
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
|
suspend fun main() {
|
||||||
|
foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var foo: suspend () -> Unit = {
|
||||||
|
bar()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var bar: suspend () -> Unit = {
|
||||||
|
//Breakpoint!
|
||||||
|
val a = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPRESSION: coroutineContext
|
||||||
|
// RESULT: instance of kotlin.coroutines.EmptyCoroutineContext(id=ID): Lkotlin/coroutines/EmptyCoroutineContext;
|
||||||
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
LineBreakpoint created at coroutineContextLambda.kt:15
|
||||||
|
Run Java
|
||||||
|
Connected to the target VM
|
||||||
|
coroutineContextLambda.kt:15
|
||||||
|
Compile bytecode for coroutineContext
|
||||||
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
Generated
+10
@@ -600,6 +600,16 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
|
|||||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/catchVariable.kt");
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/catchVariable.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("coroutineContextFun.kt")
|
||||||
|
public void testCoroutineContextFun() throws Exception {
|
||||||
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/coroutineContextFun.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("coroutineContextLambda.kt")
|
||||||
|
public void testCoroutineContextLambda() throws Exception {
|
||||||
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/coroutineContextLambda.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("delegatedPropertyInClass.kt")
|
@TestMetadata("delegatedPropertyInClass.kt")
|
||||||
public void testDelegatedPropertyInClass() throws Exception {
|
public void testDelegatedPropertyInClass() throws Exception {
|
||||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/delegatedPropertyInClass.kt");
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/delegatedPropertyInClass.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user