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 dc8c24ef9599..e92d6c0759ca 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessEvents.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessEvents.java @@ -299,12 +299,16 @@ public class DebugProcessEvents extends DebugProcessImpl { return null; } - private static void enableNonSuspendingRequest(EventRequest request, Consumer handler) { - request.setSuspendPolicy(EventRequest.SUSPEND_NONE); + public static void enableRequestWithHandler(EventRequest request, Consumer handler) { request.putProperty(REQUEST_HANDLER, handler); request.enable(); } + private static void enableNonSuspendingRequest(EventRequest request, Consumer handler) { + request.setSuspendPolicy(EventRequest.SUSPEND_NONE); + enableRequestWithHandler(request, handler); + } + private void processVMStartEvent(final SuspendContextImpl suspendContext, VMStartEvent event) { preprocessEvent(suspendContext, event.thread()); 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 89be8bb405ea..66477308681a 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 @@ -121,43 +121,46 @@ public class RequestManagerImpl extends DebugProcessAdapterImpl implements Reque } if (requestor.isClassFiltersEnabled() && !(request instanceof BreakpointRequest) /*no built-in class filters support for breakpoint requests*/ ) { - ClassFilter[] classFilters = requestor.getClassFilters(); - if (DebuggerUtilsEx.getEnabledNumber(classFilters) == 1) { - for (final ClassFilter filter : classFilters) { - if (!filter.isEnabled()) { - continue; - } - final JVMName jvmClassName = ReadAction.compute(() -> { - PsiClass psiClass = DebuggerUtils.findClass(filter.getPattern(), myDebugProcess.getProject(), myDebugProcess.getSearchScope()); - if (psiClass == null) { - return null; - } - return JVMNameUtil.getJVMQualifiedName(psiClass); - }); - String pattern = filter.getPattern(); - try { - if (jvmClassName != null) { - pattern = jvmClassName.getName(myDebugProcess); - } - } - catch (EvaluateException ignored) { - } - - addClassFilter(request, pattern); - break; // adding more than one inclusion filter does not work, only events that satisfy ALL filters are placed in the event queue. - } - } - - for (ClassFilter filter : requestor.getClassExclusionFilters()) { - if (filter.isEnabled()) { - addClassExclusionFilter(request, filter.getPattern()); - } - } + addClassFilters(request, requestor.getClassFilters(), requestor.getClassExclusionFilters()); } registerRequestInternal(requestor, request); } + public void addClassFilters(EventRequest request, ClassFilter[] classFilters, ClassFilter[] classExclusionFilters) { + if (DebuggerUtilsEx.getEnabledNumber(classFilters) == 1) { + for (final ClassFilter filter : classFilters) { + if (!filter.isEnabled()) { + continue; + } + final JVMName jvmClassName = ReadAction.compute(() -> { + PsiClass psiClass = DebuggerUtils.findClass(filter.getPattern(), myDebugProcess.getProject(), myDebugProcess.getSearchScope()); + if (psiClass == null) { + return null; + } + return JVMNameUtil.getJVMQualifiedName(psiClass); + }); + String pattern = filter.getPattern(); + try { + if (jvmClassName != null) { + pattern = jvmClassName.getName(myDebugProcess); + } + } + catch (EvaluateException ignored) { + } + + addClassFilter(request, pattern); + break; // adding more than one inclusion filter does not work, only events that satisfy ALL filters are placed in the event queue. + } + } + + for (ClassFilter filter : classExclusionFilters) { + if (filter.isEnabled()) { + addClassExclusionFilter(request, filter.getPattern()); + } + } + } + public void registerRequestInternal(final Requestor requestor, final EventRequest request) { registerRequest(requestor, request); request.putProperty(REQUESTOR, requestor); diff --git a/java/debugger/impl/src/com/intellij/debugger/settings/TraceSettings.java b/java/debugger/impl/src/com/intellij/debugger/settings/TraceSettings.java new file mode 100644 index 000000000000..a4ec786c01eb --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/settings/TraceSettings.java @@ -0,0 +1,61 @@ +// 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.settings; + +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.openapi.util.Comparing; +import com.intellij.ui.classFilter.ClassFilter; +import com.intellij.util.xmlb.annotations.XCollection; +import org.jetbrains.annotations.NotNull; + +@State(name = "TraceSettings", storages = @Storage("debugger.xml")) +public class TraceSettings implements PersistentStateComponent { + private ClassFilter[] myClassFilters; + private ClassFilter[] myClassExclusionFilters; + + @XCollection(propertyElementName = "class-filters") + public final ClassFilter[] getClassFilters() { + return myClassFilters != null ? myClassFilters : ClassFilter.EMPTY_ARRAY; + } + + public final boolean setClassFilters(ClassFilter[] classFilters) { + boolean changed = !filtersEqual(myClassFilters, classFilters); + myClassFilters = classFilters; + return changed; + } + + protected static boolean filtersEqual(Object[] a, Object[] b) { + if ((a == null || a.length == 0) && (b == null || b.length == 0)) { + return true; + } + return Comparing.equal(a, b); + } + + @XCollection(propertyElementName = "class-exclusion-filters") + public ClassFilter[] getClassExclusionFilters() { + return myClassExclusionFilters != null ? myClassExclusionFilters : ClassFilter.EMPTY_ARRAY; + } + + public boolean setClassExclusionFilters(ClassFilter[] classExclusionFilters) { + boolean changed = !filtersEqual(myClassExclusionFilters, classExclusionFilters); + myClassExclusionFilters = classExclusionFilters; + return changed; + } + + public static TraceSettings getInstance() { + return ServiceManager.getService(TraceSettings.class); + } + + @Override + public void loadState(@NotNull TraceSettings state) { + myClassFilters = state.getClassFilters(); + myClassExclusionFilters = state.getClassExclusionFilters(); + } + + @Override + public TraceSettings getState() { + return this; + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/Breakpoint.java b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/Breakpoint.java index aaf2140e02de..da987130a591 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/Breakpoint.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/Breakpoint.java @@ -30,6 +30,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.JDOMExternalizerUtil; import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.registry.Registry; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiCodeFragment; import com.intellij.psi.PsiElement; @@ -282,6 +283,12 @@ public abstract class Breakpoint

implements private void runAction(EvaluationContextImpl context, LocatableEvent event) { DebugProcessImpl debugProcess = context.getDebugProcess(); + if (getProperties().isTRACING_START() && Registry.is("debugger.call.tracing")) { + CallTracer.get(debugProcess).start(context); + } + if (getProperties().isTRACING_END() && Registry.is("debugger.call.tracing")) { + CallTracer.get(debugProcess).stop(); + } if (isLogEnabled() || isLogExpressionEnabled() || isLogStack()) { StringBuilder buf = new StringBuilder(); if (myXBreakpoint.isLogMessage()) { diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/CallTracer.java b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/CallTracer.java new file mode 100644 index 000000000000..1f676d2ede6a --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/CallTracer.java @@ -0,0 +1,114 @@ +// 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.DebugProcessEvents; +import com.intellij.debugger.engine.DebugProcessImpl; +import com.intellij.debugger.engine.evaluation.EvaluateException; +import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; +import com.intellij.debugger.impl.DebuggerUtilsEx; +import com.intellij.debugger.jdi.ThreadReferenceProxyImpl; +import com.intellij.debugger.settings.TraceSettings; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.classFilter.ClassFilter; +import com.sun.jdi.IncompatibleThreadStateException; +import com.sun.jdi.event.Event; +import com.sun.jdi.event.MethodEntryEvent; +import com.sun.jdi.request.EventRequest; +import com.sun.jdi.request.EventRequestManager; +import com.sun.jdi.request.MethodEntryRequest; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author egor + */ +public class CallTracer { + private static final Logger LOG = Logger.getInstance(CallTracer.class); + public static final Key CALL_TRACER_KEY = Key.create("CALL_TRACER"); + + private final EventRequestManager myRequestManager; + private final DebugProcessImpl myDebugProcess; + private final List myEntryRequests = new ArrayList<>(1); + private int myStartIndent = 0; + + public CallTracer(DebugProcessImpl debugProcess) { + myDebugProcess = debugProcess; + myRequestManager = debugProcess.getRequestsManager().getVMRequestManager(); + } + + public void start(EvaluationContextImpl context) { + if (myEntryRequests.isEmpty()) { + myStartIndent = 0; + try { + ThreadReferenceProxyImpl thread = context.getSuspendContext().getThread(); + if (thread != null) { + myStartIndent = thread.frameCount(); + } + } + catch (EvaluateException e) { + LOG.error(e); + } + + TraceSettings traceSettings = TraceSettings.getInstance(); + ClassFilter[] classFilters = traceSettings.getClassFilters(); + ClassFilter[] exclusionFilters = traceSettings.getClassExclusionFilters(); + if (DebuggerUtilsEx.getEnabledNumber(classFilters) == 0) { + addEntryRequest(null, exclusionFilters); + } + else { + for (ClassFilter filter : classFilters) { + if (filter.isEnabled()) { + addEntryRequest(filter, exclusionFilters); + } + } + } + } + } + + private void addEntryRequest(ClassFilter filter, ClassFilter[] exclusionFilters) { + MethodEntryRequest request = myRequestManager.createMethodEntryRequest(); + request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); // to be able to detect frameCount inside the event handler + myDebugProcess.getRequestsManager() + .addClassFilters(request, filter != null ? new ClassFilter[]{filter} : ClassFilter.EMPTY_ARRAY, exclusionFilters); + myEntryRequests.add(request); + DebugProcessEvents.enableRequestWithHandler(request, this::accept); + } + + public void stop() { + if (!myEntryRequests.isEmpty()) { + myEntryRequests.forEach(myRequestManager::deleteEventRequest); + myEntryRequests.clear(); + } + } + + private void accept(Event event) { + if (event instanceof MethodEntryEvent) { + MethodEntryEvent methodEntryEvent = (MethodEntryEvent)event; + try { + int indent = methodEntryEvent.thread().frameCount() - myStartIndent; + if (indent < 0) { + stop(); + return; + } + myDebugProcess.printToConsole("\n" + StringUtil.repeat(" ", indent) + methodEntryEvent.method()); + } + catch (IncompatibleThreadStateException e) { + LOG.error(e); + } + } + } + + @NotNull + public static CallTracer get(DebugProcessImpl debugProcess) { + CallTracer tracer = debugProcess.getUserData(CALL_TRACER_KEY); + if (tracer == null) { + tracer = new CallTracer(debugProcess); + debugProcess.putUserData(CALL_TRACER_KEY, tracer); + } + return tracer; + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/CallTracingPropertiesPanel.java b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/CallTracingPropertiesPanel.java new file mode 100644 index 000000000000..2726839baf17 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/CallTracingPropertiesPanel.java @@ -0,0 +1,95 @@ +// 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.settings.TraceSettings; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.ui.IdeBorderFactory; +import com.intellij.ui.components.JBCheckBox; +import com.intellij.xdebugger.breakpoints.XLineBreakpoint; +import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel; +import com.intellij.xdebugger.impl.breakpoints.XBreakpointBase; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class CallTracingPropertiesPanel extends XBreakpointCustomPropertiesPanel> { + private final Project myProject; + private JBCheckBox myStartTracing; + private JBCheckBox myEndTracing; + + public CallTracingPropertiesPanel(Project project) { + myProject = project; + } + + @NotNull + @Override + public JComponent getComponent() { + myStartTracing = new JBCheckBox("Start"); + myEndTracing = new JBCheckBox("Stop"); + JButton filters = new JButton("Filters..."); + //DialogUtil.registerMnemonic(myStartTracing); + //DialogUtil.registerMnemonic(myEndTracing); + + JPanel _panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0)); + _panel.add(myStartTracing); + _panel.add(myEndTracing); + _panel.add(filters); + _panel.setBorder(IdeBorderFactory.createTitledBorder("Tracing", true)); + + ActionListener listener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JCheckBox toUncheck = null; + if (myStartTracing.isSelected() && myEndTracing.isSelected()) { + Object source = e.getSource(); + if (myStartTracing.equals(source)) { + toUncheck = myEndTracing; + } + else if (myEndTracing.equals(source)) { + toUncheck = myStartTracing; + } + if (toUncheck != null) { + toUncheck.setSelected(false); + } + } + } + }; + myStartTracing.addActionListener(listener); + myEndTracing.addActionListener(listener); + + filters.addActionListener(e -> { + EditClassFiltersDialog dialog = new EditClassFiltersDialog(myProject); + TraceSettings traceSettings = TraceSettings.getInstance(); + dialog.setFilters(traceSettings.getClassFilters(), traceSettings.getClassExclusionFilters()); + dialog.setTitle("Tracing Class Filters"); + dialog.show(); + if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) { + traceSettings.setClassFilters(dialog.getFilters()); + traceSettings.setClassExclusionFilters(dialog.getExclusionFilters()); + } + }); + + return _panel; + } + + @Override + public void loadFrom(@NotNull XLineBreakpoint breakpoint) { + myStartTracing.setSelected(breakpoint.getProperties().isTRACING_START()); + myEndTracing.setSelected(breakpoint.getProperties().isTRACING_END()); + } + + @Override + public void saveTo(@NotNull XLineBreakpoint breakpoint) { + boolean changed = breakpoint.getProperties().setTRACING_START(myStartTracing.isSelected()); + changed = breakpoint.getProperties().setTRACING_END(myEndTracing.isSelected()) || changed; + if (changed) { + ((XBreakpointBase)breakpoint).fireBreakpointChanged(); + } + } +} \ No newline at end of file diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/JavaLineBreakpointType.java b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/JavaLineBreakpointType.java index 2c5d09c7d8c5..1a9ab7a9effb 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/JavaLineBreakpointType.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/JavaLineBreakpointType.java @@ -10,6 +10,7 @@ import com.intellij.icons.AllIcons; import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; @@ -18,6 +19,7 @@ import com.intellij.xdebugger.XDebuggerUtil; import com.intellij.xdebugger.XSourcePosition; import com.intellij.xdebugger.breakpoints.XBreakpoint; import com.intellij.xdebugger.breakpoints.XLineBreakpoint; +import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel; import com.intellij.xdebugger.breakpoints.ui.XBreakpointGroupingRule; import com.intellij.xdebugger.impl.XSourcePositionImpl; import com.intellij.xdebugger.impl.breakpoints.XLineBreakpointImpl; @@ -309,4 +311,13 @@ public class JavaLineBreakpointType extends JavaLineBreakpointTypeBase> createCustomPropertiesPanel(@NotNull Project project) { + if (Registry.is("debugger.call.tracing")) { + return new CallTracingPropertiesPanel(project); + } + return null; + } } diff --git a/java/debugger/impl/src/org/jetbrains/java/debugger/breakpoints/properties/JavaBreakpointProperties.java b/java/debugger/impl/src/org/jetbrains/java/debugger/breakpoints/properties/JavaBreakpointProperties.java index 608c6aed1554..c15e1f2687c6 100644 --- a/java/debugger/impl/src/org/jetbrains/java/debugger/breakpoints/properties/JavaBreakpointProperties.java +++ b/java/debugger/impl/src/org/jetbrains/java/debugger/breakpoints/properties/JavaBreakpointProperties.java @@ -29,6 +29,9 @@ public class JavaBreakpointProperties extend private ClassFilter[] myCallerFilters; private ClassFilter[] myCallerExclusionFilters; + private boolean TRACING_START = false; + private boolean TRACING_END = false; + @XCollection(propertyElementName = "instance-filters") public InstanceFilter[] getInstanceFilters() { return myInstanceFilters != null ? myInstanceFilters : InstanceFilter.EMPTY_ARRAY; @@ -100,6 +103,9 @@ public class JavaBreakpointProperties extend setCALLER_FILTERS_ENABLED(state.isCALLER_FILTERS_ENABLED()); myCallerFilters = state.getCallerFilters(); myCallerExclusionFilters = state.getCallerExclusionFilters(); + + setTRACING_START(state.isTRACING_START()); + setTRACING_END(state.isTRACING_END()); } @OptionTag("count-filter-enabled") @@ -178,4 +184,26 @@ public class JavaBreakpointProperties extend myCallerExclusionFilters = callerExclusionFilters; return changed; } + + @OptionTag("tracing-start") + public boolean isTRACING_START() { + return TRACING_START; + } + + public boolean setTRACING_START(boolean TRACING_START) { + boolean changed = this.TRACING_START != TRACING_START; + this.TRACING_START = TRACING_START; + return changed; + } + + @OptionTag("tracing-end") + public boolean isTRACING_END() { + return TRACING_END; + } + + public boolean setTRACING_END(boolean TRACING_END) { + boolean changed = this.TRACING_END != TRACING_END; + this.TRACING_END = TRACING_END; + return changed; + } } diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index bca7330f6faa..b410341a193f 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -430,6 +430,7 @@ debugger.attach.to.process.action=true debugger.sa.jdwp.debug=false debugger.evaluate.single.threaded.timeout=1000 debugger.evaluate.single.threaded.timeout.description=Number of milliseconds to evaluate resuming only the current thread, then resume all threads +debugger.call.tracing=false execution.java.always.debug=false execution.java.always.debug.description=Always run java processes with the debug agent diff --git a/resources/src/idea/RichPlatformPlugin.xml b/resources/src/idea/RichPlatformPlugin.xml index 671954eebfe0..55ef8f3fbd37 100644 --- a/resources/src/idea/RichPlatformPlugin.xml +++ b/resources/src/idea/RichPlatformPlugin.xml @@ -151,6 +151,7 @@ +