[debugger, devkit] IDEA-355621 Add navigate button to jump to read/write lock

GitOrigin-RevId: 2b413681a96e645538d70f4707d2f0ea876c0d1c
This commit is contained in:
Maksim Zuev
2024-09-25 17:41:31 +02:00
committed by intellij-monorepo-bot
parent 0975d43059
commit 8c30d7a5d2
5 changed files with 121 additions and 9 deletions

View File

@@ -322,7 +322,7 @@ public final class XFramesView extends XDebugView {
}
}
public JComponent getDefaultFocusedComponent() {
public XDebuggerFramesList getFramesList() {
return myFramesList;
}
@@ -774,6 +774,10 @@ public final class XFramesView extends XDebugView {
return null;
}
public List<XStackFrame> getHiddenFrames() {
return hiddenFrames;
}
private Optional<ItemWithSeparatorAbove> findFrameWithSeparator() {
// We check only the first frame; otherwise, it's not clear what to do.
// Might be reconsidered in the future.

View File

@@ -254,7 +254,7 @@ public class XDebugSessionTab extends DebuggerSessionTabBase {
XFramesView framesView = new XFramesView(mySession);
registerView(DebuggerContentInfo.FRAME_CONTENT, framesView);
Content framesContent = myUi.createContent(DebuggerContentInfo.FRAME_CONTENT, framesView.getMainPanel(),
XDebuggerBundle.message("debugger.session.tab.frames.title"), null, framesView.getDefaultFocusedComponent());
XDebuggerBundle.message("debugger.session.tab.frames.title"), null, framesView.getFramesList());
framesContent.setCloseable(false);
return framesContent;
}

View File

@@ -15,6 +15,7 @@
<orderEntry type="module" module-name="intellij.platform.debugger.impl" />
<orderEntry type="module" module-name="intellij.platform.execution.impl" />
<orderEntry type="module" module-name="intellij.platform.util.ui" />
<orderEntry type="module" module-name="intellij.platform.core.ui" />
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
</component>
</module>

View File

@@ -2,4 +2,5 @@ debugger.ide.state=IDE state
debugger.read.allowed=Read Access Allowed
debugger.read.state=Read {0}
debugger.write.allowed=Write Access Allowed
debugger.write.state=Write {0}
debugger.write.state=Write {0}
debugger.navigate.to.lock.access=\u2026Navigate

View File

@@ -1,20 +1,26 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.debugger
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.JavaStackFrame
import com.intellij.debugger.engine.JavaValue
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.evaluation.EvaluationContext
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.engine.jdi.StackFrameProxy
import com.intellij.debugger.memory.utils.StackFrameItem
import com.intellij.debugger.ui.tree.ExtraDebugNodesProvider
import com.intellij.icons.AllIcons
import com.intellij.openapi.util.registry.Registry
import com.intellij.xdebugger.frame.*
import com.intellij.xdebugger.impl.XDebugSessionImpl
import com.intellij.xdebugger.impl.frame.XDebuggerFramesList
import com.intellij.xdebugger.impl.frame.XFramesView
import com.sun.jdi.BooleanValue
import com.sun.jdi.ClassType
import com.sun.jdi.ObjectReference
import com.sun.jdi.Value
import javax.swing.Icon
/**
* @see com.intellij.ide.debug.ApplicationStateDebugSupport
@@ -60,18 +66,25 @@ internal class DebugeeIdeStateRenderer : ExtraDebugNodesProvider {
}
override fun computeChildren(node: XCompositeNode) {
addChild(node, "debugger.read.allowed", isReadActionAllowed?.toString(), AllIcons.Actions.ShowReadAccess, isWriteActionAllowed == null)
addChild(node, "debugger.write.allowed", isWriteActionAllowed?.toString(), AllIcons.Actions.ShowWriteAccess, true)
addChild(node, isReadActionAllowed, isRead = true, addLink = isWriteActionAllowed != true)
addChild(node, isWriteActionAllowed, isRead = false, addLink = true)
node.addChildren(XValueChildrenList.EMPTY, true)
}
private fun addChild(node: XCompositeNode, nameKey: String, value: String?, icon: Icon, isLast: Boolean) {
private fun addChild(node: XCompositeNode, value: Boolean?, isRead: Boolean, addLink: Boolean) {
if (value == null) return
val nameKey = if (isRead) "debugger.read.allowed" else "debugger.write.allowed"
val icon = if (isRead) AllIcons.Actions.ShowReadAccess else AllIcons.Actions.ShowWriteAccess
node.addChildren(XValueChildrenList.singleton(DevKitDebuggerBundle.message(nameKey), object : XValue() {
override fun canNavigateToSource(): Boolean = false
override fun computePresentation(node: XValueNode, place: XValuePlace) {
node.setPresentation(icon, null, value, false)
node.setPresentation(icon, null, value.toString(), false)
if (value == true && addLink) {
addLinkToLockAccess(isRead, node, evaluationContext)
}
}
}), isLast)
}), false)
}
})
@@ -148,3 +161,96 @@ private fun findLockAccessIndex(frames: List<StackFrameProxy>): Pair<Int, Int> {
}
return readIndex to writeIndex
}
private fun isLockAccessMethod(frame: StackFrameProxy, isRead: Boolean): Boolean {
val location = frame.location()
val className = location.declaringType().name()
val methodName = location.method().name()
return if (isRead) {
isReadAccessCall(className, methodName)
}
else {
isWriteAccessCall(className, methodName)
}
}
private fun isReadAccessCall(className: String, methodName: String): Boolean =
className == "com.intellij.openapi.application.impl.NonBlockingReadActionImpl" && methodName == "executeSynchronously"
|| ((className == "com.intellij.openapi.application.Application" || className == "com.intellij.openapi.application.impl.ApplicationImpl")
&& (methodName == "runReadAction" || methodName == "runWriteIntentReadAction" || methodName == "tryRunReadAction"))
|| className == "com.intellij.openapi.application.ActionsKt" && methodName == "runReadAction"
|| (className == "com.intellij.openapi.application.ReadAction"
&& (methodName == "run" || methodName == "execute" || methodName == "compute" || methodName == "computeCancellable"))
|| (className == "com.intellij.openapi.application.WriteIntentReadAction"
&& (methodName == "run" || methodName == "compute"))
|| (className == "com.intellij.openapi.application.CoroutinesKt"
&& (methodName == "readAction" || methodName == "smartReadAction" || methodName == "constrainedReadAction"
|| methodName == "readActionUndispatched" || methodName == "constrainedReadActionUndispatched"
|| methodName == "readActionBlocking" || methodName == "smartReadActionBlocking"
|| methodName == "constrainedReadActionBlocking" || methodName == "writeIntentReadAction"))
private fun isWriteAccessCall(className: String, methodName: String): Boolean =
((className == "com.intellij.openapi.application.Application" || className == "com.intellij.openapi.application.impl.ApplicationImpl")
&& (methodName == "runWriteAction" || methodName == "runWriteActionWithNonCancellableProgressInDispatchThread"
|| methodName == "runIntendedWriteActionOnCurrentThread"))
|| className == "com.intellij.openapi.application.ActionsKt" && methodName == "runWriteAction"
|| (className == "com.intellij.openapi.application.WriteAction"
&& (methodName == "run" || methodName == "execute" || methodName == "compute" || methodName == "runAndWait" || methodName == "computeAndWait"))
|| (className == "com.intellij.openapi.application.CoroutinesKt"
&& (methodName == "writeAction" || methodName == "readAndWriteAction" || methodName == "constrainedReadAndWriteAction"))
private fun addLinkToLockAccess(
isRead: Boolean,
node: XValueNode,
evaluationContext: EvaluationContext,
) {
node.setFullValueEvaluator(object : JavaValue.JavaFullValueEvaluator(DevKitDebuggerBundle.message("debugger.navigate.to.lock.access"), evaluationContext as EvaluationContextImpl) {
override fun isShowValuePopup() = false
override fun evaluate(callback: XFullValueEvaluationCallback) {
navigateToStackFrame()
callback.evaluated("")
}
private fun navigateToStackFrame() {
val debugProcess = evaluationContext.debugProcess as? DebugProcessImpl ?: return
val xDebugSession = debugProcess.session.xDebugSession as? XDebugSessionImpl ?: return
val framesView = xDebugSession.sessionTab?.framesView ?: return
val framesList = framesView.framesList
val xFrames = framesList.model.items.filterIsInstance<XStackFrame>()
val asyncFrameIndex = xFrames.indexOfFirst { it is StackFrameItem.CapturedStackFrame }
val syncXFrames = xFrames.flattenJavaFrames(toIndex = indexOrEndOfList(asyncFrameIndex, xFrames))
selectLockFrame(syncXFrames, isRead, framesList)
}
})
}
private fun selectLockFrame(
javaFrames: List<JavaStackFrame>,
isRead: Boolean,
framesList: XDebuggerFramesList,
): Boolean {
val (targetXFrameIndex, _) = javaFrames.withIndex().reversed()
.firstOrNull { (_, frame) -> isLockAccessMethod(frame.stackFrameProxy, isRead) } ?: return false
if (targetXFrameIndex < 0) return false
var targetXFrame: XStackFrame = javaFrames.getOrNull(targetXFrameIndex + 1) ?: return false
if (!framesList.model.contains(targetXFrame)) {
val collapsedFrames = framesList.model.items.filterIsInstance<XFramesView.HiddenStackFramesItem>()
targetXFrame = collapsedFrames.firstOrNull { it.hiddenFrames.contains(targetXFrame) } ?: return false
if (!framesList.model.contains(targetXFrame)) return false
}
framesList.selectFrame(targetXFrame)
return true
}
private fun indexOrEndOfList(index: Int, list: List<*>): Int = if (index < 0) list.size else index
private fun List<XStackFrame>.flattenJavaFrames(toIndex: Int = size) =
subList(0, toIndex).flatMap { frame ->
when (frame) {
is JavaStackFrame -> listOf(frame)
is XFramesView.HiddenStackFramesItem -> frame.hiddenFrames.filterIsInstance<JavaStackFrame>()
else -> emptyList()
}
}