From 4a99c1ffd0dec8167717bbb9476ff268acfb3b57 Mon Sep 17 00:00:00 2001 From: Maksim Zuev Date: Fri, 18 Jul 2025 14:31:05 +0200 Subject: [PATCH] [debugger] IDEA-376098 Compute stack frame position asynchronously GitOrigin-RevId: ae65f2306ec402f1163e0a10449d550e24b7a27c --- .../engine/CompoundPositionManager.kt | 48 +++++++++++++------ .../debugger/engine/JavaExecutionStack.java | 32 +++++++++---- .../debugger/engine/PositionManagerAsync.kt | 12 +---- .../PositionManagerWithMultipleStackFrames.kt | 20 ++++---- .../com/intellij/debugger/impl/DebugUtils.kt | 13 +++++ .../debugger/core/KotlinPositionManager.kt | 2 +- .../debugger/core/StackFrameInterceptor.kt | 4 +- .../CoroutineStackFrameInterceptor.kt | 7 ++- 8 files changed, 92 insertions(+), 46 deletions(-) diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/CompoundPositionManager.kt b/java/debugger/impl/src/com/intellij/debugger/engine/CompoundPositionManager.kt index 6ac64ee8ca8d..9f005f3971ae 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/CompoundPositionManager.kt +++ b/java/debugger/impl/src/com/intellij/debugger/engine/CompoundPositionManager.kt @@ -28,6 +28,7 @@ import com.intellij.xdebugger.frame.XStackFrame import com.sun.jdi.Location import com.sun.jdi.ReferenceType import com.sun.jdi.request.ClassPrepareRequest +import org.jetbrains.annotations.ApiStatus import java.util.* import java.util.concurrent.CompletableFuture @@ -159,23 +160,40 @@ class CompoundPositionManager() : PositionManagerWithConditionEvaluation, MultiR } }!! - fun createStackFrames(descriptor: StackFrameDescriptorImpl): MutableList? = - iterate(null, null, ProgressManager::checkCanceled) { - if (it is PositionManagerWithMultipleStackFrames) { - val stackFrames = it.createStackFrames(descriptor) - if (stackFrames != null) { - return@iterate stackFrames - } - } - else if (it is PositionManagerEx) { - val xStackFrame = it.createStackFrame(descriptor) - if (xStackFrame != null) { - return@iterate mutableListOf(xStackFrame) - } - } - throw NoDataException.INSTANCE + @ApiStatus.Internal + fun createStackFrames(descriptor: StackFrameDescriptorImpl): List? = + iterate(null, null, ProgressManager::checkCanceled) { positionManager -> + createStackFramesInternal(positionManager, descriptor) { createStackFrames(it) } } + @ApiStatus.Internal + fun createStackFramesAsync(descriptor: StackFrameDescriptorImpl): CompletableFuture?> = + invokeCommandAsCompletableFuture { + iterate(null, null, { checkCanceled() }) { positionManager -> + createStackFramesInternal(positionManager, descriptor) { createStackFramesAsync(it) } + } + } + + private inline fun createStackFramesInternal( + manager: PositionManager, + descriptor: StackFrameDescriptorImpl, + extractMultipleFrames: PositionManagerWithMultipleStackFrames.(StackFrameDescriptorImpl) -> List?, + ): List { + if (manager is PositionManagerWithMultipleStackFrames) { + val stackFrames = manager.extractMultipleFrames(descriptor) + if (stackFrames != null) { + return stackFrames + } + } + else if (manager is PositionManagerEx) { + val xStackFrame = manager.createStackFrame(descriptor) + if (xStackFrame != null) { + return mutableListOf(xStackFrame) + } + } + throw NoDataException.INSTANCE + } + override fun evaluateCondition( context: EvaluationContext, frame: StackFrameProxyImpl, diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java b/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java index 3981ce022887..e9ffeb629e2f 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java @@ -158,17 +158,11 @@ public class JavaExecutionStack extends XExecutionStack { } return StackFrameDescriptorImpl.createAsync(stackFrameProxy, myTracker) - .thenApply(this::createFrames); + .thenCompose(this::createFramesAsync); } private @NotNull List createFrames(StackFrameDescriptorImpl descriptor) { - XStackFrame topFrame = ContainerUtil.getFirstItem(myTopFrames); - if (descriptor.getUiIndex() == 1 && topFrame instanceof JavaStackFrame) { - Method method = descriptor.getMethod(); - if (method != null) { - ((JavaStackFrame)topFrame).getDescriptor().putUserData(BreakpointIntentionAction.CALLER_KEY, DebuggerUtilsEx.methodKey(method)); - } - } + markCallerFrame(descriptor); List customFrames = myDebugProcess.getPositionManager().createStackFrames(descriptor); if (customFrames != null) { @@ -178,6 +172,28 @@ public class JavaExecutionStack extends XExecutionStack { return Collections.singletonList(new JavaStackFrame(descriptor, true)); } + private @NotNull CompletableFuture<@NotNull List> createFramesAsync(StackFrameDescriptorImpl descriptor) { + markCallerFrame(descriptor); + + return myDebugProcess.getPositionManager().createStackFramesAsync(descriptor) + .thenApply(customFrames -> { + if (customFrames != null) { + return customFrames; + } + return Collections.singletonList(new JavaStackFrame(descriptor, true)); + }); + } + + private void markCallerFrame(StackFrameDescriptorImpl descriptor) { + XStackFrame topFrame = ContainerUtil.getFirstItem(myTopFrames); + if (descriptor.getUiIndex() == 1 && topFrame instanceof JavaStackFrame) { + Method method = descriptor.getMethod(); + if (method != null) { + ((JavaStackFrame)topFrame).getDescriptor().putUserData(BreakpointIntentionAction.CALLER_KEY, DebuggerUtilsEx.methodKey(method)); + } + } + } + @Override public @Nullable XStackFrame getTopFrame() { return ContainerUtil.getFirstItem(myTopFrames); diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/PositionManagerAsync.kt b/java/debugger/impl/src/com/intellij/debugger/engine/PositionManagerAsync.kt index 59d2aed0770d..a022caae827e 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/PositionManagerAsync.kt +++ b/java/debugger/impl/src/com/intellij/debugger/engine/PositionManagerAsync.kt @@ -3,10 +3,7 @@ package com.intellij.debugger.engine import com.intellij.debugger.PositionManager import com.intellij.debugger.SourcePosition -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.diagnostic.fileLogger -import com.intellij.openapi.progress.ProgressManager -import com.intellij.openapi.progress.runBlockingMaybeCancellable +import com.intellij.debugger.impl.runBlockingAssertNotInReadAction import com.intellij.util.concurrency.annotations.RequiresBlockingContext import com.sun.jdi.Location import org.jetbrains.annotations.ApiStatus @@ -17,12 +14,7 @@ interface PositionManagerAsync : PositionManager { @RequiresBlockingContext override fun getSourcePosition(location: Location?): SourcePosition? { - if (ApplicationManager.getApplication().isInternal - && ApplicationManager.getApplication().isReadAccessAllowed - && !ProgressManager.getInstance().hasProgressIndicator()) { - fileLogger().error("Call runBlocking from read action without indicator") - } - return runBlockingMaybeCancellable { + return runBlockingAssertNotInReadAction { getSourcePositionAsync(location) } } diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/PositionManagerWithMultipleStackFrames.kt b/java/debugger/impl/src/com/intellij/debugger/engine/PositionManagerWithMultipleStackFrames.kt index 320fbc11cf18..52bbb9636909 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/PositionManagerWithMultipleStackFrames.kt +++ b/java/debugger/impl/src/com/intellij/debugger/engine/PositionManagerWithMultipleStackFrames.kt @@ -1,19 +1,21 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.debugger.engine; +package com.intellij.debugger.engine -import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl; -import com.intellij.xdebugger.frame.XStackFrame; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import com.intellij.debugger.impl.runBlockingAssertNotInReadAction +import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl +import com.intellij.xdebugger.frame.XStackFrame -import java.util.List; +interface PositionManagerWithMultipleStackFrames : PositionManagerWithConditionEvaluation { + @Deprecated("Use createStackFramesAsync instead") + fun createStackFrames(descriptor: StackFrameDescriptorImpl): List? { + return runBlockingAssertNotInReadAction { createStackFramesAsync(descriptor) } + } -public interface PositionManagerWithMultipleStackFrames extends PositionManagerWithConditionEvaluation { /** * Allows to replace a jvm frame with one or several frames, or skip a frame * @return a list of frames to replace the original frame with, or null to use the default mapping */ - default @Nullable List createStackFrames(@NotNull StackFrameDescriptorImpl descriptor) { - return null; + suspend fun createStackFramesAsync(descriptor: StackFrameDescriptorImpl): List? { + return null } } diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/DebugUtils.kt b/java/debugger/impl/src/com/intellij/debugger/impl/DebugUtils.kt index a0457ea078dc..2f6719590db3 100644 --- a/java/debugger/impl/src/com/intellij/debugger/impl/DebugUtils.kt +++ b/java/debugger/impl/src/com/intellij/debugger/impl/DebugUtils.kt @@ -5,12 +5,16 @@ import com.intellij.debugger.engine.DebugProcessEvents import com.intellij.debugger.engine.DebuggerManagerThreadImpl import com.intellij.debugger.engine.DebuggerUtils import com.intellij.debugger.engine.evaluation.EvaluateException +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.diagnostic.fileLogger import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.runBlockingMaybeCancellable import com.intellij.openapi.util.registry.Registry import com.sun.jdi.* import com.sun.jdi.event.ClassPrepareEvent +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.consumeEach import kotlinx.coroutines.future.await @@ -118,3 +122,12 @@ fun preloadAllClasses(vm: VirtualMachine) { } } } + +internal fun runBlockingAssertNotInReadAction(block: suspend CoroutineScope.() -> T): T { + if (ApplicationManager.getApplication().isInternal + && ApplicationManager.getApplication().isReadAccessAllowed + && !ProgressManager.getInstance().hasProgressIndicator()) { + fileLogger().error("Call runBlocking from read action without indicator") + } + return runBlockingMaybeCancellable(block) +} diff --git a/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/KotlinPositionManager.kt b/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/KotlinPositionManager.kt index 21279e967f3e..310a97c01ee9 100644 --- a/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/KotlinPositionManager.kt +++ b/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/KotlinPositionManager.kt @@ -106,7 +106,7 @@ class KotlinPositionManager(private val debugProcess: DebugProcess) : MultiReque return ThreeState.UNSURE } - override fun createStackFrames(descriptor: StackFrameDescriptorImpl): List? { + override suspend fun createStackFramesAsync(descriptor: StackFrameDescriptorImpl): List? { DebuggerManagerThreadImpl.assertIsManagerThread() if (descriptor.location?.isInKotlinSources() != true) { return null diff --git a/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/StackFrameInterceptor.kt b/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/StackFrameInterceptor.kt index b12f9b829e6c..203b540ef0e2 100644 --- a/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/StackFrameInterceptor.kt +++ b/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/StackFrameInterceptor.kt @@ -8,10 +8,12 @@ import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.openapi.components.serviceOrNull import com.intellij.xdebugger.frame.XStackFrame import com.sun.jdi.Location +import org.jetbrains.annotations.ApiStatus import org.jetbrains.kotlin.idea.debugger.core.stepping.CoroutineFilter +@ApiStatus.Internal interface StackFrameInterceptor { - fun createStackFrames( + suspend fun createStackFrames( frame: StackFrameProxyImpl, debugProcess: DebugProcessImpl ): List? diff --git a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineStackFrameInterceptor.kt b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineStackFrameInterceptor.kt index d74d48c822f9..c1d8ee5e0b47 100644 --- a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineStackFrameInterceptor.kt +++ b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineStackFrameInterceptor.kt @@ -32,7 +32,7 @@ import org.jetbrains.kotlin.idea.debugger.coroutine.util.* private class CoroutineStackFrameInterceptor : StackFrameInterceptor { - override fun createStackFrames(frame: StackFrameProxyImpl, debugProcess: DebugProcessImpl): List? { + override suspend fun createStackFrames(frame: StackFrameProxyImpl, debugProcess: DebugProcessImpl): List? { DebuggerManagerThreadImpl.assertIsManagerThread() if (debugProcess.xdebugProcess?.session !is XDebugSessionImpl || frame is SkipCoroutineStackFrameProxyImpl @@ -75,7 +75,10 @@ private class CoroutineStackFrameInterceptor : StackFrameInterceptor { return listOf(stackFrame) } val frameItemLists = CoroutineFrameBuilder.build(stackFrame, withPreFrames = false) - return listOf(stackFrame) + frameItemLists.frames.mapNotNull { it.createFrame(debugProcess) } + return listOf(stackFrame) + frameItemLists.frames.mapNotNull { + val sourcePosition = debugProcess.positionManager.getSourcePositionAsync(it.location) + it.createFrame(debugProcess, sourcePosition) + } } private fun anySuspendFramesBefore(frame: StackFrameProxyImpl): Boolean {