From c565ed3fac07a2fc71baaa7ea050861a85166f03 Mon Sep 17 00:00:00 2001 From: "Vitaliy.Bibaev" Date: Tue, 15 Jan 2019 10:46:51 +0300 Subject: [PATCH] Initial: memory agent --- .../impl/intellij.java.debugger.impl.iml | 2 +- .../actions/JavaReferringObjectsValue.java | 28 ++++- .../intellij/debugger/engine/JavaValue.java | 2 +- .../engine/ReferringObjectsProvider.java | 20 ++++ .../action/CalculateRetainedSizeAction.java | 29 +++++ .../memory/action/NativeAgentActionBase.java | 65 +++++++++++ .../ShowGarbageCollectorRootsAction.java | 47 ++++++++ .../debugger/memory/agent/AgentLoader.java | 102 +++++++++++++++++ .../debugger/memory/agent/MemoryAgent.java | 24 ++++ .../memory/agent/MemoryAgentImpl.java | 106 ++++++++++++++++++ .../MemoryAgentReferringObjectProvider.java | 42 +++++++ .../agent/parsers/GcRootsPathsParser.kt | 62 ++++++++++ .../memory/agent/parsers/LongValueParser.kt | 15 +++ .../memory/agent/parsers/ResultParser.java | 9 ++ .../intellij.java.debugger.memory.agent.iml | 5 +- resources/src/idea/JavaActions.xml | 14 +++ 16 files changed, 563 insertions(+), 9 deletions(-) create mode 100644 java/debugger/impl/src/com/intellij/debugger/engine/ReferringObjectsProvider.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/memory/action/CalculateRetainedSizeAction.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/memory/action/NativeAgentActionBase.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/memory/action/ShowGarbageCollectorRootsAction.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/memory/agent/AgentLoader.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgent.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentImpl.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentReferringObjectProvider.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/memory/agent/parsers/GcRootsPathsParser.kt create mode 100644 java/debugger/impl/src/com/intellij/debugger/memory/agent/parsers/LongValueParser.kt create mode 100644 java/debugger/impl/src/com/intellij/debugger/memory/agent/parsers/ResultParser.java diff --git a/java/debugger/impl/intellij.java.debugger.impl.iml b/java/debugger/impl/intellij.java.debugger.impl.iml index c9dfe2b5d2f7..06d9aad16b64 100644 --- a/java/debugger/impl/intellij.java.debugger.impl.iml +++ b/java/debugger/impl/intellij.java.debugger.impl.iml @@ -21,8 +21,8 @@ - + diff --git a/java/debugger/impl/src/com/intellij/debugger/actions/JavaReferringObjectsValue.java b/java/debugger/impl/src/com/intellij/debugger/actions/JavaReferringObjectsValue.java index d9999af7268f..d87ff9a82dd1 100644 --- a/java/debugger/impl/src/com/intellij/debugger/actions/JavaReferringObjectsValue.java +++ b/java/debugger/impl/src/com/intellij/debugger/actions/JavaReferringObjectsValue.java @@ -8,6 +8,7 @@ import com.intellij.debugger.engine.SuspendContextImpl; import com.intellij.debugger.engine.evaluation.EvaluateException; import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; import com.intellij.debugger.engine.events.SuspendContextCommandImpl; +import com.intellij.debugger.engine.ReferringObjectsProvider; import com.intellij.debugger.ui.impl.watch.FieldDescriptorImpl; import com.intellij.debugger.ui.impl.watch.NodeManagerImpl; import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl; @@ -27,22 +28,39 @@ import java.util.List; public class JavaReferringObjectsValue extends JavaValue { private static final long MAX_REFERRING = 100; + private final ReferringObjectsProvider myReferringObjectsProvider; private final boolean myIsField; private JavaReferringObjectsValue(@Nullable JavaValue parent, @NotNull ValueDescriptorImpl valueDescriptor, @NotNull EvaluationContextImpl evaluationContext, + @NotNull ReferringObjectsProvider referringObjectsProvider, NodeManagerImpl nodeManager, boolean isField) { super(parent, valueDescriptor, evaluationContext, nodeManager, false); + myReferringObjectsProvider = referringObjectsProvider; myIsField = isField; } - public JavaReferringObjectsValue(@NotNull JavaValue javaValue, boolean isField) { + public JavaReferringObjectsValue(@NotNull JavaValue javaValue, + @NotNull ReferringObjectsProvider referringObjectsProvider, + boolean isField) { super(null, javaValue.getName(), javaValue.getDescriptor(), javaValue.getEvaluationContext(), javaValue.getNodeManager(), false); + myReferringObjectsProvider = referringObjectsProvider; myIsField = isField; } + @Nullable + @Override + public XReferrersProvider getReferrersProvider() { + return new XReferrersProvider() { + @Override + public XValue getReferringObjectsValue() { + return new JavaReferringObjectsValue(JavaReferringObjectsValue.this, myReferringObjectsProvider, false); + } + }; + } + @Override public void computeChildren(@NotNull final XCompositeNode node) { scheduleCommand(getEvaluationContext(), node, new SuspendContextCommandImpl(getEvaluationContext().getSuspendContext()) { @@ -59,7 +77,7 @@ public class JavaReferringObjectsValue extends JavaValue { List references; try { - references = ((ObjectReference)value).referringObjects(MAX_REFERRING); + references = myReferringObjectsProvider.getReferringObjects((ObjectReference)value, MAX_REFERRING); } catch (ObjectCollectedException e) { node.setErrorMessage(DebuggerBundle.message("evaluation.error.object.collected")); return; @@ -76,7 +94,8 @@ public class JavaReferringObjectsValue extends JavaValue { return reference; } }; - children.add(new JavaReferringObjectsValue(null, descriptor, getEvaluationContext(), getNodeManager(), true)); + children.add(new JavaReferringObjectsValue(null, descriptor, getEvaluationContext(), + myReferringObjectsProvider, getNodeManager(), true)); i++; } else { @@ -96,7 +115,8 @@ public class JavaReferringObjectsValue extends JavaValue { return null; } }; - children.add("Referrer " + i++, new JavaReferringObjectsValue(null, descriptor, getEvaluationContext(), getNodeManager(), false)); + children.add("Referrer " + i++, new JavaReferringObjectsValue(null, descriptor, getEvaluationContext(), + myReferringObjectsProvider, getNodeManager(), false)); } } diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/JavaValue.java b/java/debugger/impl/src/com/intellij/debugger/engine/JavaValue.java index b035c6b3e908..a8cf4008fc67 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/JavaValue.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/JavaValue.java @@ -650,7 +650,7 @@ public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XV return new XReferrersProvider() { @Override public XValue getReferringObjectsValue() { - return new JavaReferringObjectsValue(JavaValue.this, false); + return new JavaReferringObjectsValue(JavaValue.this, ReferringObjectsProvider.BASIC_JDI, false); } }; } diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/ReferringObjectsProvider.java b/java/debugger/impl/src/com/intellij/debugger/engine/ReferringObjectsProvider.java new file mode 100644 index 000000000000..c4d4b5cefea4 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/engine/ReferringObjectsProvider.java @@ -0,0 +1,20 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.debugger.engine; + +import com.sun.jdi.ObjectReference; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public interface ReferringObjectsProvider { + @NotNull + List getReferringObjects(@NotNull ObjectReference value, long limit); + + ReferringObjectsProvider BASIC_JDI = new ReferringObjectsProvider() { + @NotNull + @Override + public List getReferringObjects(@NotNull ObjectReference value, long limit) { + return value.referringObjects(limit); + } + }; +} diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/action/CalculateRetainedSizeAction.java b/java/debugger/impl/src/com/intellij/debugger/memory/action/CalculateRetainedSizeAction.java new file mode 100644 index 000000000000..b08f8c8f5436 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/memory/action/CalculateRetainedSizeAction.java @@ -0,0 +1,29 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.debugger.memory.action; + +import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; +import com.intellij.debugger.memory.agent.AgentLoader; +import com.intellij.debugger.memory.agent.MemoryAgent; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.ui.messages.MessageDialog; +import com.intellij.util.ArrayUtil; +import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl; +import com.sun.jdi.ObjectReference; +import org.jetbrains.annotations.NotNull; + +/** + * @author Vitaliy.Bibaev + */ +public class CalculateRetainedSizeAction extends NativeAgentActionBase { + @Override + protected void perform(@NotNull EvaluationContextImpl evaluationContext, + @NotNull ObjectReference reference, + @NotNull XValueNodeImpl node) { + MemoryAgent memoryAgent = new AgentLoader().load(evaluationContext, evaluationContext.getDebugProcess().getVirtualMachineProxy()); + long size = memoryAgent.evaluateObjectSize(reference); + ApplicationManager.getApplication().invokeLater( + () -> new MessageDialog(node.getTree().getProject(), String.valueOf(size), "Size of the Object", + ArrayUtil.EMPTY_STRING_ARRAY, 0, null, false) + .show()); + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/action/NativeAgentActionBase.java b/java/debugger/impl/src/com/intellij/debugger/memory/action/NativeAgentActionBase.java new file mode 100644 index 000000000000..abcb0f1a63a1 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/memory/action/NativeAgentActionBase.java @@ -0,0 +1,65 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.debugger.memory.action; + +import com.intellij.debugger.engine.DebugProcessImpl; +import com.intellij.debugger.engine.JavaValue; +import com.intellij.debugger.engine.SuspendContextImpl; +import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; +import com.intellij.debugger.engine.managerThread.DebuggerCommand; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import com.intellij.xdebugger.XDebuggerManager; +import com.intellij.xdebugger.frame.XSuspendContext; +import com.intellij.xdebugger.frame.XValue; +import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.Value; +import org.jetbrains.annotations.NotNull; + +public abstract class NativeAgentActionBase extends DebuggerTreeAction { + protected final Logger LOG = Logger.getInstance(this.getClass()); + + @Override + protected void perform(XValueNodeImpl node, @NotNull String nodeName, AnActionEvent e) { + Project project = node.getTree().getProject(); + XValue container = node.getValueContainer(); + XDebugSession currentSession = XDebuggerManager.getInstance(project).getCurrentSession(); + XSuspendContext suspendContext = currentSession != null ? currentSession.getSuspendContext() : null; + DebugProcessImpl debugProcess = + suspendContext instanceof SuspendContextImpl ? ((SuspendContextImpl)suspendContext).getDebugProcess() : null; + if (debugProcess == null) return; + debugProcess.getManagerThread().invokeCommand(new DebuggerCommand() { + @Override + public void action() { + if (container instanceof JavaValue) { + EvaluationContextImpl evaluationContext = debugProcess.getDebuggerContext().createEvaluationContext(); + if (evaluationContext == null) return; + Value value = ((JavaValue)container).getDescriptor().getValue(); + perform(evaluationContext, (ObjectReference)value, node); + } + } + + @Override + public void commandCancelled() { + LOG.info("command cancelled"); + } + }); + } + + @Override + protected boolean isEnabled(@NotNull XValueNodeImpl node, @NotNull AnActionEvent e) { + if (!super.isEnabled(node, e)) return false; + XValue container = node.getValueContainer(); + if (container instanceof JavaValue) { + if (((JavaValue)container).getDescriptor().getValue() instanceof ObjectReference) return true; + } + + return false; + } + + protected abstract void perform(@NotNull EvaluationContextImpl evaluationContext, + @NotNull ObjectReference reference, + @NotNull XValueNodeImpl node); +} diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/action/ShowGarbageCollectorRootsAction.java b/java/debugger/impl/src/com/intellij/debugger/memory/action/ShowGarbageCollectorRootsAction.java new file mode 100644 index 000000000000..7319990dea29 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/memory/action/ShowGarbageCollectorRootsAction.java @@ -0,0 +1,47 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.debugger.memory.action; + +import com.intellij.debugger.actions.JavaReferringObjectsValue; +import com.intellij.debugger.engine.JavaValue; +import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; +import com.intellij.debugger.jdi.VirtualMachineProxyImpl; +import com.intellij.debugger.memory.agent.AgentLoader; +import com.intellij.debugger.memory.agent.MemoryAgent; +import com.intellij.debugger.engine.ReferringObjectsProvider; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.xdebugger.impl.XDebuggerManagerImpl; +import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree; +import com.intellij.xdebugger.impl.ui.tree.XInspectDialog; +import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl; +import com.sun.jdi.ObjectReference; +import org.jetbrains.annotations.NotNull; + +public class ShowGarbageCollectorRootsAction extends NativeAgentActionBase { + @Override + protected void perform(@NotNull EvaluationContextImpl evaluationContext, + @NotNull ObjectReference reference, + @NotNull XValueNodeImpl node) { + VirtualMachineProxyImpl virtualMachineProxy = evaluationContext.getDebugProcess().getVirtualMachineProxy(); + MemoryAgent memoryAgent = new AgentLoader().load(evaluationContext, virtualMachineProxy); + ReferringObjectsProvider roots = memoryAgent.canFindGcRoots() ? memoryAgent.findGcRoots(reference) : null; + if (roots == null) { + XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification("This feature is unavailable", NotificationType.INFORMATION); + return; + } + ApplicationManager.getApplication().invokeLater( + () -> { + XDebuggerTree tree = node.getTree(); + JavaValue javaValue = (JavaValue)node.getValueContainer(); + JavaReferringObjectsValue value = new JavaReferringObjectsValue(javaValue, roots, false); + XInspectDialog dialog = + new XInspectDialog(tree.getProject(), tree.getEditorsProvider(), tree.getSourcePosition(), StringUtil.notNullize(node.getName()), + value, tree.getValueMarkers(), + evaluationContext.getDebugProcess().getSession().getXDebugSession(), false); + dialog.setTitle("Paths to GC Roots"); + dialog.show(); + } + ); + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/agent/AgentLoader.java b/java/debugger/impl/src/com/intellij/debugger/memory/agent/AgentLoader.java new file mode 100644 index 000000000000..abe8a62041e2 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/memory/agent/AgentLoader.java @@ -0,0 +1,102 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.debugger.memory.agent; + +import com.intellij.debugger.engine.DebugProcessImpl; +import com.intellij.debugger.engine.DebuggerManagerThreadImpl; +import com.intellij.debugger.engine.ReferringObjectsProvider; +import com.intellij.debugger.engine.evaluation.EvaluateException; +import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; +import com.intellij.debugger.engine.jdi.VirtualMachineProxy; +import com.intellij.debugger.impl.ClassLoadingUtils; +import com.intellij.openapi.diagnostic.Logger; +import com.sun.jdi.ClassLoaderReference; +import com.sun.jdi.ClassType; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ReferenceType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * @author Vitaliy.Bibaev + */ +public class AgentLoader { + private static final Logger LOG = Logger.getInstance(AgentLoader.class); + private static final MemoryAgent DEFAULT_PROXY = new MyDisabledMemoryAgent(); + + @NotNull + public MemoryAgent load(@NotNull EvaluationContextImpl evaluationContext, @NotNull VirtualMachineProxy virtualMachine) { + DebuggerManagerThreadImpl.assertIsManagerThread(); + try { + ClassType classType = ensureClassLoaded(evaluationContext, virtualMachine); + return classType == null ? DEFAULT_PROXY : new MemoryAgentImpl(evaluationContext, classType); + } + catch (EvaluateException e) { + LOG.error("Could not load proxy class", e); + return DEFAULT_PROXY; + } + } + + @Nullable + private static ClassType ensureClassLoaded(@NotNull EvaluationContextImpl context, @NotNull VirtualMachineProxy vm) + throws EvaluateException { + List classes = vm.classesByName(MemoryAgentImpl.PROXY_CLASS_NAME); + if (classes.isEmpty()) { + ClassType classType = loadUtilityClass(context); + if (classType == null) { + LOG.error("Could not load proxy class"); + } + return classType; + } + + LOG.assertTrue(classes.size() == 1, "Too many utility classes loaded: " + classes.size()); + return (ClassType)classes.get(0); + } + + @Nullable + private static ClassType loadUtilityClass(@NotNull EvaluationContextImpl context) throws EvaluateException { + DebugProcessImpl debugProcess = context.getDebugProcess(); + byte[] bytes = readUtilityClass(); + ClassLoaderReference classLoader = ClassLoadingUtils.getClassLoader(context, debugProcess); + ClassLoadingUtils.defineClass(MemoryAgentImpl.PROXY_CLASS_NAME, bytes, context, debugProcess, classLoader); + return (ClassType)debugProcess.findClass(context, MemoryAgentImpl.PROXY_CLASS_NAME, classLoader); + } + + @NotNull + private static byte[] readUtilityClass() { + return new ProxyExtractor().extractProxy(); + } + + private static class MyDisabledMemoryAgent implements MemoryAgent { + @Override + public boolean canEvaluateObjectSize() { + return false; + } + + @Override + public long evaluateObjectSize(@NotNull ObjectReference reference) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean canEvaluateObjectsSizes() { + return false; + } + + @Override + public List evaluateObjectsSizes(@NotNull List references) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean canFindGcRoots() { + return false; + } + + @Override + public ReferringObjectsProvider findGcRoots(@NotNull ObjectReference reference) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgent.java b/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgent.java new file mode 100644 index 000000000000..a7efb76bf3e6 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgent.java @@ -0,0 +1,24 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.debugger.memory.agent; + +import com.intellij.debugger.engine.ReferringObjectsProvider; +import com.sun.jdi.ObjectReference; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public interface MemoryAgent { + boolean canEvaluateObjectSize(); + + long evaluateObjectSize(@NotNull ObjectReference reference); + + boolean canEvaluateObjectsSizes(); + + List evaluateObjectsSizes(@NotNull List references); + + boolean canFindGcRoots(); + + @Nullable + ReferringObjectsProvider findGcRoots(@NotNull ObjectReference reference); +} diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentImpl.java b/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentImpl.java new file mode 100644 index 000000000000..44c3c4c5331a --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentImpl.java @@ -0,0 +1,106 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.debugger.memory.agent; + +import com.intellij.debugger.engine.ReferringObjectsProvider; +import com.intellij.debugger.engine.evaluation.EvaluateException; +import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; +import com.intellij.debugger.memory.agent.parsers.GcRootsPathsParser; +import com.intellij.debugger.memory.agent.parsers.LongValueParser; +import com.intellij.openapi.diagnostic.Logger; +import com.sun.jdi.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; + +public class MemoryAgentImpl implements MemoryAgent { + public static final String PROXY_CLASS_NAME = "com.intellij.memory.agent.proxy.IdeaNativeAgentProxy"; + + private static final Logger LOG = Logger.getInstance(MemoryAgentImpl.class); + + private static final String SIZE_OF_SINGLE_OBJECT_METHOD_NAME = "size"; + private static final String SIZE_OF_OBJECTS_METHOD_NAME = "size"; + private static final String GARBAGE_COLLECTOR_ROOTS_METHOD_NAME = "gcRoots"; + + private final EvaluationContextImpl myEvaluationContext; + private final ClassType myProxyClassType; + + public MemoryAgentImpl(@NotNull EvaluationContextImpl context, @NotNull ClassType reference) { + myEvaluationContext = context; + myProxyClassType = reference; + } + + @Override + public boolean canEvaluateObjectSize() { + return !myProxyClassType.methodsByName(SIZE_OF_SINGLE_OBJECT_METHOD_NAME).isEmpty(); + } + + @Override + public long evaluateObjectSize(@NotNull ObjectReference reference) { + if (!canEvaluateObjectSize()) throw new UnsupportedOperationException(); + Value result = callMethod(SIZE_OF_SINGLE_OBJECT_METHOD_NAME, Collections.singletonList(reference)); + return result != null ? new LongValueParser().parse(result) : -1; + } + + @Override + public boolean canEvaluateObjectsSizes() { + return !myProxyClassType.methodsByName(SIZE_OF_OBJECTS_METHOD_NAME).isEmpty(); + } + + @Override + public List evaluateObjectsSizes(@NotNull List references) { + if (!canEvaluateObjectsSizes()) throw new UnsupportedOperationException(); + Value result = callMethod(SIZE_OF_OBJECTS_METHOD_NAME, references); + // TODO: Implement method and conversion + return Collections.emptyList(); + } + + @Override + public boolean canFindGcRoots() { + return !myProxyClassType.methodsByName(GARBAGE_COLLECTOR_ROOTS_METHOD_NAME).isEmpty(); + } + + @Nullable + @Override + public ReferringObjectsProvider findGcRoots(@NotNull ObjectReference reference) { + if (!canFindGcRoots()) throw new UnsupportedOperationException(); + Value value = callMethod(GARBAGE_COLLECTOR_ROOTS_METHOD_NAME, Collections.singletonList(reference)); + return value == null ? null : new GcRootsPathsParser().parse(value); + } + + + @Nullable + private Value callMethod(@NotNull String methodName, + @NotNull List args) { + List methods = myProxyClassType.methodsByName(methodName); + if (methods.isEmpty()) { + LOG.error("Method \"" + methodName + "\" not found"); + return null; + } + if (methods.size() > 1) { + LOG.warn("Too many methods \"" + methodName + "\" found. Count: " + methods.size()); + } + + Method method = methods.get(0); + if (!method.isStatic()) { + LOG.error("Utility method should be static"); + } + try { + return myEvaluationContext.getDebugProcess().invokeMethod(myEvaluationContext, myProxyClassType, method, args); + } + catch (EvaluateException e) { + LOG.error("Something went wrong", e); + } + + return null; + } + + private static long parseLong(@Nullable Value value) { + if (value instanceof PrimitiveValue) { + return ((PrimitiveValue)value).longValue(); + } + + throw new IllegalArgumentException("Unexpected argument. Primitive value is expected"); + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentReferringObjectProvider.java b/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentReferringObjectProvider.java new file mode 100644 index 000000000000..b355febda746 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentReferringObjectProvider.java @@ -0,0 +1,42 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.debugger.memory.agent; + +import com.intellij.debugger.engine.ReferringObjectsProvider; +import com.intellij.openapi.diagnostic.Logger; +import com.sun.jdi.ObjectReference; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class MemoryAgentReferringObjectProvider implements ReferringObjectsProvider { + private static final Logger LOG = Logger.getInstance(MemoryAgentReferringObjectProvider.class); + + private final Map myReversedMap = new HashMap<>(); + private final List myDirectMap; + private final List> myPaths; + + public MemoryAgentReferringObjectProvider(@NotNull List values, @NotNull List> paths) { + myDirectMap = values; + for (int i = 0; i < values.size(); i++) { + myReversedMap.put(values.get(i), i); + } + + myPaths = paths; + } + + @NotNull + @Override + public List getReferringObjects(@NotNull ObjectReference value, long limit) { + Integer index = myReversedMap.get(value); + if (index == null) { + LOG.error("Could not find referring object for reference " + value.toString()); + return Collections.emptyList(); + } + + return myPaths.get(index).stream().limit(limit).map(ix -> myDirectMap.get(ix)).collect(Collectors.toList()); + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/agent/parsers/GcRootsPathsParser.kt b/java/debugger/impl/src/com/intellij/debugger/memory/agent/parsers/GcRootsPathsParser.kt new file mode 100644 index 000000000000..dbad15f984c7 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/memory/agent/parsers/GcRootsPathsParser.kt @@ -0,0 +1,62 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.debugger.memory.agent.parsers + +import com.intellij.debugger.memory.agent.MemoryAgentReferringObjectProvider +import com.intellij.debugger.engine.ReferringObjectsProvider +import com.intellij.openapi.diagnostic.Logger +import com.sun.jdi.ArrayReference +import com.sun.jdi.IntegerValue +import com.sun.jdi.ObjectReference +import com.sun.jdi.Value +import java.util.ArrayList + +class GcRootsPathsParser : ResultParser { + private companion object { + val LOG = Logger.getInstance(GcRootsPathsParser::class.java) + } + + override fun parse(value: Value): ReferringObjectsProvider { + if (value is ArrayReference) { + LOG.assertTrue(value.length() == 2, "Array must represent 2 values: objects and backward references") + val values = parseValues(value.getValue(0)) + val backwardReferences = parseBackwardReferences(value.getValue(1)) + return MemoryAgentReferringObjectProvider(values, backwardReferences) + } + throw AssertionError("Incorrect result format: array of arrays is expected") + } + + private fun parseBackwardReferences(value: Value): List> { + if (value is ArrayReference) { + val result = ArrayList>() + for (item in value.values) { + if (item !is ArrayReference || "int" == item.type().name()) { + throw AssertionError("Incorrect result format: int array expected") + } + val ints = item.values.map { x -> (x as IntegerValue).value() } + result.add(ints) + } + + return result + } + + throw AssertionError("Incorrect result format: array with nested int arrays expected") + } + + private fun parseValues(value: Value): List { + if (value is ArrayReference) { + val result = ArrayList() + for (item in value.values) { + if (item !is ObjectReference) break + result.add(item) + } + + if (result.size != value.length()) { + throw AssertionError("All values should be object references but some of them are not") + } + + return result + } + + throw AssertionError("Incorrect result format: array with object references expected") + } +} \ No newline at end of file diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/agent/parsers/LongValueParser.kt b/java/debugger/impl/src/com/intellij/debugger/memory/agent/parsers/LongValueParser.kt new file mode 100644 index 000000000000..b6ec423d5baa --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/memory/agent/parsers/LongValueParser.kt @@ -0,0 +1,15 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.debugger.memory.agent.parsers + +import com.sun.jdi.PrimitiveValue +import com.sun.jdi.Value + +class LongValueParser : ResultParser { + override fun parse(value: Value): Long { + if (value is PrimitiveValue) { + return value.longValue() + } + + throw IllegalArgumentException("Unexpected argument. Primitive value is expected") + } +} \ No newline at end of file diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/agent/parsers/ResultParser.java b/java/debugger/impl/src/com/intellij/debugger/memory/agent/parsers/ResultParser.java new file mode 100644 index 000000000000..01c625e71c2b --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/memory/agent/parsers/ResultParser.java @@ -0,0 +1,9 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.debugger.memory.agent.parsers; + +import com.sun.jdi.Value; +import org.jetbrains.annotations.NotNull; + +public interface ResultParser { + T parse(@NotNull Value value); +} diff --git a/java/debugger/memory-agent/intellij.java.debugger.memory.agent.iml b/java/debugger/memory-agent/intellij.java.debugger.memory.agent.iml index 42e7db43f4f5..21056f695be7 100644 --- a/java/debugger/memory-agent/intellij.java.debugger.memory.agent.iml +++ b/java/debugger/memory-agent/intellij.java.debugger.memory.agent.iml @@ -4,13 +4,12 @@ - - + - + \ No newline at end of file diff --git a/resources/src/idea/JavaActions.xml b/resources/src/idea/JavaActions.xml index d965be3b5c5a..9651a83ea539 100644 --- a/resources/src/idea/JavaActions.xml +++ b/resources/src/idea/JavaActions.xml @@ -250,6 +250,20 @@ relative-to-action="Debugger.ShowReferring"/> + + + + + + + +