track classes redefinition outside of the debugger for IDEA-156500 and IDEA-182956

This commit is contained in:
Egor Ushakov
2018-07-24 14:52:25 +03:00
parent 3ade296e04
commit a1453fc3b8
4 changed files with 163 additions and 8 deletions
@@ -6,6 +6,7 @@ import com.intellij.debugger.engine.events.DebuggerCommandImpl;
import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
import com.intellij.debugger.engine.requests.LocatableEventRequestor;
import com.intellij.debugger.engine.requests.MethodReturnValueWatcher;
import com.intellij.debugger.engine.requests.RequestManagerImpl;
import com.intellij.debugger.impl.DebuggerManagerImpl;
import com.intellij.debugger.impl.DebuggerSession;
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
@@ -13,6 +14,7 @@ import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
import com.intellij.debugger.requests.Requestor;
import com.intellij.debugger.settings.DebuggerSettings;
import com.intellij.debugger.ui.breakpoints.Breakpoint;
import com.intellij.debugger.ui.breakpoints.InstrumentationTracker;
import com.intellij.debugger.ui.breakpoints.RunToCursorBreakpoint;
import com.intellij.debugger.ui.breakpoints.StackCapturingLineBreakpoint;
import com.intellij.debugger.ui.overhead.OverheadProducer;
@@ -27,6 +29,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.Consumer;
import com.intellij.util.concurrency.AppExecutorUtil;
@@ -362,6 +365,10 @@ public class DebugProcessEvents extends DebugProcessImpl {
}
});
if (Registry.is("debugger.track.instrumentation", true)) {
trackClassRedefinitions();
}
final String addressDisplayName = DebuggerBundle.getAddressDisplayName(getConnection());
final String transportName = DebuggerBundle.getTransportName(getConnection());
showStatusText(DebuggerBundle.message("status.connected", addressDisplayName, transportName));
@@ -369,6 +376,19 @@ public class DebugProcessEvents extends DebugProcessImpl {
}
}
private void trackClassRedefinitions() {
getManagerThread().invoke(new DebuggerCommandImpl() {
@Override
public Priority getPriority() {
return Priority.HIGH;
}
@Override
protected void action() {
InstrumentationTracker.track(DebugProcessEvents.this);
}
});
}
private void createStackCapturingBreakpoints() {
getManagerThread().invoke(new DebuggerCommandImpl() {
@Override
@@ -484,15 +504,16 @@ public class DebugProcessEvents extends DebugProcessImpl {
final SuspendManager suspendManager = getSuspendManager();
SuspendContextImpl evaluatingContext = SuspendManagerUtil.getEvaluatingContext(suspendManager, suspendContext.getThread());
if (evaluatingContext != null && !DebuggerSession.enableBreakpointsDuringEvaluation()) {
final LocatableEventRequestor requestor = (LocatableEventRequestor)RequestManagerImpl.findRequestor(event.request());
if (evaluatingContext != null &&
!(requestor instanceof InstrumentationTracker.InstrumentationMethodBreakpoint) &&
!DebuggerSession.enableBreakpointsDuringEvaluation()) {
notifySkippedBreakpoints(event);
// is inside evaluation, so ignore any breakpoints
suspendManager.voteResume(suspendContext);
return;
}
final LocatableEventRequestor requestor = (LocatableEventRequestor) getRequestsManager().findRequestor(event.request());
boolean resumePreferred = requestor != null && DebuggerSettings.SUSPEND_NONE.equals(requestor.getSuspendPolicy());
boolean requestHit = false;
long start = requestor instanceof OverheadProducer && !(requestor instanceof RunToCursorBreakpoint) ? System.currentTimeMillis() : 0;
@@ -1,6 +1,4 @@
/*
* Copyright 2000-2017 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.
*/
// 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.engine.requests;
import com.intellij.debugger.DebuggerBundle;
@@ -21,7 +19,6 @@ import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiClass;
import com.intellij.ui.classFilter.ClassFilter;
import java.util.HashMap;
import com.sun.jdi.*;
import com.sun.jdi.event.ClassPrepareEvent;
import com.sun.jdi.request.*;
@@ -73,7 +70,7 @@ public class RequestManagerImpl extends DebugProcessAdapterImpl implements Reque
}
@Nullable
public Requestor findRequestor(EventRequest request) {
public static Requestor findRequestor(EventRequest request) {
DebuggerManagerThreadImpl.assertIsManagerThread();
return request != null? (Requestor)request.getProperty(REQUESTOR) : null;
}
@@ -0,0 +1,136 @@
// 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.ui.breakpoints;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
import com.intellij.debugger.engine.requests.RequestManagerImpl;
import com.intellij.debugger.requests.Requestor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.containers.ContainerUtil;
import com.sun.jdi.*;
import com.sun.jdi.event.LocatableEvent;
import com.sun.tools.jdi.ReferenceTypeImpl;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
/**
* @author egor
*/
public class InstrumentationTracker {
private static final Logger LOG = Logger.getInstance(InstrumentationTracker.class);
@SuppressWarnings("FieldCanBeLocal") private final InstrumentationMethodBreakpoint myRedefineBreakpoint;
@SuppressWarnings("FieldCanBeLocal") private final InstrumentationMethodBreakpoint myRetransformBreakpoint;
@NotNull private final DebugProcessImpl myDebugProcess;
private static final java.lang.reflect.Method ourNoticeRedefineClassMethod =
ReflectionUtil.getDeclaredMethod(ReferenceTypeImpl.class, "noticeRedefineClass");
public static void track(DebugProcessImpl debugProcess) {
if (ourNoticeRedefineClassMethod != null) {
new InstrumentationTracker(debugProcess);
}
}
private InstrumentationTracker(DebugProcessImpl debugProcess) {
myRedefineBreakpoint =
new InstrumentationMethodBreakpoint(debugProcess.getProject(), "sun.instrument.InstrumentationImpl", "redefineClasses") {
@Override
public boolean processLocatableEvent(SuspendContextCommandImpl action, LocatableEvent event) {
try {
Value value = event.thread().frame(0).getArgumentValues().get(0);
if (value instanceof ArrayReference) {
((ArrayReference)value).getValues().forEach(v -> {
Value aClass = ((ObjectReference)v).getValue(((ReferenceType)v.type()).fieldByName("mClass"));
noticeRedefineClass(((ClassObjectReference)aClass).reflectedType());
});
}
}
catch (IncompatibleThreadStateException e) {
LOG.warn(e);
}
return false;
}
};
myRetransformBreakpoint =
new InstrumentationMethodBreakpoint(debugProcess.getProject(), "sun.instrument.InstrumentationImpl", "retransformClasses") {
@Override
public boolean processLocatableEvent(SuspendContextCommandImpl action, LocatableEvent event) {
try {
Value value = event.thread().frame(0).getArgumentValues().get(0);
if (value instanceof ArrayReference) {
((ArrayReference)value).getValues().forEach(v -> noticeRedefineClass(((ClassObjectReference)v).reflectedType()));
}
}
catch (IncompatibleThreadStateException e) {
LOG.warn(e);
}
return false;
}
};
myDebugProcess = debugProcess;
myRedefineBreakpoint.createRequest(debugProcess);
myRetransformBreakpoint.createRequest(debugProcess);
}
private void noticeRedefineClass(ReferenceType type) {
List<Requestor> requestors = StreamEx.of(type.virtualMachine().eventRequestManager().breakpointRequests())
.filter(r -> type.equals(r.location().declaringType()))
.map(RequestManagerImpl::findRequestor)
.toList();
requestors.forEach(myDebugProcess.getRequestsManager()::deleteRequest);
try {
//noinspection ConstantConditions
ourNoticeRedefineClassMethod.invoke(type);
}
catch (IllegalAccessException | InvocationTargetException e) {
LOG.error(e);
}
StreamEx.of(requestors).select(Breakpoint.class).forEach(b -> b.createRequest(myDebugProcess));
}
public static class InstrumentationMethodBreakpoint extends SyntheticLineBreakpoint {
private final String myClassName;
private final String myMethodName;
public InstrumentationMethodBreakpoint(@NotNull Project project, String className, String methodName) {
super(project);
myClassName = className;
myMethodName = methodName;
}
@Override
public void createRequest(@NotNull DebugProcessImpl debugProcess) {
createOrWaitPrepare(debugProcess, myClassName);
}
@Override
protected void createRequestForPreparedClass(DebugProcessImpl debugProcess, ReferenceType classType) {
for (Method method : classType.methodsByName(myMethodName)) {
createRequestInMethod(debugProcess, method);
}
}
protected void createRequestInMethod(DebugProcessImpl debugProcess, Method method) {
try {
Location location = ContainerUtil.getLastItem(method.allLineLocations());
BreakpointWithHighlighter.createLocationBreakpointRequest(this, location, debugProcess);
}
catch (AbsentInformationException ignored) {
}
}
@Override
public String getDisplayName() {
return "Instrumentation tracker: " + myMethodName;
}
}
}
@@ -411,6 +411,7 @@ debugger.enable.overhead.monitor=true
debugger.tree.states.depth=100
debugger.new.threads.view=false
debugger.breakpoints.caller.filter=true
debugger.track.instrumentation=true
index.run.configuration.jre=false
index.run.configuration.jre.restartRequired=true