async debugger: capture locals values, show captured stack inside the frames view

This commit is contained in:
Egor.Ushakov
2017-01-19 16:31:42 +03:00
parent 7aa92efaed
commit 7634a84ff4
6 changed files with 224 additions and 20 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,14 +22,18 @@ import com.intellij.debugger.impl.DebuggerUtilsEx;
import com.intellij.debugger.jdi.StackFrameProxyImpl;
import com.intellij.debugger.jdi.ThreadGroupReferenceProxyImpl;
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
import com.intellij.debugger.memory.utils.StackFrameItem;
import com.intellij.debugger.ui.breakpoints.StackCapturingLineBreakpoint;
import com.intellij.debugger.ui.impl.watch.MethodsTracker;
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.xdebugger.frame.XExecutionStack;
import com.intellij.xdebugger.frame.XStackFrame;
import com.intellij.xdebugger.settings.XDebuggerSettingsManager;
import com.sun.jdi.Location;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ThreadReference;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -37,6 +41,8 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* @author egor
@@ -184,7 +190,16 @@ public class JavaExecutionStack extends XExecutionStack {
myStackFramesIterator.next();
}
else {
frame = createStackFrame(myStackFramesIterator.next(), myTracker);
StackFrameProxyImpl frameProxy = myStackFramesIterator.next();
// replace the rest with the related stack
List<? extends XStackFrame> relatedStack = getRelatedStack(frameProxy, myDebugProcess);
if (!ContainerUtil.isEmpty(relatedStack)) {
myContainer.addStackFrames(relatedStack, true);
return;
}
frame = createStackFrame(frameProxy, myTracker);
if (first && !myTopFrameReady) {
myTopFrame = frame;
myTopFrameReady = true;
@@ -204,6 +219,22 @@ public class JavaExecutionStack extends XExecutionStack {
}
}
@Nullable
static List<? extends XStackFrame> getRelatedStack(StackFrameProxyImpl frame, DebugProcessImpl process) {
Map<ObjectReference, List<StackFrameItem>> data = process.getUserData(StackCapturingLineBreakpoint.CAPTURED_STACKS);
if (data != null) {
try {
ObjectReference thisObject = frame.thisObject();
if (thisObject != null) {
return data.get(thisObject);
}
}
catch (EvaluateException ignore) {
}
}
return null;
}
private static boolean showFrame(@NotNull XStackFrame frame) {
if (XDebuggerSettingsManager.getInstance().getDataViewSettings().isShowLibraryStackFrames()) return true;
if (frame instanceof JavaStackFrame) {
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -411,7 +411,7 @@ public class PositionManagerImpl implements PositionManager, MultiRequestPositio
}
@Nullable
private static PsiClass findClass(Project project, String originalQName, GlobalSearchScope searchScope) {
public static PsiClass findClass(Project project, String originalQName, GlobalSearchScope searchScope) {
PsiClass psiClass = DebuggerUtils.findClass(originalQName, project, searchScope); // try to lookup original name first
if (psiClass == null) {
int dollar = originalQName.indexOf('$');
@@ -684,7 +684,7 @@ public class PositionManagerImpl implements PositionManager, MultiRequestPositio
}
}
private static class ClsSourcePosition extends RemappedSourcePosition {
public static class ClsSourcePosition extends RemappedSourcePosition {
private final int myOriginalLine;
public ClsSourcePosition(SourcePosition delegate, int originalLine) {
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -243,7 +243,8 @@ public class ConstructorInstancesTracker implements TrackerForNewInstances, Disp
if (myReference.equals(thisRef.referenceType()) && data != null) {
thisRef.disableCollection();
myTrackedObjects.add(thisRef);
final List<StackFrameItem> frame = StackFrameItem.createFrames(suspendContext.getThread());
final List<StackFrameItem> frame =
StackFrameItem.createFrames(suspendContext.getThread(), suspendContext.getDebugProcess(), false);
data.getTrackedStacks().addStack(thisRef, frame);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,26 +15,79 @@
*/
package com.intellij.debugger.memory.utils;
import com.intellij.debugger.SourcePosition;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.PositionManagerImpl;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.jdi.StackFrameProxyImpl;
import com.intellij.debugger.impl.DebuggerUtilsEx;
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
import com.intellij.debugger.settings.NodeRendererSettings;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NullableLazyValue;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiCompiledFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.ui.ColoredTextContainer;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.util.PlatformIcons;
import com.intellij.util.ui.EmptyIcon;
import com.intellij.util.ui.JBUI;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.frame.*;
import com.sun.jdi.Location;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.PrimitiveValue;
import com.sun.jdi.Value;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class StackFrameItem {
public class StackFrameItem extends XStackFrame {
private static final Logger LOG = Logger.getInstance(StackFrameItem.class);
private final Project myProject;
private final GlobalSearchScope mySearchScope;
private final String myFilePath;
private final String myMethodName;
private final int myLineNumber;
private List<VariableItem> myVariables = null;
public StackFrameItem(@NotNull String path, @NotNull String methodName, int line) {
private final NullableLazyValue<XSourcePosition> mySourcePosition;
public StackFrameItem(Project project,
GlobalSearchScope scope,
@NotNull String path,
@NotNull String methodName,
int line) {
myProject = project;
mySearchScope = scope;
myFilePath = path.replace('\\', '.');
myMethodName = methodName;
myLineNumber = line;
//TODO: need to reuse PositionManager somehow
mySourcePosition = NullableLazyValue.createValue(() -> {
PsiClass psiClass = PositionManagerImpl.findClass(myProject, myFilePath, mySearchScope);
if (psiClass == null) {
return null;
}
SourcePosition position = SourcePosition.createFromLine(psiClass.getContainingFile(), myLineNumber - 1);
PsiFile psiFile = psiClass.getContainingFile().getOriginalFile();
if (psiFile instanceof PsiCompiledFile) {
position = new PositionManagerImpl.ClsSourcePosition(position, myLineNumber - 1);
}
return DebuggerUtilsEx.toXSourcePosition(position);
});
}
@NotNull
@@ -61,20 +114,138 @@ public class StackFrameItem {
return myLineNumber;
}
public static List<StackFrameItem> createFrames(ThreadReferenceProxyImpl threadReferenceProxy) throws EvaluateException {
List<StackFrameProxyImpl> stack = threadReferenceProxy == null ? null : threadReferenceProxy.frames();
@Override
public void customizePresentation(@NotNull ColoredTextContainer component) {
component.setIcon(JBUI.scale(EmptyIcon.create(6)));
component.append(String.format("%s:%d, %s", myMethodName, myLineNumber, myFilePath), SimpleTextAttributes.REGULAR_ATTRIBUTES);
}
if (stack != null) {
return StreamEx.of(stack).map(frame -> {
@Nullable
@Override
public XSourcePosition getSourcePosition() {
return mySourcePosition.getValue();
}
void addVariable(VariableItem var) {
if (myVariables == null) {
myVariables = new ArrayList<>();
}
myVariables.add(var);
}
public static List<StackFrameItem> createFrames(@Nullable ThreadReferenceProxyImpl threadReferenceProxy,
DebugProcessImpl process,
boolean withVars)
throws EvaluateException {
if (threadReferenceProxy != null) {
return StreamEx.of(threadReferenceProxy.frames()).map(frame -> {
try {
Location loc = frame.location();
return new StackFrameItem(loc.declaringType().name(), loc.method().name(), loc.lineNumber());
StackFrameItem frameItem = new StackFrameItem(process.getProject(),
process.getSearchScope(),
loc.declaringType().name(),
loc.method().name(),
loc.lineNumber());
if (withVars) {
try {
ObjectReference thisObject = frame.thisObject();
if (thisObject != null) {
frameItem.addVariable(createVariable(thisObject, "this", VariableItem.VarType.OBJECT));
}
}
catch (EvaluateException e) {
LOG.error(e);
}
try {
frame.visibleVariables().forEach(v -> {
try {
Value value = frame.getValue(v);
VariableItem.VarType varType = VariableItem.VarType.OBJECT;
if (v.getVariable().isArgument()) {
varType = VariableItem.VarType.PARAM;
}
else if (value instanceof PrimitiveValue) {
varType = VariableItem.VarType.PRIMITIVE;
}
frameItem.addVariable(createVariable(value, v.name(), varType));
}
catch (EvaluateException e) {
LOG.error(e);
}
});
}
catch (EvaluateException ignore) {
}
}
return frameItem;
}
catch (EvaluateException e) {
LOG.error(e);
return null;
}
}).nonNull().toList();
}
return Collections.emptyList();
}
private static VariableItem createVariable(Value value, String name, VariableItem.VarType varType) {
String type = null;
String valueText = "null";
if (value instanceof ObjectReference) {
valueText = "";
type = value.type().name() + "@" + ((ObjectReference)value).uniqueID();
}
else if (value != null) {
valueText = value.toString();
type = value.type().name();
}
return new VariableItem(name, type, valueText, varType);
}
@Override
public void computeChildren(@NotNull XCompositeNode node) {
if (myVariables != null) {
XValueChildrenList children = new XValueChildrenList();
myVariables.forEach(v -> children.add(v.myName, new XValue() {
@Override
public void computePresentation(@NotNull XValueNode node, @NotNull XValuePlace place) {
String type = NodeRendererSettings.getInstance().getClassRenderer().renderTypeName(v.myType);
Icon icon;
switch (v.myVarType) {
case PARAM:
icon = PlatformIcons.PARAMETER_ICON;
break;
case PRIMITIVE:
icon = AllIcons.Debugger.Db_primitive;
type = null;
break;
default:
icon = AllIcons.Debugger.Value;
}
node.setPresentation(icon, type, v.myValue, false);
}
}));
node.addChildren(children, true);
}
else {
node.addChildren(XValueChildrenList.EMPTY, true);
}
}
private static class VariableItem {
enum VarType {PARAM, PRIMITIVE, OBJECT}
private final String myName;
private final String myType;
private final String myValue;
private final VarType myVarType;
public VariableItem(String name, String type, String value, VarType varType) {
myName = name;
myType = type;
myValue = value;
myVarType = varType;
}
}
}
@@ -97,7 +97,7 @@ public class StackCapturingLineBreakpoint extends WildcardMethodBreakpoint {
}
Value key = ContainerUtil.getOrElse(frameProxy.getArgumentValues(), myParamNo, null);
if (key instanceof ObjectReference) {
stacks.put((ObjectReference)key, StackFrameItem.createFrames(suspendContext.getThread()));
stacks.put((ObjectReference)key, StackFrameItem.createFrames(suspendContext.getThread(), process, true));
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -76,8 +76,9 @@ public class ClassRenderer extends NodeRendererImpl{
myProperties.setEnabled(true);
}
public final String renderTypeName(final String typeName) {
if (SHOW_FQ_TYPE_NAMES) {
@Nullable
public final String renderTypeName(@Nullable final String typeName) {
if (SHOW_FQ_TYPE_NAMES || typeName == null) {
return typeName;
}
String baseLambdaClassName = DebuggerUtilsEx.getLambdaBaseClassName(typeName);