Files
openide/java/debugger/impl/src/com/intellij/debugger/engine/JavaDebugProcess.java
Egor Ushakov 32d39ad93f debugger ui experiment - fixed other places using registry directly
GitOrigin-RevId: 491dd3952b2f9397a25d868d1678b07a8e980f23
2022-05-30 15:42:58 +00:00

557 lines
22 KiB
Java

// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.engine;
import com.intellij.debugger.JavaDebuggerBundle;
import com.intellij.debugger.actions.JvmDropFrameActionHandler;
import com.intellij.debugger.actions.JvmSmartStepIntoActionHandler;
import com.intellij.debugger.engine.dfaassist.DfaAssist;
import com.intellij.debugger.engine.evaluation.EvaluationContext;
import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
import com.intellij.debugger.impl.*;
import com.intellij.debugger.jdi.StackFrameProxyImpl;
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
import com.intellij.debugger.memory.component.MemoryViewDebugProcessData;
import com.intellij.debugger.memory.ui.ClassesFilteredView;
import com.intellij.debugger.settings.DebuggerSettings;
import com.intellij.debugger.ui.AlternativeSourceNotificationProvider;
import com.intellij.debugger.ui.DebuggerContentInfo;
import com.intellij.debugger.ui.breakpoints.Breakpoint;
import com.intellij.debugger.ui.impl.ThreadsPanel;
import com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeImpl;
import com.intellij.debugger.ui.impl.watch.MessageDescriptor;
import com.intellij.debugger.ui.impl.watch.NodeManagerImpl;
import com.intellij.debugger.ui.overhead.OverheadView;
import com.intellij.debugger.ui.tree.NodeDescriptor;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.ui.ExecutionConsole;
import com.intellij.execution.ui.ExecutionConsoleEx;
import com.intellij.execution.ui.RunnerLayoutUi;
import com.intellij.execution.ui.UIExperiment;
import com.intellij.execution.ui.layout.PlaceInGrid;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.extensions.ExtensionPointListener;
import com.intellij.openapi.extensions.PluginDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.NlsActions;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.EditorNotifications;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentManagerEvent;
import com.intellij.ui.content.ContentManagerListener;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.xdebugger.*;
import com.intellij.xdebugger.breakpoints.XBreakpoint;
import com.intellij.xdebugger.breakpoints.XBreakpointHandler;
import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider;
import com.intellij.xdebugger.frame.XDropFrameHandler;
import com.intellij.xdebugger.frame.XStackFrame;
import com.intellij.xdebugger.frame.XSuspendContext;
import com.intellij.xdebugger.frame.XValueMarkerProvider;
import com.intellij.xdebugger.impl.XDebugSessionImpl;
import com.intellij.xdebugger.impl.XDebuggerUtilImpl;
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil;
import com.intellij.xdebugger.memory.component.InstancesTracker;
import com.intellij.xdebugger.memory.component.MemoryViewManager;
import com.intellij.xdebugger.stepping.XSmartStepIntoHandler;
import com.intellij.xdebugger.ui.XDebugTabLayouter;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.LocatableEvent;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.java.debugger.JavaDebuggerEditorsProvider;
public class JavaDebugProcess extends XDebugProcess {
private final DebuggerSession myJavaSession;
private final JavaDebuggerEditorsProvider myEditorsProvider;
private volatile XBreakpointHandler<?>[] myBreakpointHandlers;
private final NodeManagerImpl myNodeManager;
private final JvmSmartStepIntoActionHandler mySmartStepIntoActionHandler;
private final JvmDropFrameActionHandler myDropFrameActionActionHandler;
private static final JavaBreakpointHandlerFactory[] ourDefaultBreakpointHandlerFactories = {
process -> new JavaBreakpointHandler.JavaLineBreakpointHandler(process),
process -> new JavaBreakpointHandler.JavaExceptionBreakpointHandler(process),
process -> new JavaBreakpointHandler.JavaFieldBreakpointHandler(process),
process -> new JavaBreakpointHandler.JavaMethodBreakpointHandler(process),
process -> new JavaBreakpointHandler.JavaWildcardBreakpointHandler(process),
process -> new JavaBreakpointHandler.JavaCollectionBreakpointHandler(process)
};
public static JavaDebugProcess create(@NotNull final XDebugSession session, @NotNull final DebuggerSession javaSession) {
JavaDebugProcess res = new JavaDebugProcess(session, javaSession);
javaSession.getProcess().setXDebugProcess(res);
return res;
}
protected JavaDebugProcess(@NotNull final XDebugSession session, @NotNull final DebuggerSession javaSession) {
super(session);
myJavaSession = javaSession;
myEditorsProvider = new JavaDebuggerEditorsProvider();
final DebugProcessImpl process = javaSession.getProcess();
myBreakpointHandlers = StreamEx.of(ourDefaultBreakpointHandlerFactories)
.append(JavaBreakpointHandlerFactory.EP_NAME.extensions())
.map(factory -> factory.createHandler(process))
.toArray(XBreakpointHandler[]::new);
JavaBreakpointHandlerFactory.EP_NAME.addExtensionPointListener(new ExtensionPointListener<>() {
@Override
public void extensionAdded(@NotNull JavaBreakpointHandlerFactory extension, @NotNull PluginDescriptor pluginDescriptor) {
//noinspection NonAtomicOperationOnVolatileField
myBreakpointHandlers = ArrayUtil.append(myBreakpointHandlers, extension.createHandler(myJavaSession.getProcess()));
}
}, process.myDisposable);
myJavaSession.getContextManager().addListener(new DebuggerContextListener() {
@Override
public void changeEvent(@NotNull final DebuggerContextImpl newContext, DebuggerSession.Event event) {
if (event == DebuggerSession.Event.PAUSE
|| event == DebuggerSession.Event.CONTEXT
|| event == DebuggerSession.Event.REFRESH
|| event == DebuggerSession.Event.REFRESH_WITH_STACK
&& myJavaSession.isPaused()) {
final SuspendContextImpl newSuspendContext = newContext.getSuspendContext();
if (newSuspendContext != null &&
(shouldApplyContext(newContext) || event == DebuggerSession.Event.REFRESH_WITH_STACK)) {
process.getManagerThread().schedule(new SuspendContextCommandImpl(newSuspendContext) {
@Override
public void contextAction(@NotNull SuspendContextImpl suspendContext) {
ThreadReferenceProxyImpl threadProxy = newContext.getThreadProxy();
newSuspendContext.initExecutionStacks(threadProxy);
Pair<Breakpoint, Event> item = ContainerUtil.getFirstItem(DebuggerUtilsEx.getEventDescriptors(newSuspendContext));
if (item != null) {
XBreakpoint xBreakpoint = item.getFirst().getXBreakpoint();
Event second = item.getSecond();
if (xBreakpoint != null && second instanceof LocatableEvent &&
threadProxy != null && ((LocatableEvent)second).thread() == threadProxy.getThreadReference()) {
((XDebugSessionImpl)getSession()).breakpointReachedNoProcessing(xBreakpoint, newSuspendContext);
unsetPausedIfNeeded(newContext);
SourceCodeChecker.checkSource(newContext);
return;
}
}
getSession().positionReached(newSuspendContext);
unsetPausedIfNeeded(newContext);
SourceCodeChecker.checkSource(newContext);
}
});
}
}
else if (event == DebuggerSession.Event.ATTACHED) {
getSession().rebuildViews(); // to refresh variables views message
}
}
});
myNodeManager = new NodeManagerImpl(session.getProject(), null) {
@NotNull
@Override
public DebuggerTreeNodeImpl createNode(final NodeDescriptor descriptor, EvaluationContext evaluationContext) {
return new DebuggerTreeNodeImpl(null, descriptor);
}
@Override
public DebuggerTreeNodeImpl createMessageNode(MessageDescriptor descriptor) {
return new DebuggerTreeNodeImpl(null, descriptor);
}
@NotNull
@Override
public DebuggerTreeNodeImpl createMessageNode(String message) {
return new DebuggerTreeNodeImpl(null, new MessageDescriptor(message));
}
};
session.addSessionListener(new XDebugSessionListener() {
@Override
public void sessionPaused() {
saveNodeHistory();
showAlternativeNotification(session.getCurrentStackFrame());
}
@Override
public void stackFrameChanged() {
XStackFrame frame = session.getCurrentStackFrame();
if (frame instanceof JavaStackFrame) {
showAlternativeNotification(frame);
StackFrameProxyImpl frameProxy = ((JavaStackFrame)frame).getStackFrameProxy();
DebuggerContextUtil.setStackFrame(javaSession.getContextManager(), frameProxy);
saveNodeHistory(frameProxy);
}
}
private void showAlternativeNotification(@Nullable XStackFrame frame) {
if (frame != null) {
XSourcePosition position = frame.getSourcePosition();
if (position != null) {
VirtualFile file = position.getFile();
if (!AlternativeSourceNotificationProvider.isFileProcessed(file)) {
EditorNotifications.getInstance(session.getProject()).updateNotifications(file);
}
}
}
}
});
if (!DebuggerUtilsImpl.isRemote(process)) {
DfaAssist.installDfaAssist(myJavaSession, session);
}
mySmartStepIntoActionHandler = new JvmSmartStepIntoActionHandler(javaSession);
myDropFrameActionActionHandler = new JvmDropFrameActionHandler(javaSession);
}
private void unsetPausedIfNeeded(DebuggerContextImpl context) {
SuspendContextImpl suspendContext = context.getSuspendContext();
if (suspendContext != null && !suspendContext.suspends(context.getThreadProxy())) {
((XDebugSessionImpl)getSession()).unsetPaused();
}
}
private boolean shouldApplyContext(DebuggerContextImpl context) {
SuspendContextImpl suspendContext = context.getSuspendContext();
SuspendContextImpl currentContext = (SuspendContextImpl)getSession().getSuspendContext();
if (suspendContext != null && !suspendContext.equals(currentContext)) return true;
JavaExecutionStack currentExecutionStack = currentContext != null ? currentContext.getActiveExecutionStack() : null;
return currentExecutionStack == null || !Comparing.equal(context.getThreadProxy(), currentExecutionStack.getThreadProxy());
}
public void saveNodeHistory() {
saveNodeHistory(getDebuggerStateManager().getContext().getFrameProxy());
}
private void saveNodeHistory(final StackFrameProxyImpl frameProxy) {
myJavaSession.getProcess().getManagerThread().invoke(PrioritizedTask.Priority.NORMAL,
() -> myNodeManager.setHistoryByContext(frameProxy));
}
private DebuggerStateManager getDebuggerStateManager() {
return myJavaSession.getContextManager();
}
public DebuggerSession getDebuggerSession() {
return myJavaSession;
}
@NotNull
@Override
public XDebuggerEditorsProvider getEditorsProvider() {
return myEditorsProvider;
}
@Override
public void startStepOver(@Nullable XSuspendContext context) {
myJavaSession.stepOver(false);
}
@Override
public void startStepInto(@Nullable XSuspendContext context) {
myJavaSession.stepInto(false, null);
}
@Override
public void startForceStepInto(@Nullable XSuspendContext context) {
myJavaSession.stepInto(true, null);
}
@Override
public void startStepOut(@Nullable XSuspendContext context) {
myJavaSession.stepOut();
}
@Override
public void stop() {
myJavaSession.dispose();
myNodeManager.dispose();
}
@Override
public void startPausing() {
myJavaSession.pause();
}
@Override
public void resume(@Nullable XSuspendContext context) {
myJavaSession.resume();
}
@Override
public void runToPosition(@NotNull XSourcePosition position, @Nullable XSuspendContext context) {
myJavaSession.runToCursor(position, false);
}
@Override
public XBreakpointHandler<?> @NotNull [] getBreakpointHandlers() {
return myBreakpointHandlers;
}
@Override
public boolean checkCanInitBreakpoints() {
return false;
}
@Nullable
@Override
protected ProcessHandler doGetProcessHandler() {
return myJavaSession.getProcess().getProcessHandler();
}
@NotNull
@Override
public ExecutionConsole createConsole() {
ExecutionConsole console = myJavaSession.getProcess().getExecutionResult().getExecutionConsole();
if (console != null) return console;
return super.createConsole();
}
@NotNull
@Override
public XDebugTabLayouter createTabLayouter() {
return new XDebugTabLayouter() {
@Override
public void registerAdditionalContent(@NotNull RunnerLayoutUi ui) {
registerThreadsPanel(ui);
registerMemoryViewPanel(ui);
registerOverheadMonitor(ui);
}
@NotNull
@Override
public Content registerConsoleContent(@NotNull RunnerLayoutUi ui, @NotNull ExecutionConsole console) {
Content content = null;
if (console instanceof ExecutionConsoleEx) {
((ExecutionConsoleEx)console).buildUi(ui);
content = ui.findContent(DebuggerContentInfo.CONSOLE_CONTENT);
}
if (content == null) {
content = super.registerConsoleContent(ui, console);
}
return content;
}
private void registerThreadsPanel(@NotNull RunnerLayoutUi ui) {
final ThreadsPanel panel = new ThreadsPanel(myJavaSession.getProject(), getDebuggerStateManager());
final Content threadsContent = ui.createContent(
DebuggerContentInfo.THREADS_CONTENT, panel, XDebuggerBundle.message("debugger.session.tab.threads.title"),
null, panel.getDefaultFocusedComponent());
threadsContent.setCloseable(false);
ui.addContent(threadsContent, 0, PlaceInGrid.left, true);
ui.addListener(new ContentManagerListener() {
@Override
public void selectionChanged(@NotNull ContentManagerEvent event) {
if (event.getContent() == threadsContent) {
if (threadsContent.isSelected()) {
panel.setUpdateEnabled(true);
if (panel.isRefreshNeeded()) {
panel.rebuildIfVisible(DebuggerSession.Event.CONTEXT);
}
}
else {
panel.setUpdateEnabled(false);
}
}
}
}, threadsContent);
}
private void registerMemoryViewPanel(@NotNull RunnerLayoutUi ui) {
if (!Registry.is("debugger.enable.memory.view")) return;
final XDebugSession session = getSession();
final DebugProcessImpl process = myJavaSession.getProcess();
final InstancesTracker tracker = InstancesTracker.getInstance(myJavaSession.getProject());
final ClassesFilteredView classesFilteredView = new ClassesFilteredView(session, process, tracker);
final Content memoryViewContent =
ui.createContent(MemoryViewManager.MEMORY_VIEW_CONTENT, classesFilteredView, JavaDebuggerBundle.message("memory.toolwindow.title"),
null, classesFilteredView.getDefaultFocusedComponent());
memoryViewContent.setCloseable(false);
memoryViewContent.setShouldDisposeContent(true);
final MemoryViewDebugProcessData data = new MemoryViewDebugProcessData();
process.putUserData(MemoryViewDebugProcessData.KEY, data);
session.addSessionListener(new XDebugSessionListener() {
@Override
public void sessionStopped() {
session.removeSessionListener(this);
data.getTrackedStacks().clear();
}
});
ui.addContent(memoryViewContent, 0, PlaceInGrid.right, true);
final DebuggerManagerThreadImpl managerThread = process.getManagerThread();
ui.addListener(new ContentManagerListener() {
@Override
public void selectionChanged(@NotNull ContentManagerEvent event) {
if (event.getContent() == memoryViewContent) {
classesFilteredView.setActive(memoryViewContent.isSelected(), managerThread);
}
}
}, memoryViewContent);
}
private void registerOverheadMonitor(@NotNull RunnerLayoutUi ui) {
if (!Registry.is("debugger.enable.overhead.monitor")) return;
DebugProcessImpl process = myJavaSession.getProcess();
OverheadView monitor = new OverheadView(process);
Content overheadContent = ui.createContent("OverheadMonitor", monitor, JavaDebuggerBundle.message("overhead.toolwindow.title"), null, monitor.getDefaultFocusedComponent());
monitor.setBouncer(() -> ui.setBouncing(overheadContent, true));
overheadContent.setCloseable(false);
overheadContent.setShouldDisposeContent(true);
ui.addContent(overheadContent, 0, PlaceInGrid.right, true);
}
};
}
@Override
public void registerAdditionalActions(@NotNull DefaultActionGroup leftToolbar,
@NotNull DefaultActionGroup topToolbar,
@NotNull DefaultActionGroup settings) {
if (!UIExperiment.isNewDebuggerUIEnabled()) {
Constraints beforeRunner = new Constraints(Anchor.BEFORE, "Runner.Layout");
leftToolbar.add(Separator.getInstance(), beforeRunner);
leftToolbar.add(ActionManager.getInstance().getAction("DumpThreads"), beforeRunner);
leftToolbar.add(Separator.getInstance(), beforeRunner);
}
Constraints beforeSort = new Constraints(Anchor.BEFORE, "XDebugger.ToggleSortValues");
settings.addAction(new WatchLastMethodReturnValueAction(), beforeSort);
settings.addAction(new AutoVarsSwitchAction(), beforeSort);
}
private static class AutoVarsSwitchAction extends ToggleAction {
private volatile boolean myAutoModeEnabled;
AutoVarsSwitchAction() {
super(JavaDebuggerBundle.message("action.auto.variables.mode"), JavaDebuggerBundle.message("action.auto.variables.mode.description"), null);
myAutoModeEnabled = DebuggerSettings.getInstance().AUTO_VARIABLES_MODE;
}
@Override
public boolean isSelected(@NotNull AnActionEvent e) {
return myAutoModeEnabled;
}
@Override
public void setSelected(@NotNull AnActionEvent e, boolean enabled) {
myAutoModeEnabled = enabled;
DebuggerSettings.getInstance().AUTO_VARIABLES_MODE = enabled;
XDebuggerUtilImpl.rebuildAllSessionsViews(e.getProject());
}
}
private static class WatchLastMethodReturnValueAction extends ToggleAction {
private final @NlsActions.ActionText String myText;
private final @NlsActions.ActionText String myTextUnavailable;
WatchLastMethodReturnValueAction() {
super("", JavaDebuggerBundle.message("action.watch.method.return.value.description"), null);
myText = JavaDebuggerBundle.message("action.watches.method.return.value.enable");
myTextUnavailable = JavaDebuggerBundle.message("action.watches.method.return.value.unavailable.reason");
}
@Override
public void update(@NotNull final AnActionEvent e) {
super.update(e);
final Presentation presentation = e.getPresentation();
DebugProcessImpl process = getCurrentDebugProcess(e);
if (process == null || process.canGetMethodReturnValue()) {
presentation.setEnabled(true);
presentation.setText(myText);
}
else {
presentation.setEnabled(false);
presentation.setText(myTextUnavailable);
}
}
@Override
public boolean isSelected(@NotNull AnActionEvent e) {
return DebuggerSettings.getInstance().WATCH_RETURN_VALUES;
}
@Override
public void setSelected(@NotNull AnActionEvent e, boolean watch) {
DebuggerSettings.getInstance().WATCH_RETURN_VALUES = watch;
DebugProcessImpl process = getCurrentDebugProcess(e);
if (process != null) {
process.setWatchMethodReturnValuesEnabled(watch);
}
}
}
@Nullable
public static DebugProcessImpl getCurrentDebugProcess(@NotNull AnActionEvent e) {
XDebugSession session = DebuggerUIUtil.getSession(e);
if (session != null) {
XDebugProcess process = session.getDebugProcess();
if (process instanceof JavaDebugProcess) {
return ((JavaDebugProcess)process).getDebuggerSession().getProcess();
}
}
return null;
}
/**
* @deprecated use {@link #getCurrentDebugProcess(AnActionEvent)}
*/
@Nullable
@Deprecated
public static DebugProcessImpl getCurrentDebugProcess(@Nullable Project project) {
if (project != null) {
XDebugSession session = XDebuggerManager.getInstance(project).getCurrentSession();
if (session != null) {
XDebugProcess process = session.getDebugProcess();
if (process instanceof JavaDebugProcess) {
return ((JavaDebugProcess)process).getDebuggerSession().getProcess();
}
}
}
return null;
}
public NodeManagerImpl getNodeManager() {
return myNodeManager;
}
@Override
public String getCurrentStateMessage() {
String description = myJavaSession.getStateDescription();
return description != null ? description : super.getCurrentStateMessage();
}
@Nullable
@Override
public XValueMarkerProvider<?, ?> createValueMarkerProvider() {
return new JavaValueMarker();
}
@Override
public boolean isLibraryFrameFilterSupported() {
return true;
}
@Nullable
@Override
public XSmartStepIntoHandler<?> getSmartStepIntoHandler() {
return mySmartStepIntoActionHandler;
}
@ApiStatus.Experimental
@Override
public @Nullable XDropFrameHandler getDropFrameHandler() {
return myDropFrameActionActionHandler;
}
}