diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessEvents.java b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessEvents.java index 2438a9e717ad..826321538f47 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessEvents.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessEvents.java @@ -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; diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/requests/RequestManagerImpl.java b/java/debugger/impl/src/com/intellij/debugger/engine/requests/RequestManagerImpl.java index 82f3ec310132..7f5075ba863d 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/requests/RequestManagerImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/requests/RequestManagerImpl.java @@ -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; } diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/InstrumentationTracker.java b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/InstrumentationTracker.java new file mode 100644 index 000000000000..88569df7142f --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/InstrumentationTracker.java @@ -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 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; + } + } +} diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 1ff1fb60a410..7deece2233e5 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -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