diff --git a/java/debugger/debugger-agent-storage/debugger-agent-storage.iml b/java/debugger/debugger-agent-storage/debugger-agent-storage.iml
new file mode 100644
index 000000000000..c90834f2d607
--- /dev/null
+++ b/java/debugger/debugger-agent-storage/debugger-agent-storage.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java/debugger/debugger-agent-storage/src/com/intellij/rt/debugger/agent/CaptureStorage.java b/java/debugger/debugger-agent-storage/src/com/intellij/rt/debugger/agent/CaptureStorage.java
new file mode 100644
index 000000000000..ade32caec0a6
--- /dev/null
+++ b/java/debugger/debugger-agent-storage/src/com/intellij/rt/debugger/agent/CaptureStorage.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.rt.debugger.agent;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @author egor
+ */
+public class CaptureStorage {
+ private static final int MAX_STORED_STACKS = 1000;
+
+ public static final Map STORAGE = Collections.synchronizedMap(new LinkedHashMap() {
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return size() > MAX_STORED_STACKS;
+ }
+ });
+
+ // to be run from the debugger
+ @SuppressWarnings("unused")
+ public static Object[][] getRelatedStack(Object key) {
+ Exception exception = STORAGE.get(key);
+ StackTraceElement[] stackTrace = exception.getStackTrace();
+ Object[][] res = new Object[stackTrace.length][];
+ for (int i = 0; i < stackTrace.length; i++) {
+ StackTraceElement elem = stackTrace[i];
+ res[i] = new Object[]{elem.getClassName(), elem.getFileName(), elem.getMethodName(), String.valueOf(elem.getLineNumber())};
+ }
+ return res;
+ }
+}
diff --git a/java/debugger/debugger-agent/META-INF/MANIFEST.MF b/java/debugger/debugger-agent/META-INF/MANIFEST.MF
new file mode 100644
index 000000000000..3c53b952c270
--- /dev/null
+++ b/java/debugger/debugger-agent/META-INF/MANIFEST.MF
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Premain-Class: com.intellij.rt.debugger.agent.CaptureAgent
diff --git a/java/debugger/debugger-agent/debugger-agent.iml b/java/debugger/debugger-agent/debugger-agent.iml
new file mode 100644
index 000000000000..8b0d822d1f09
--- /dev/null
+++ b/java/debugger/debugger-agent/debugger-agent.iml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java/debugger/debugger-agent/src/com/intellij/rt/debugger/agent/CaptureAgent.java b/java/debugger/debugger-agent/src/com/intellij/rt/debugger/agent/CaptureAgent.java
new file mode 100644
index 000000000000..14267b3693b4
--- /dev/null
+++ b/java/debugger/debugger-agent/src/com/intellij/rt/debugger/agent/CaptureAgent.java
@@ -0,0 +1,161 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.rt.debugger.agent;
+
+import org.jetbrains.org.objectweb.asm.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.Instrumentation;
+import java.lang.instrument.UnmodifiableClassException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.security.ProtectionDomain;
+import java.util.*;
+import java.util.jar.JarFile;
+
+/**
+ * @author egor
+ */
+public class CaptureAgent {
+ private static Instrumentation OurInstrumentation;
+ private static volatile List myCapturePoints = Arrays.asList(new CapturePoint
+ ("javax/swing/SwingUtilities", "invokeLater", 0)
+ // ,
+ // new CapturePoint("Test", "foo")
+ );
+
+ public static void premain(String args, Instrumentation instrumentation) throws IOException {
+ OurInstrumentation = instrumentation;
+ instrumentation.appendToBootstrapClassLoaderSearch(createTempJar("debugger-agent-storage.jar"));
+ instrumentation.appendToSystemClassLoaderSearch(createTempJar("asm-all.jar"));
+ instrumentation.addTransformer(new CaptureTransformer());
+ System.out.println("Capture agent: ready");
+ }
+
+ private static class CaptureTransformer implements ClassFileTransformer {
+ @Override
+ public byte[] transform(ClassLoader loader,
+ String className,
+ Class> classBeingRedefined,
+ ProtectionDomain protectionDomain,
+ byte[] classfileBuffer) {
+ // TODO: speedup
+ for (CapturePoint capturePoint : myCapturePoints) {
+ if (capturePoint.myClassName.equals(className)) {
+ ClassReader reader = new ClassReader(classfileBuffer);
+ ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
+ CaptureInstrumentor visitor = new CaptureInstrumentor(Opcodes.ASM6, writer, capturePoint);
+ reader.accept(visitor, 0);
+ return writer.toByteArray();
+ }
+ }
+ return null;
+ }
+ }
+
+ private static class CaptureInstrumentor extends ClassVisitor {
+ private CapturePoint capturePoint;
+
+ public CaptureInstrumentor(int api, ClassVisitor cv, CapturePoint capturePoint) {
+ super(api, cv);
+ this.capturePoint = capturePoint;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
+ if (capturePoint.myMethodName.equals(name)) {
+ System.out.println("Capture agent: instrumented " + capturePoint.myClassName + "." + name);
+ return new MethodVisitor(api, mv) {
+ @Override
+ public void visitCode() {
+ visitFieldInsn(Opcodes.GETSTATIC, CaptureStorage.class.getName().replaceAll("\\.", "/"), "STORAGE", "Ljava/util/Map;");
+ visitVarInsn(Opcodes.ALOAD, capturePoint.myParamSlotId);
+ visitTypeInsn(Opcodes.NEW, "java/lang/Exception");
+ visitInsn(Opcodes.DUP);
+ visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Exception", "", "()V", false);
+ visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Map", "put",
+ "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);
+ visitInsn(Opcodes.POP);
+ super.visitCode();
+ }
+ };
+ }
+ return mv;
+ }
+ }
+
+ static class CapturePoint {
+ final String myClassName;
+ final String myMethodName;
+ final int myParamSlotId;
+
+ public CapturePoint(String myClassName, String myMethodName, int myParamSlotId) {
+ this.myClassName = myClassName;
+ this.myMethodName = myMethodName;
+ this.myParamSlotId = myParamSlotId;
+ }
+ }
+
+ // TODO: these files are not deleted even if deleteOnExit or anything else, we need to separate jars
+ private static JarFile createTempJar(String name) throws IOException {
+ File tempJar = File.createTempFile("Capture", ".jar");
+ Files.copy(CaptureAgent.class.getClassLoader().getResourceAsStream(name), tempJar.toPath(),
+ StandardCopyOption.REPLACE_EXISTING);
+ JarFile res = new JarFile(tempJar);
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ public void run() {
+ try {
+ res.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ tempJar.delete();
+ }
+ });
+ return res;
+ }
+
+ // to be run from the debugger
+ @SuppressWarnings("unused")
+ public static void setCapturePoints(Object[][] capturePoints) throws UnmodifiableClassException {
+ Set classNames = new HashSet<>();
+ for (CapturePoint point : myCapturePoints) {
+ classNames.add(point.myClassName);
+ }
+
+ List points = new ArrayList<>(capturePoints.length);
+ for (Object[] capturePoint : capturePoints) {
+ String className = (String)capturePoint[0];
+ classNames.add(className);
+ points.add(new CapturePoint(className, (String)capturePoint[1], (int)capturePoint[2]));
+ }
+ myCapturePoints = points;
+
+ List classes = new ArrayList<>(capturePoints.length);
+ for (String name : classNames) {
+ try {
+ classes.add(Class.forName(name));
+ }
+ catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+ OurInstrumentation.retransformClasses(classes.toArray(new Class[0]));
+ }
+}
diff --git a/java/debugger/impl/debugger-impl.iml b/java/debugger/impl/debugger-impl.iml
index a4a7834fe605..a11386710af3 100644
--- a/java/debugger/impl/debugger-impl.iml
+++ b/java/debugger/impl/debugger-impl.iml
@@ -22,6 +22,7 @@
+
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 61f2027fa1ae..f7021dec8862 100644
--- a/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java
+++ b/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java
@@ -208,7 +208,7 @@ public class JavaExecutionStack extends XExecutionStack {
// replace the rest with the related stack (if available)
if (Registry.is("debugger.capture.points") && frame instanceof JavaStackFrame) {
- List relatedStack = StackCapturingLineBreakpoint.getRelatedStack(frameProxy, suspendContext);
+ List relatedStack = StackCapturingLineBreakpoint.getRelatedStack(frameProxy, suspendContext, true);
if (!ContainerUtil.isEmpty(relatedStack)) {
int i = 0;
boolean separator = true;
diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerManagerImpl.java b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerManagerImpl.java
index 488e5c4e3174..8192b6d6b17c 100644
--- a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerManagerImpl.java
+++ b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerManagerImpl.java
@@ -49,11 +49,15 @@ import com.intellij.openapi.projectRoots.ex.JavaSdkUtil;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiClass;
+import com.intellij.rt.debugger.agent.CaptureStorage;
import com.intellij.util.EventDispatcher;
import com.intellij.util.Function;
+import com.intellij.util.PathUtil;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import org.jdom.Element;
@@ -466,6 +470,16 @@ public class DebuggerManagerImpl extends DebuggerManagerEx implements Persistent
ApplicationManager.getApplication().runReadAction(() -> {
JavaSdkUtil.addRtJar(parameters.getClassPath());
+ if (Registry.is("debugger.capture.points.agent")) {
+ String path = PathUtil.getJarPathForClass(CaptureStorage.class);
+ //TODO: for now works only in debug mode
+ String agent = "-javaagent:" +
+ FileUtil.toSystemDependentName(PathUtil.getParentPath(PathUtil.getParentPath(path))) +
+ "/artifacts/debugger_agent/debugger-agent.jar";
+ if (!parameters.getVMParametersList().hasParameter(agent)) {
+ parameters.getVMParametersList().add(agent);
+ }
+ }
final Sdk jdk = parameters.getJdk();
final boolean forceClassicVM = shouldForceClassicVM(jdk);
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java b/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java
index 7ab32c520236..d7ed5baae392 100644
--- a/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java
@@ -26,17 +26,20 @@ import com.intellij.debugger.settings.NodeRendererSettings;
import com.intellij.debugger.ui.breakpoints.StackCapturingLineBreakpoint;
import com.intellij.debugger.ui.tree.render.ClassRenderer;
import com.intellij.icons.AllIcons;
+import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.CommonClassNames;
+import com.intellij.psi.PsiClass;
import com.intellij.ui.ColoredTextContainer;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.util.PlatformIcons;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.EmptyIcon;
import com.intellij.util.ui.JBUI;
+import com.intellij.xdebugger.XDebuggerUtil;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.frame.*;
import com.intellij.xdebugger.frame.presentation.XStringValuePresentation;
@@ -82,6 +85,11 @@ public class StackFrameItem {
return myLocation.declaringType().name();
}
+ @NotNull
+ public String method() {
+ return myLocation.method().name();
+ }
+
public int line() {
return DebuggerUtilsEx.getLineNumber(myLocation, false);
}
@@ -152,7 +160,7 @@ public class StackFrameItem {
StackFrameItem frameItem = new StackFrameItem(location, vars);
res.add(frameItem);
- List relatedStack = StackCapturingLineBreakpoint.getRelatedStack(frame, suspendContext);
+ List relatedStack = StackCapturingLineBreakpoint.getRelatedStack(frame, suspendContext, false);
if (!ContainerUtil.isEmpty(relatedStack)) {
res.add(null); // separator
res.addAll(relatedStack);
@@ -239,14 +247,27 @@ public class StackFrameItem {
public CapturedStackFrame(DebugProcessImpl debugProcess, StackFrameItem item) {
DebuggerManagerThreadImpl.assertIsManagerThread();
- mySourcePosition = DebuggerUtilsEx.toXSourcePosition(debugProcess.getPositionManager().getSourcePosition(item.myLocation));
- myIsSynthetic = DebuggerUtils.isSynthetic(item.myLocation.method());
- myIsInLibraryContent =
- DebuggerUtilsEx.isInLibraryContent(mySourcePosition != null ? mySourcePosition.getFile() : null, debugProcess.getProject());
myPath = item.path();
- myMethodName = item.myLocation.method().name();
+ myMethodName = item.method();
myLineNumber = item.line();
myVariables = item.myVariables;
+
+ Location location = item.myLocation;
+ if (location != null) {
+ mySourcePosition = DebuggerUtilsEx.toXSourcePosition(debugProcess.getPositionManager().getSourcePosition(location));
+ }
+ else {
+ mySourcePosition = ReadAction.compute(() -> {
+ PsiClass aClass = PositionManagerImpl.findClass(debugProcess.getProject(), item.path(), debugProcess.getSearchScope());
+ if (aClass != null) {
+ return XDebuggerUtil.getInstance().createPosition(aClass.getContainingFile().getVirtualFile(), myLineNumber - 1);
+ }
+ return null;
+ });
+ }
+ myIsSynthetic = location != null && DebuggerUtils.isSynthetic(location.method());
+ myIsInLibraryContent =
+ DebuggerUtilsEx.isInLibraryContent(mySourcePosition != null ? mySourcePosition.getFile() : null, debugProcess.getProject());
}
@Nullable
diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/StackCapturingLineBreakpoint.java b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/StackCapturingLineBreakpoint.java
index c8be4a4369a4..64fc9b0e6ff7 100644
--- a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/StackCapturingLineBreakpoint.java
+++ b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/StackCapturingLineBreakpoint.java
@@ -39,6 +39,7 @@ import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
+import com.intellij.rt.debugger.agent.CaptureStorage;
import com.intellij.ui.SimpleColoredComponent;
import com.intellij.util.containers.ContainerUtil;
import com.sun.jdi.*;
@@ -48,10 +49,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.java.debugger.breakpoints.properties.JavaMethodBreakpointProperties;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -245,10 +243,12 @@ public class StackCapturingLineBreakpoint extends WildcardMethodBreakpoint {
}
@Nullable
- public static List getRelatedStack(@NotNull StackFrameProxyImpl frame, @NotNull SuspendContextImpl suspendContext) {
+ public static List getRelatedStack(@NotNull StackFrameProxyImpl frame,
+ @NotNull SuspendContextImpl suspendContext,
+ boolean checkInProcessData) {
DebugProcessImpl debugProcess = suspendContext.getDebugProcess();
Map> capturedStacks = debugProcess.getUserData(CAPTURED_STACKS);
- if (ContainerUtil.isEmpty(capturedStacks)) {
+ if (ContainerUtil.isEmpty(capturedStacks) && !Registry.is("debugger.capture.points.agent")) {
return null;
}
List captureBreakpoints = debugProcess.getUserData(CAPTURE_BREAKPOINTS);
@@ -265,10 +265,18 @@ public class StackCapturingLineBreakpoint extends WildcardMethodBreakpoint {
if ((StringUtil.isEmpty(insertClassName) || StringUtil.equals(insertClassName, className)) &&
StringUtil.equals(b.myCapturePoint.myInsertMethodName, methodName)) {
try {
- Value key = b.myInsertEvaluator.evaluate(new EvaluationContextImpl(suspendContext, frame));
+ EvaluationContextImpl evaluationContext = new EvaluationContextImpl(suspendContext, frame);
+ Value key = b.myInsertEvaluator.evaluate(evaluationContext);
+ List items = null;
if (key instanceof ObjectReference) {
- return capturedStacks.get(getKey((ObjectReference)key));
+ if (capturedStacks != null) {
+ items = capturedStacks.get(getKey((ObjectReference)key));
+ }
+ if (items == null && checkInProcessData) {
+ items = getProcessCapturedStack(key, evaluationContext);
+ }
}
+ return items;
}
catch (EvaluateException e) {
LOG.debug(e);
@@ -283,6 +291,63 @@ public class StackCapturingLineBreakpoint extends WildcardMethodBreakpoint {
return null;
}
+ private static List getProcessCapturedStack(Value key, EvaluationContextImpl evaluationContext)
+ throws EvaluateException {
+ if (Registry.is("debugger.capture.points.agent")) {
+ DebugProcessImpl process = evaluationContext.getDebugProcess();
+ // TODO: cache class & method
+ ClassType captureClass = (ClassType)process.findClass(evaluationContext, CaptureStorage.class.getName(), null);
+ Method getRelatedStackMethod = captureClass.methodsByName("getRelatedStack").get(0);
+ Value resArray = process.invokeMethod(evaluationContext, captureClass, getRelatedStackMethod, Collections.singletonList(key), true);
+ if (resArray instanceof ArrayReference) {
+ List values = ((ArrayReference)resArray).getValues();
+ List res = new ArrayList<>(values.size());
+ for (Value value : values) {
+ List values1 = ((ArrayReference)value).getValues();
+ res.add(new ProcessStackFrameItem(getStringRefValue((StringReference)values1.get(0)),
+ getStringRefValue((StringReference)values1.get(2)),
+ Integer.parseInt(((StringReference)values1.get(3)).value())));
+ }
+ return res;
+ }
+ }
+ return null;
+ }
+
+ private static String getStringRefValue(StringReference ref) {
+ return ref != null ? ref.value() : null;
+ }
+
+ private static class ProcessStackFrameItem extends StackFrameItem {
+ final String myClass;
+ final String myMethod;
+ final int myLine;
+
+ public ProcessStackFrameItem(String aClass, String method, int line) {
+ super(null, null);
+ myClass = aClass;
+ myMethod = method;
+ myLine = line;
+ }
+
+ @NotNull
+ @Override
+ public String path() {
+ return myClass;
+ }
+
+ @Override
+ public int line() {
+ return myLine;
+ }
+
+ @NotNull
+ @Override
+ public String method() {
+ return myMethod;
+ }
+ }
+
@Nullable
public static List getRelatedStack(@Nullable ObjectReference key, @Nullable DebugProcessImpl process) {
if (process != null && key != null) {
diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties
index 155f00bd77f3..808fb5431e17 100644
--- a/platform/util/resources/misc/registry.properties
+++ b/platform/util/resources/misc/registry.properties
@@ -297,6 +297,7 @@ debugger.emulate.method.breakpoints.description=Emulate method breakpoints with
debugger.intern.string.literals=false
debugger.intern.string.literals.description=Make string literal refer to the same instance of class String
debugger.capture.points=true
+debugger.capture.points.agent=false
debugger.capture.points.annotations=false
debugger.resume.yourkit.threads=false
debugger.keep.step.requests=false