[debugger] IDEA-376098 Compute stack frame position asynchronously

GitOrigin-RevId: ae65f2306ec402f1163e0a10449d550e24b7a27c
This commit is contained in:
Maksim Zuev
2025-07-18 14:37:16 +00:00
committed by intellij-monorepo-bot
parent 8bd029b85b
commit 4a99c1ffd0
8 changed files with 92 additions and 46 deletions
@@ -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<XStackFrame>? =
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<XStackFrame>? =
iterate(null, null, ProgressManager::checkCanceled) { positionManager ->
createStackFramesInternal(positionManager, descriptor) { createStackFrames(it) }
}
@ApiStatus.Internal
fun createStackFramesAsync(descriptor: StackFrameDescriptorImpl): CompletableFuture<List<XStackFrame>?> =
invokeCommandAsCompletableFuture {
iterate(null, null, { checkCanceled() }) { positionManager ->
createStackFramesInternal(positionManager, descriptor) { createStackFramesAsync(it) }
}
}
private inline fun createStackFramesInternal(
manager: PositionManager,
descriptor: StackFrameDescriptorImpl,
extractMultipleFrames: PositionManagerWithMultipleStackFrames.(StackFrameDescriptorImpl) -> List<XStackFrame>?,
): List<XStackFrame> {
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,
@@ -158,17 +158,11 @@ public class JavaExecutionStack extends XExecutionStack {
}
return StackFrameDescriptorImpl.createAsync(stackFrameProxy, myTracker)
.thenApply(this::createFrames);
.thenCompose(this::createFramesAsync);
}
private @NotNull List<XStackFrame> 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<XStackFrame> 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<XStackFrame>> 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);
@@ -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)
}
}
@@ -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<XStackFrame>? {
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<XStackFrame> createStackFrames(@NotNull StackFrameDescriptorImpl descriptor) {
return null;
suspend fun createStackFramesAsync(descriptor: StackFrameDescriptorImpl): List<XStackFrame>? {
return null
}
}
@@ -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 <T> 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)
}
@@ -106,7 +106,7 @@ class KotlinPositionManager(private val debugProcess: DebugProcess) : MultiReque
return ThreeState.UNSURE
}
override fun createStackFrames(descriptor: StackFrameDescriptorImpl): List<XStackFrame>? {
override suspend fun createStackFramesAsync(descriptor: StackFrameDescriptorImpl): List<XStackFrame>? {
DebuggerManagerThreadImpl.assertIsManagerThread()
if (descriptor.location?.isInKotlinSources() != true) {
return null
@@ -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<XStackFrame>?
@@ -32,7 +32,7 @@ import org.jetbrains.kotlin.idea.debugger.coroutine.util.*
private class CoroutineStackFrameInterceptor : StackFrameInterceptor {
override fun createStackFrames(frame: StackFrameProxyImpl, debugProcess: DebugProcessImpl): List<XStackFrame>? {
override suspend fun createStackFrames(frame: StackFrameProxyImpl, debugProcess: DebugProcessImpl): List<XStackFrame>? {
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 {