mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-02-07 02:36:56 +07:00
Also make ResumeCommmand resume the given thread and not the current one. GitOrigin-RevId: 4197c705c10a09726b1ecf3f805bebba127f8a41
2470 lines
94 KiB
Java
2470 lines
94 KiB
Java
// Copyright 2000-2023 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.Patches;
|
|
import com.intellij.ProjectTopics;
|
|
import com.intellij.ReviseWhenPortedToJDK;
|
|
import com.intellij.debugger.*;
|
|
import com.intellij.debugger.actions.DebuggerAction;
|
|
import com.intellij.debugger.engine.evaluation.*;
|
|
import com.intellij.debugger.engine.events.DebuggerCommandImpl;
|
|
import com.intellij.debugger.engine.events.DebuggerContextCommandImpl;
|
|
import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
|
|
import com.intellij.debugger.engine.jdi.ThreadReferenceProxy;
|
|
import com.intellij.debugger.engine.requests.MethodReturnValueWatcher;
|
|
import com.intellij.debugger.engine.requests.RequestManagerImpl;
|
|
import com.intellij.debugger.impl.*;
|
|
import com.intellij.debugger.impl.attach.PidRemoteConnection;
|
|
import com.intellij.debugger.jdi.EmptyConnectorArgument;
|
|
import com.intellij.debugger.jdi.StackFrameProxyImpl;
|
|
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
|
|
import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
|
|
import com.intellij.debugger.settings.DebuggerSettings;
|
|
import com.intellij.debugger.settings.NodeRendererSettings;
|
|
import com.intellij.debugger.ui.breakpoints.*;
|
|
import com.intellij.debugger.ui.tree.render.*;
|
|
import com.intellij.execution.CantRunException;
|
|
import com.intellij.execution.ExecutionException;
|
|
import com.intellij.execution.ExecutionResult;
|
|
import com.intellij.execution.configurations.RemoteConnection;
|
|
import com.intellij.execution.process.*;
|
|
import com.intellij.execution.runners.ExecutionUtil;
|
|
import com.intellij.idea.ActionsBundle;
|
|
import com.intellij.openapi.Disposable;
|
|
import com.intellij.openapi.application.ApplicationManager;
|
|
import com.intellij.openapi.application.ApplicationNamesInfo;
|
|
import com.intellij.openapi.diagnostic.Logger;
|
|
import com.intellij.openapi.project.DumbService;
|
|
import com.intellij.openapi.project.Project;
|
|
import com.intellij.openapi.projectRoots.JavaSdk;
|
|
import com.intellij.openapi.projectRoots.JavaSdkType;
|
|
import com.intellij.openapi.projectRoots.ProjectJdkTable;
|
|
import com.intellij.openapi.projectRoots.Sdk;
|
|
import com.intellij.openapi.roots.ModuleRootEvent;
|
|
import com.intellij.openapi.roots.ModuleRootListener;
|
|
import com.intellij.openapi.ui.MessageType;
|
|
import com.intellij.openapi.ui.Messages;
|
|
import com.intellij.openapi.util.*;
|
|
import com.intellij.openapi.util.registry.Registry;
|
|
import com.intellij.openapi.util.text.StringUtil;
|
|
import com.intellij.openapi.wm.ToolWindowId;
|
|
import com.intellij.openapi.wm.impl.status.StatusBarUtil;
|
|
import com.intellij.psi.CommonClassNames;
|
|
import com.intellij.psi.PsiFile;
|
|
import com.intellij.psi.PsiManager;
|
|
import com.intellij.psi.search.GlobalSearchScope;
|
|
import com.intellij.ui.classFilter.ClassFilter;
|
|
import com.intellij.ui.classFilter.DebuggerClassFilterProvider;
|
|
import com.intellij.util.Alarm;
|
|
import com.intellij.util.EventDispatcher;
|
|
import com.intellij.util.ObjectUtils;
|
|
import com.intellij.util.concurrency.Semaphore;
|
|
import com.intellij.util.containers.ContainerUtil;
|
|
import com.intellij.util.lang.JavaVersion;
|
|
import com.intellij.util.ui.UIUtil;
|
|
import com.intellij.xdebugger.XDebugSession;
|
|
import com.intellij.xdebugger.XDebuggerBundle;
|
|
import com.intellij.xdebugger.XSourcePosition;
|
|
import com.intellij.xdebugger.impl.XDebugSessionImpl;
|
|
import com.intellij.xdebugger.impl.XDebuggerManagerImpl;
|
|
import com.intellij.xdebugger.impl.actions.XDebuggerActions;
|
|
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil;
|
|
import com.jetbrains.jdi.ClassLoaderReferenceImpl;
|
|
import com.jetbrains.jdi.MethodImpl;
|
|
import com.jetbrains.jdi.VirtualMachineManagerImpl;
|
|
import com.sun.jdi.*;
|
|
import com.sun.jdi.connect.*;
|
|
import com.sun.jdi.request.EventRequest;
|
|
import com.sun.jdi.request.EventRequestManager;
|
|
import com.sun.jdi.request.StepRequest;
|
|
import one.util.streamex.StreamEx;
|
|
import org.intellij.lang.annotations.MagicConstant;
|
|
import org.jetbrains.annotations.Contract;
|
|
import org.jetbrains.annotations.Nls;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import java.io.IOException;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.net.UnknownHostException;
|
|
import java.util.*;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
import java.util.function.Consumer;
|
|
|
|
public abstract class DebugProcessImpl extends UserDataHolderBase implements DebugProcess {
|
|
private static final Logger LOG = Logger.getInstance(DebugProcessImpl.class);
|
|
|
|
private final Project myProject;
|
|
private final RequestManagerImpl myRequestManager;
|
|
|
|
private final Deque<VirtualMachineData> myStashedVirtualMachines = new LinkedList<>();
|
|
|
|
private volatile VirtualMachineProxyImpl myVirtualMachineProxy = null;
|
|
protected final EventDispatcher<DebugProcessListener> myDebugProcessDispatcher = EventDispatcher.create(DebugProcessListener.class);
|
|
protected final EventDispatcher<EvaluationListener> myEvaluationDispatcher = EventDispatcher.create(EvaluationListener.class);
|
|
|
|
private final List<ProcessListener> myProcessListeners = ContainerUtil.createLockFreeCopyOnWriteList();
|
|
private final StringBuilder myTextBeforeStart = new StringBuilder();
|
|
|
|
protected enum State {INITIAL, ATTACHED, DETACHING, DETACHED}
|
|
|
|
protected final AtomicReference<State> myState = new AtomicReference<>(State.INITIAL);
|
|
|
|
private volatile ExecutionResult myExecutionResult;
|
|
private volatile RemoteConnection myConnection;
|
|
private JavaDebugProcess myXDebugProcess;
|
|
|
|
private volatile Map<String, Connector.Argument> myArguments;
|
|
|
|
private final List<NodeRenderer> myRenderers = new ArrayList<>();
|
|
|
|
// we use null key here
|
|
private final Map<Type, Object> myNodeRenderersMap = new HashMap<>();
|
|
|
|
private final SuspendManagerImpl mySuspendManager = new SuspendManagerImpl(this);
|
|
protected CompoundPositionManager myPositionManager = CompoundPositionManager.EMPTY;
|
|
private volatile @NotNull DebuggerManagerThreadImpl myDebuggerManagerThread;
|
|
|
|
private final Semaphore myWaitFor = new Semaphore();
|
|
private final AtomicBoolean myIsFailed = new AtomicBoolean(false);
|
|
private final AtomicBoolean myIsStopped = new AtomicBoolean(false);
|
|
protected DebuggerSession mySession;
|
|
@Nullable protected MethodReturnValueWatcher myReturnValueWatcher;
|
|
protected final Disposable myDisposable = Disposer.newDisposable();
|
|
private final Alarm myStatusUpdateAlarm = new Alarm();
|
|
|
|
private final ThreadBlockedMonitor myThreadBlockedMonitor = new ThreadBlockedMonitor(this, myDisposable);
|
|
|
|
protected DebugProcessImpl(Project project) {
|
|
myProject = project;
|
|
myDebuggerManagerThread = new DebuggerManagerThreadImpl(myDisposable, myProject);
|
|
myRequestManager = new RequestManagerImpl(this);
|
|
NodeRendererSettings.getInstance().addListener(this::reloadRenderers, myDisposable);
|
|
NodeRenderer.EP_NAME.addChangeListener(this::reloadRenderers, myDisposable);
|
|
CompoundRendererProvider.EP_NAME.addChangeListener(this::reloadRenderers, myDisposable);
|
|
addDebugProcessListener(new DebugProcessAdapterImpl() {
|
|
@Override
|
|
public void processAttached(DebugProcessImpl process) {
|
|
reloadRenderers();
|
|
}
|
|
}, myDisposable);
|
|
myDebugProcessDispatcher.addListener(new DebugProcessListener() {
|
|
@Override
|
|
public void paused(@NotNull SuspendContext suspendContext) {
|
|
myThreadBlockedMonitor.stopWatching(
|
|
suspendContext.getSuspendPolicy() != EventRequest.SUSPEND_ALL ? suspendContext.getThread() : null);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void reloadRenderers() {
|
|
getManagerThread().invoke(new DebuggerCommandImpl(PrioritizedTask.Priority.HIGH) {
|
|
@Override
|
|
protected void action() {
|
|
myNodeRenderersMap.clear();
|
|
myRenderers.clear();
|
|
try {
|
|
myRenderers.addAll(NodeRendererSettings.getInstance().getAllRenderers(myProject));
|
|
}
|
|
finally {
|
|
DebuggerInvocationUtil.swingInvokeLater(myProject, () -> {
|
|
final DebuggerSession session = mySession;
|
|
if (session != null && session.isAttached()) {
|
|
DebuggerAction.refreshViews(mySession.getXDebugSession());
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
@Nullable
|
|
public Pair<Method, Value> getLastExecutedMethod() {
|
|
final MethodReturnValueWatcher watcher = myReturnValueWatcher;
|
|
if (watcher == null) {
|
|
return null;
|
|
}
|
|
final Method method = watcher.getLastExecutedMethod();
|
|
if (method == null) {
|
|
return null;
|
|
}
|
|
return Pair.create(method, watcher.getLastMethodReturnValue());
|
|
}
|
|
|
|
public void setWatchMethodReturnValuesEnabled(boolean enabled) {
|
|
if (myReturnValueWatcher != null) {
|
|
myReturnValueWatcher.setEnabled(enabled);
|
|
}
|
|
}
|
|
|
|
public boolean canGetMethodReturnValue() {
|
|
return myReturnValueWatcher != null;
|
|
}
|
|
|
|
@NotNull
|
|
public CompletableFuture<List<NodeRenderer>> getApplicableRenderers(Type type) {
|
|
return DebuggerUtilsImpl.getApplicableRenderers(myRenderers, type);
|
|
}
|
|
|
|
@NotNull
|
|
public CompletableFuture<NodeRenderer> getAutoRendererAsync(@Nullable Type type) {
|
|
DebuggerManagerThreadImpl.assertIsManagerThread();
|
|
// in case evaluation is not possible, force default renderer
|
|
if (!isEvaluationPossible()) {
|
|
return CompletableFuture.completedFuture(getDefaultRenderer(type));
|
|
}
|
|
|
|
try {
|
|
Object renderer = myNodeRenderersMap.get(type);
|
|
if (renderer instanceof NodeRenderer) {
|
|
return CompletableFuture.completedFuture((NodeRenderer)renderer);
|
|
}
|
|
else if (renderer instanceof CompletableFuture) {
|
|
//noinspection unchecked
|
|
CompletableFuture<NodeRenderer> future = (CompletableFuture<NodeRenderer>)renderer;
|
|
LOG.assertTrue(!future.isDone(), "Completed future for " + type);
|
|
return future;
|
|
}
|
|
List<NodeRenderer> enabledRenderers = ContainerUtil.filter(myRenderers, NodeRenderer::isEnabled);
|
|
CompletableFuture<NodeRenderer> res = DebuggerUtilsImpl.getFirstApplicableRenderer(enabledRenderers, type)
|
|
.handle((r, throwable) -> {
|
|
DebuggerManagerThreadImpl.assertIsManagerThread();
|
|
if (r == null || throwable != null) {
|
|
r = getDefaultRenderer(type); // do not cache the fallback renderer
|
|
myNodeRenderersMap.remove(type);
|
|
}
|
|
else {
|
|
// TODO: may add a new (possibly incorrect) value after the cleanup in reloadRenderers
|
|
myNodeRenderersMap.put(type, r);
|
|
}
|
|
return r;
|
|
});
|
|
// check if future is already done
|
|
myNodeRenderersMap.putIfAbsent(type, res);
|
|
return res;
|
|
}
|
|
catch (ClassNotPreparedException e) {
|
|
LOG.info(e);
|
|
// use default, but do not cache
|
|
return CompletableFuture.completedFuture(getDefaultRenderer(type));
|
|
}
|
|
}
|
|
|
|
@NotNull
|
|
public static NodeRenderer getDefaultRenderer(Value value) {
|
|
return getDefaultRenderer(value != null ? value.type() : null);
|
|
}
|
|
|
|
@NotNull
|
|
public static NodeRenderer getDefaultRenderer(Type type) {
|
|
final NodeRendererSettings settings = NodeRendererSettings.getInstance();
|
|
|
|
final PrimitiveRenderer primitiveRenderer = settings.getPrimitiveRenderer();
|
|
if (primitiveRenderer.isApplicable(type)) {
|
|
return primitiveRenderer;
|
|
}
|
|
|
|
final ArrayRenderer arrayRenderer = settings.getArrayRenderer();
|
|
if (arrayRenderer.isApplicable(type)) {
|
|
return arrayRenderer;
|
|
}
|
|
|
|
final ClassRenderer classRenderer = settings.getClassRenderer();
|
|
LOG.assertTrue(classRenderer.isApplicable(type), type.name());
|
|
return classRenderer;
|
|
}
|
|
|
|
private static final int ourTraceMask;
|
|
|
|
static {
|
|
String traceStr = System.getProperty("idea.debugger.trace");
|
|
int mask = 0;
|
|
if (!StringUtil.isEmpty(traceStr)) {
|
|
StringTokenizer tokenizer = new StringTokenizer(traceStr);
|
|
while (tokenizer.hasMoreTokens()) {
|
|
String token = tokenizer.nextToken();
|
|
if ("SENDS".compareToIgnoreCase(token) == 0) {
|
|
mask |= VirtualMachine.TRACE_SENDS;
|
|
}
|
|
else if ("RAW_SENDS".compareToIgnoreCase(token) == 0) {
|
|
mask |= 0x01000000;
|
|
}
|
|
else if ("RECEIVES".compareToIgnoreCase(token) == 0) {
|
|
mask |= VirtualMachine.TRACE_RECEIVES;
|
|
}
|
|
else if ("RAW_RECEIVES".compareToIgnoreCase(token) == 0) {
|
|
mask |= 0x02000000;
|
|
}
|
|
else if ("EVENTS".compareToIgnoreCase(token) == 0) {
|
|
mask |= VirtualMachine.TRACE_EVENTS;
|
|
}
|
|
else if ("REFTYPES".compareToIgnoreCase(token) == 0) {
|
|
mask |= VirtualMachine.TRACE_REFTYPES;
|
|
}
|
|
else if ("OBJREFS".compareToIgnoreCase(token) == 0) {
|
|
mask |= VirtualMachine.TRACE_OBJREFS;
|
|
}
|
|
else if ("ALL".compareToIgnoreCase(token) == 0) {
|
|
mask |= VirtualMachine.TRACE_ALL;
|
|
}
|
|
}
|
|
}
|
|
ourTraceMask = mask;
|
|
}
|
|
|
|
protected int getTraceMask() {
|
|
int mask = ourTraceMask;
|
|
DebugEnvironment environment = mySession.getDebugEnvironment();
|
|
if (environment instanceof DefaultDebugEnvironment) {
|
|
mask |= ((DefaultDebugEnvironment)environment).getTraceMode();
|
|
}
|
|
return mask;
|
|
}
|
|
|
|
protected void commitVM(VirtualMachine vm) {
|
|
if (!isInInitialState()) {
|
|
LOG.error("State is invalid " + myState.get());
|
|
}
|
|
DebuggerManagerThreadImpl.assertIsManagerThread();
|
|
myPositionManager = new CompoundPositionManager(new PositionManagerImpl(this));
|
|
myProject.getMessageBus().connect(myDisposable).subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() {
|
|
@Override
|
|
public void rootsChanged(@NotNull final ModuleRootEvent event) {
|
|
DumbService.getInstance(myProject).runWhenSmart(
|
|
() -> getManagerThread().schedule(PrioritizedTask.Priority.HIGH, () -> {
|
|
myPositionManager.clearCache();
|
|
DebuggerUIUtil.invokeLater(() -> mySession.refresh(true));
|
|
}));
|
|
}
|
|
});
|
|
LOG.debug("*******************VM attached******************");
|
|
|
|
vm.setDebugTraceMode(getTraceMask());
|
|
|
|
checkVirtualMachineVersion(vm);
|
|
myVirtualMachineProxy = new VirtualMachineProxyImpl(this, vm);
|
|
}
|
|
|
|
private void stopConnecting() {
|
|
Map<String, Connector.Argument> arguments = myArguments;
|
|
try {
|
|
if (arguments == null) {
|
|
return;
|
|
}
|
|
if (myConnection.isServerMode()) {
|
|
Connector connector = getConnector();
|
|
if (connector instanceof ListeningConnector) {
|
|
((ListeningConnector)connector).stopListening(arguments);
|
|
}
|
|
}
|
|
}
|
|
catch (IOException | IllegalConnectorArgumentsException | IllegalArgumentException e) {
|
|
LOG.debug(e);
|
|
}
|
|
catch (ExecutionException e) {
|
|
LOG.error(e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void printToConsole(final String text) {
|
|
synchronized (myProcessListeners) {
|
|
if (myExecutionResult == null) {
|
|
myTextBeforeStart.append(text);
|
|
}
|
|
else {
|
|
printToConsoleImpl(text);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void printToConsoleImpl(String text) {
|
|
myExecutionResult.getProcessHandler().notifyTextAvailable(text, ProcessOutputTypes.SYSTEM);
|
|
}
|
|
|
|
@Override
|
|
public ProcessHandler getProcessHandler() {
|
|
return myExecutionResult != null ? myExecutionResult.getProcessHandler() : null;
|
|
}
|
|
|
|
/**
|
|
* @param size the step size. One of {@link StepRequest#STEP_LINE} or {@link StepRequest#STEP_MIN}
|
|
* @param hint may be null
|
|
*/
|
|
protected void doStep(final SuspendContextImpl suspendContext, final ThreadReferenceProxyImpl stepThread, int size, int depth,
|
|
RequestHint hint) {
|
|
if (stepThread == null) {
|
|
return;
|
|
}
|
|
try {
|
|
final ThreadReference stepThreadReference = stepThread.getThreadReference();
|
|
if (LOG.isDebugEnabled()) {
|
|
LOG.debug("DO_STEP: creating step request for " + stepThreadReference);
|
|
}
|
|
deleteStepRequests(stepThreadReference);
|
|
EventRequestManager requestManager = getVirtualMachineProxy().eventRequestManager();
|
|
StepRequest stepRequest = requestManager.createStepRequest(stepThreadReference, size, depth);
|
|
if (!(hint != null && hint.isIgnoreFilters()) && !isPositionFiltered(getLocation(stepThread, suspendContext))) {
|
|
getActiveFilters().forEach(f -> stepRequest.addClassExclusionFilter(f.getPattern()));
|
|
}
|
|
|
|
// suspend policy to match the suspend policy of the context:
|
|
// if all threads were suspended, then during stepping all the threads must be suspended
|
|
// if only event thread was suspended, then only this particular thread must be suspended during stepping
|
|
stepRequest.setSuspendPolicy(suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD
|
|
? EventRequest.SUSPEND_EVENT_THREAD
|
|
: EventRequest.SUSPEND_ALL);
|
|
|
|
stepRequest.addCountFilter(1);
|
|
|
|
if (hint != null) {
|
|
stepRequest.putProperty("hint", hint);
|
|
}
|
|
DebuggerUtilsAsync.setEnabled(stepRequest, true).whenComplete((__, e) -> {
|
|
if (DebuggerUtilsAsync.unwrap(e) instanceof IllegalThreadStateException) {
|
|
DebuggerUtilsAsync.deleteEventRequest(requestManager, stepRequest);
|
|
}
|
|
});
|
|
}
|
|
catch (ObjectCollectedException ignored) {
|
|
|
|
}
|
|
}
|
|
|
|
static boolean isPositionFiltered(Location location) {
|
|
List<ClassFilter> activeFilters = getActiveFilters();
|
|
if (!activeFilters.isEmpty()) {
|
|
ReferenceType referenceType = location != null ? location.declaringType() : null;
|
|
if (referenceType != null) {
|
|
String currentClassName = referenceType.name();
|
|
return currentClassName != null && DebuggerUtilsEx.isFiltered(currentClassName, activeFilters);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static boolean isClassFiltered(@Nullable String name) {
|
|
if (name == null) {
|
|
return false;
|
|
}
|
|
return DebuggerUtilsEx.isFiltered(name, getActiveFilters());
|
|
}
|
|
|
|
@NotNull
|
|
private static List<ClassFilter> getActiveFilters() {
|
|
DebuggerSettings settings = DebuggerSettings.getInstance();
|
|
StreamEx<ClassFilter> stream = StreamEx.of(DebuggerClassFilterProvider.EP_NAME.getExtensionList())
|
|
.flatCollection(DebuggerClassFilterProvider::getFilters);
|
|
if (settings.TRACING_FILTERS_ENABLED) {
|
|
stream = stream.prepend(settings.getSteppingFilters());
|
|
}
|
|
return stream.filter(ClassFilter::isEnabled).toList();
|
|
}
|
|
|
|
void deleteStepRequests(@Nullable final ThreadReference stepThread) {
|
|
EventRequestManager requestManager = getVirtualMachineProxy().eventRequestManager();
|
|
for (StepRequest request : new ArrayList<>(requestManager.stepRequests())) { // need a copy here to avoid CME
|
|
if (stepThread == null || stepThread.equals(request.thread())) {
|
|
try {
|
|
DebuggerUtilsAsync.deleteEventRequest(requestManager, request);
|
|
}
|
|
catch (ObjectCollectedException ignored) {
|
|
}
|
|
catch (VMDisconnectedException e) {
|
|
throw e;
|
|
}
|
|
catch (Exception e) {
|
|
LOG.error(e); // report all for now
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int getFrameCount(@Nullable ThreadReferenceProxyImpl thread, @NotNull SuspendContextImpl suspendContext) {
|
|
if (thread != null) {
|
|
if (thread.equals(suspendContext.getThread())) {
|
|
return suspendContext.frameCount();
|
|
}
|
|
else {
|
|
try {
|
|
return thread.frameCount();
|
|
}
|
|
catch (EvaluateException ignored) {
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
@Nullable
|
|
static Location getLocation(@Nullable ThreadReferenceProxyImpl thread, @NotNull SuspendContextImpl suspendContext) {
|
|
if (thread != null) {
|
|
if (thread.equals(suspendContext.getThread())) {
|
|
return suspendContext.getLocation();
|
|
}
|
|
else {
|
|
try {
|
|
if (thread.frameCount() > 0) {
|
|
StackFrameProxyImpl stackFrame = thread.frame(0);
|
|
if (stackFrame != null) {
|
|
return stackFrame.location();
|
|
}
|
|
}
|
|
}
|
|
catch (EvaluateException ignored) {
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private VirtualMachine createVirtualMachineInt() throws ExecutionException {
|
|
try {
|
|
if (myArguments != null) {
|
|
throw new IOException(JavaDebuggerBundle.message("error.debugger.already.listening"));
|
|
}
|
|
|
|
final String port = myConnection.getDebuggerAddress();
|
|
|
|
Connector connector = getConnector();
|
|
myArguments = connector.defaultArguments();
|
|
if (myConnection instanceof PidRemoteConnection && !((PidRemoteConnection)myConnection).isFixedAddress()) {
|
|
String pid = ((PidRemoteConnection)myConnection).getPid();
|
|
if (StringUtil.isEmpty(pid)) {
|
|
throw new CantRunException(JavaDebuggerBundle.message("error.no.pid"));
|
|
}
|
|
setConnectorArgument("pid", pid);
|
|
}
|
|
else if (myConnection.isServerMode()) {
|
|
if (myArguments == null) {
|
|
throw new CantRunException(JavaDebuggerBundle.message("error.no.debug.listen.port"));
|
|
}
|
|
}
|
|
else { // is client mode, should attach to already running process
|
|
if (myConnection.isUseSockets()) {
|
|
String debuggerHostName = myConnection.getDebuggerHostName();
|
|
if (debuggerHostName != null) {
|
|
setConnectorArgument("hostname", debuggerHostName);
|
|
}
|
|
if (port == null) {
|
|
throw new CantRunException(JavaDebuggerBundle.message("error.no.debug.attach.port"));
|
|
}
|
|
setConnectorArgument("port", port);
|
|
}
|
|
else {
|
|
if (port == null) {
|
|
throw new CantRunException(JavaDebuggerBundle.message("error.no.shmem.address"));
|
|
}
|
|
setConnectorArgument("name", port);
|
|
}
|
|
setConnectorArgument("timeout", "0"); // wait forever
|
|
}
|
|
return connector instanceof AttachingConnector
|
|
? attachConnector((AttachingConnector)connector)
|
|
: connectorListen(port, (ListeningConnector)connector);
|
|
}
|
|
catch (IOException e) {
|
|
throw new ExecutionException(processIOException(e, JavaDebuggerBundle.getAddressDisplayName(myConnection)), e);
|
|
}
|
|
catch (IllegalConnectorArgumentsException e) {
|
|
throw new ExecutionException(processError(e), e);
|
|
}
|
|
finally {
|
|
myArguments = null;
|
|
}
|
|
}
|
|
|
|
private void setConnectorArgument(String name, String value) {
|
|
Connector.Argument argument = myArguments.get(name);
|
|
if (argument != null) {
|
|
argument.setValue(value);
|
|
}
|
|
}
|
|
|
|
@ReviseWhenPortedToJDK("13")
|
|
private VirtualMachine connectorListen(String address, ListeningConnector connector)
|
|
throws CantRunException, IOException, IllegalConnectorArgumentsException {
|
|
if (address == null) {
|
|
throw new CantRunException(JavaDebuggerBundle.message("error.no.debug.listen.port"));
|
|
}
|
|
// zero port number means the caller leaves to debugger to decide at which port to listen
|
|
final Connector.Argument portArg = myConnection.isUseSockets() ? myArguments.get("port") : myArguments.get("name");
|
|
if (portArg != null) {
|
|
portArg.setValue(address);
|
|
|
|
// to allow connector to listen on several auto generated addresses
|
|
if (address.length() == 0 || address.equals("0")) {
|
|
EmptyConnectorArgument uniqueArg = new EmptyConnectorArgument("argForUniqueness");
|
|
myArguments.put(uniqueArg.name(), uniqueArg);
|
|
}
|
|
}
|
|
if (Registry.is("debugger.jb.jdi") || Runtime.version().feature() >= 13) {
|
|
setConnectorArgument("localAddress", "*");
|
|
}
|
|
setConnectorArgument("timeout", "0"); // wait forever
|
|
try {
|
|
String listeningAddress = connector.startListening(myArguments);
|
|
String port = StringUtil.substringAfterLast(listeningAddress, ":");
|
|
if (port != null) {
|
|
listeningAddress = port;
|
|
}
|
|
myConnection.setDebuggerAddress(listeningAddress);
|
|
myConnection.setApplicationAddress(listeningAddress);
|
|
|
|
myDebugProcessDispatcher.getMulticaster().connectorIsReady();
|
|
|
|
return connector.accept(myArguments);
|
|
}
|
|
catch (IllegalArgumentException e) {
|
|
throw new CantRunException(e.getLocalizedMessage());
|
|
}
|
|
finally {
|
|
if (myArguments != null) {
|
|
try {
|
|
connector.stopListening(myArguments);
|
|
}
|
|
catch (IllegalArgumentException | IllegalConnectorArgumentsException ignored) {
|
|
// ignored
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private VirtualMachine attachConnector(AttachingConnector connector)
|
|
throws IOException, IllegalConnectorArgumentsException, CantRunException {
|
|
myDebugProcessDispatcher.getMulticaster().connectorIsReady();
|
|
try {
|
|
return connector.attach(myArguments);
|
|
}
|
|
catch (IllegalArgumentException e) {
|
|
throw new CantRunException(e.getLocalizedMessage());
|
|
}
|
|
}
|
|
|
|
public void showStatusText(final @Nls String text) {
|
|
myStatusUpdateAlarm.cancelAllRequests();
|
|
myStatusUpdateAlarm.addRequest(() -> {
|
|
if (!myProject.isDisposed()) {
|
|
StatusBarUtil.setStatusBarInfo(myProject, text);
|
|
}
|
|
}, 50);
|
|
}
|
|
|
|
private Connector getConnector() throws ExecutionException {
|
|
if (myConnection instanceof PidRemoteConnection && !((PidRemoteConnection)myConnection).isFixedAddress()) {
|
|
return ((PidRemoteConnection)myConnection).getConnector(this);
|
|
}
|
|
return findConnector(myConnection.isUseSockets(), myConnection.isServerMode());
|
|
}
|
|
|
|
@NotNull
|
|
public static Connector findConnector(boolean useSockets, boolean listen) throws ExecutionException {
|
|
String connectorName = (Registry.is("debugger.jb.jdi") ? "com.jetbrains.jdi." : "com.sun.jdi.") +
|
|
(useSockets ? "Socket" : "SharedMemory") +
|
|
(listen ? "Listen" : "Attach");
|
|
return findConnector(connectorName);
|
|
}
|
|
|
|
@NotNull
|
|
public static Connector findConnector(String connectorName) throws ExecutionException {
|
|
VirtualMachineManager virtualMachineManager;
|
|
if (connectorName.startsWith("com.jetbrains")) {
|
|
virtualMachineManager = VirtualMachineManagerImpl.virtualMachineManager();
|
|
}
|
|
else {
|
|
try {
|
|
virtualMachineManager = Bootstrap.virtualMachineManager();
|
|
}
|
|
catch (Error e) {
|
|
throw new ExecutionException(JavaDebuggerBundle.message("debugger.jdi.bootstrap.error",
|
|
e.getClass().getName() + " : " + e.getLocalizedMessage()));
|
|
}
|
|
}
|
|
return StreamEx.of(virtualMachineManager.allConnectors())
|
|
.findFirst(c -> connectorName.equals(c.name()))
|
|
.orElseThrow(() -> new CantRunException(JavaDebuggerBundle.message("error.debug.connector.not.found", connectorName)));
|
|
}
|
|
|
|
protected void checkVirtualMachineVersion(VirtualMachine vm) {
|
|
final String versionString = vm.version();
|
|
if ("1.4.0".equals(versionString)) {
|
|
DebuggerInvocationUtil.swingInvokeLater(myProject, () -> Messages.showMessageDialog(
|
|
myProject,
|
|
JavaDebuggerBundle.message("warning.jdk140.unstable"), JavaDebuggerBundle.message("title.jdk140.unstable"), Messages.getWarningIcon()
|
|
));
|
|
}
|
|
if (getSession().getAlternativeJre() == null) {
|
|
Sdk runjre = getSession().getRunJre();
|
|
JavaVersion version = JavaVersion.tryParse(versionString);
|
|
if (version != null && (runjre == null || runjre.getSdkType() instanceof JavaSdkType) && !versionMatch(runjre, version)) {
|
|
Arrays.stream(ProjectJdkTable.getInstance().getAllJdks())
|
|
.sorted(Comparator.comparing(sdk -> !(sdk.getSdkType() instanceof JavaSdk))) // prefer regular jdks
|
|
.filter(sdk -> versionMatch(sdk, version))
|
|
.findFirst().ifPresent(sdk -> {
|
|
XDebuggerManagerImpl.getNotificationGroup().createNotification(
|
|
JavaDebuggerBundle.message("message.remote.jre.version.mismatch",
|
|
versionString,
|
|
runjre != null ? runjre.getVersionString() : "unknown",
|
|
sdk.getName())
|
|
, MessageType.INFO).notify(myProject);
|
|
getSession().setAlternativeJre(sdk);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
private static boolean versionMatch(@Nullable Sdk sdk, @NotNull JavaVersion version) {
|
|
if (sdk != null && sdk.getSdkType() instanceof JavaSdkType) {
|
|
JavaVersion v = JavaVersion.tryParse(sdk.getVersionString());
|
|
if (v != null) {
|
|
return version.feature == v.feature && version.minor == v.minor && version.update == v.update;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*Event dispatching*/
|
|
public void addEvaluationListener(EvaluationListener evaluationListener) {
|
|
myEvaluationDispatcher.addListener(evaluationListener);
|
|
}
|
|
|
|
public void removeEvaluationListener(EvaluationListener evaluationListener) {
|
|
myEvaluationDispatcher.removeListener(evaluationListener);
|
|
}
|
|
|
|
|
|
@Override
|
|
public void addDebugProcessListener(DebugProcessListener listener, Disposable parentDisposable) {
|
|
myDebugProcessDispatcher.addListener(listener, parentDisposable);
|
|
}
|
|
|
|
@Override
|
|
public void addDebugProcessListener(DebugProcessListener listener) {
|
|
myDebugProcessDispatcher.addListener(listener);
|
|
}
|
|
|
|
@Override
|
|
public void removeDebugProcessListener(DebugProcessListener listener) {
|
|
myDebugProcessDispatcher.removeListener(listener);
|
|
}
|
|
|
|
public void addProcessListener(ProcessListener processListener) {
|
|
synchronized (myProcessListeners) {
|
|
if (getProcessHandler() != null) {
|
|
getProcessHandler().addProcessListener(processListener);
|
|
}
|
|
else {
|
|
myProcessListeners.add(processListener);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void removeProcessListener(ProcessListener processListener) {
|
|
synchronized (myProcessListeners) {
|
|
if (getProcessHandler() != null) {
|
|
getProcessHandler().removeProcessListener(processListener);
|
|
}
|
|
else {
|
|
myProcessListeners.remove(processListener);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* getters */
|
|
public RemoteConnection getConnection() {
|
|
return myConnection;
|
|
}
|
|
|
|
@Override
|
|
public ExecutionResult getExecutionResult() {
|
|
return myExecutionResult;
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public Project getProject() {
|
|
return myProject;
|
|
}
|
|
|
|
public boolean canRedefineClasses() {
|
|
final VirtualMachineProxyImpl vm = myVirtualMachineProxy;
|
|
return vm != null && vm.canRedefineClasses();
|
|
}
|
|
|
|
public boolean isInInitialState() {
|
|
return myState.get() == State.INITIAL;
|
|
}
|
|
|
|
@Override
|
|
public boolean isAttached() {
|
|
return myState.get() == State.ATTACHED;
|
|
}
|
|
|
|
@Override
|
|
public boolean isDetached() {
|
|
return myState.get() == State.DETACHED;
|
|
}
|
|
|
|
@Override
|
|
public boolean isDetaching() {
|
|
return myState.get() == State.DETACHING;
|
|
}
|
|
|
|
@Override
|
|
public RequestManagerImpl getRequestsManager() {
|
|
return myRequestManager;
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public VirtualMachineProxyImpl getVirtualMachineProxy() {
|
|
DebuggerManagerThreadImpl.assertIsManagerThread();
|
|
final VirtualMachineProxyImpl vm = myVirtualMachineProxy;
|
|
if (vm == null) {
|
|
if (isInInitialState()) {
|
|
throw new IllegalStateException("Virtual machine is not initialized yet");
|
|
}
|
|
else {
|
|
throw new VMDisconnectedException();
|
|
}
|
|
}
|
|
return vm;
|
|
}
|
|
|
|
@Override
|
|
public void appendPositionManager(final PositionManager positionManager) {
|
|
DebuggerManagerThreadImpl.assertIsManagerThread();
|
|
myPositionManager.appendPositionManager(positionManager);
|
|
}
|
|
|
|
private volatile SteppingBreakpoint mySteppingBreakpoint;
|
|
|
|
public void setSteppingBreakpoint(@Nullable SteppingBreakpoint breakpoint) {
|
|
mySteppingBreakpoint = breakpoint;
|
|
}
|
|
|
|
public void cancelRunToCursorBreakpoint() {
|
|
DebuggerManagerThreadImpl.assertIsManagerThread();
|
|
final SteppingBreakpoint runToCursorBreakpoint = mySteppingBreakpoint;
|
|
if (runToCursorBreakpoint != null) {
|
|
setSteppingBreakpoint(null);
|
|
getRequestsManager().deleteRequest(runToCursorBreakpoint);
|
|
if (runToCursorBreakpoint.isRestoreBreakpoints()) {
|
|
DebuggerManagerEx.getInstanceEx(getProject()).getBreakpointManager().enableBreakpoints(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void prepareAndSetSteppingBreakpoint(SuspendContextImpl context,
|
|
@NotNull SteppingBreakpoint breakpoint,
|
|
RequestHint hint,
|
|
boolean resetThreadFilter) {
|
|
DebugProcessImpl debugProcess = context.getDebugProcess();
|
|
if (resetThreadFilter) {
|
|
BreakpointManager breakpointManager = DebuggerManagerEx.getInstanceEx(debugProcess.getProject()).getBreakpointManager();
|
|
breakpointManager.applyThreadFilter(debugProcess, null); // clear the filter on resume
|
|
}
|
|
breakpoint.setSuspendPolicy(
|
|
context.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD ? DebuggerSettings.SUSPEND_THREAD : DebuggerSettings.SUSPEND_ALL);
|
|
breakpoint.createRequest(debugProcess);
|
|
breakpoint.setRequestHint(hint);
|
|
debugProcess.setSteppingBreakpoint(breakpoint);
|
|
}
|
|
|
|
public void resetIgnoreSteppingFilters(@Nullable Location location, @Nullable RequestHint hint) {
|
|
if (hint != null && hint.isResetIgnoreFilters() && location != null && !isPositionFiltered(location)) {
|
|
getSession().resetIgnoreStepFiltersFlag();
|
|
}
|
|
}
|
|
|
|
private void detachProcess(boolean closedByUser, boolean keepManagerThread, Consumer<@Nullable VirtualMachineData> callback) {
|
|
DebuggerManagerThreadImpl.assertIsManagerThread();
|
|
|
|
if (myState.compareAndSet(State.INITIAL, State.DETACHING) || myState.compareAndSet(State.ATTACHED, State.DETACHING)) {
|
|
try {
|
|
if (!keepManagerThread) {
|
|
getManagerThread().close();
|
|
}
|
|
}
|
|
finally {
|
|
if (!(myConnection instanceof RemoteConnectionStub)) {
|
|
VirtualMachineData vmData = new VirtualMachineData(myVirtualMachineProxy, myConnection, myDebuggerManagerThread);
|
|
myVirtualMachineProxy = null;
|
|
myPositionManager = CompoundPositionManager.EMPTY;
|
|
myReturnValueWatcher = null;
|
|
myNodeRenderersMap.clear();
|
|
myRenderers.clear();
|
|
DebuggerUtils.cleanupAfterProcessFinish(this);
|
|
myState.compareAndSet(State.DETACHING, State.DETACHED);
|
|
try {
|
|
myDebugProcessDispatcher.getMulticaster().processDetached(this, closedByUser);
|
|
}
|
|
finally {
|
|
callback.accept(vmData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void closeProcess(boolean closedByUser) {
|
|
detachProcess(closedByUser, false, vmData -> {
|
|
//if (DebuggerSettings.getInstance().UNMUTE_ON_STOP) {
|
|
// XDebugSession session = mySession.getXDebugSession();
|
|
// if (session != null) {
|
|
// session.setBreakpointMuted(false);
|
|
// }
|
|
//}
|
|
if (vmData != null && vmData.vm != null) {
|
|
try {
|
|
vmData.vm.dispose(); // to be on the safe side ensure that VM mirror, if present, is disposed and invalidated
|
|
}
|
|
catch (Throwable ignored) {
|
|
}
|
|
}
|
|
myWaitFor.up();
|
|
|
|
unstashAndReattach();
|
|
});
|
|
}
|
|
|
|
@Contract(pure = true)
|
|
private static String formatMessage(String message) {
|
|
final int lineLength = 90;
|
|
StringBuilder buf = new StringBuilder(message.length());
|
|
int index = 0;
|
|
while (index < message.length()) {
|
|
buf.append(message, index, Math.min(index + lineLength, message.length())).append('\n');
|
|
index += lineLength;
|
|
}
|
|
return buf.toString();
|
|
}
|
|
|
|
public static @NlsContexts.DialogMessage String processError(Exception e) {
|
|
String message;
|
|
|
|
if (e instanceof VMStartException) {
|
|
VMStartException e1 = (VMStartException)e;
|
|
message = e1.getLocalizedMessage();
|
|
}
|
|
else if (e instanceof IllegalConnectorArgumentsException) {
|
|
IllegalConnectorArgumentsException e1 = (IllegalConnectorArgumentsException)e;
|
|
final List<String> invalidArgumentNames = e1.argumentNames();
|
|
message = formatMessage(
|
|
JavaDebuggerBundle.message("error.invalid.argument", invalidArgumentNames.size()) + ": " + e1.getLocalizedMessage()) + invalidArgumentNames;
|
|
LOG.debug(e1);
|
|
}
|
|
else if (e instanceof CantRunException) {
|
|
message = e.getLocalizedMessage();
|
|
}
|
|
else if (e instanceof VMDisconnectedException) {
|
|
message = JavaDebuggerBundle.message("error.vm.disconnected");
|
|
}
|
|
else if (e instanceof IOException) {
|
|
message = processIOException((IOException)e, null);
|
|
}
|
|
else if (e instanceof ExecutionException) {
|
|
message = e.getLocalizedMessage();
|
|
}
|
|
else {
|
|
message = JavaDebuggerBundle.message("error.exception.while.connecting", e.getClass().getName(), e.getLocalizedMessage());
|
|
LOG.debug(e);
|
|
}
|
|
return message;
|
|
}
|
|
|
|
@NotNull
|
|
public static @NlsContexts.DialogMessage String processIOException(@NotNull IOException e, @Nullable String address) {
|
|
if (e instanceof UnknownHostException) {
|
|
if (address != null) {
|
|
return JavaDebuggerBundle.message("error.unknown.host.with.address", address) + ":\n" + e.getLocalizedMessage();
|
|
}
|
|
return JavaDebuggerBundle.message("error.unknown.host") + ":\n" + e.getLocalizedMessage();
|
|
}
|
|
|
|
// Failed SA attach
|
|
Throwable cause = e.getCause();
|
|
if (cause instanceof InvocationTargetException) {
|
|
if (cause.getCause() != null) {
|
|
return cause.getCause().getLocalizedMessage();
|
|
}
|
|
}
|
|
|
|
@Nls StringBuilder buf = new StringBuilder();
|
|
if (address != null) {
|
|
buf.append(JavaDebuggerBundle.message("error.cannot.open.debugger.port"));
|
|
buf.append(" (").append(address).append("): ");
|
|
}
|
|
buf.append(e.getClass().getName()).append(" ");
|
|
if (!StringUtil.isEmpty(e.getLocalizedMessage())) {
|
|
buf.append('"').append(e.getLocalizedMessage()).append('"');
|
|
}
|
|
if (cause != null && !StringUtil.isEmpty(cause.getLocalizedMessage())) {
|
|
buf.append(" (").append(cause.getLocalizedMessage()).append(')');
|
|
}
|
|
LOG.debug(e);
|
|
return buf.toString();
|
|
}
|
|
|
|
public void dispose() {
|
|
Disposer.dispose(myDisposable);
|
|
myRequestManager.setFilterThread(null);
|
|
}
|
|
|
|
@Override
|
|
public @NotNull DebuggerManagerThreadImpl getManagerThread() {
|
|
return myDebuggerManagerThread;
|
|
}
|
|
|
|
public boolean isCurrentVirtualMachine(@NotNull VirtualMachineProxyImpl vmProxy) {
|
|
VirtualMachine vm = ObjectUtils.doIfNotNull(myVirtualMachineProxy, it -> it.getVirtualMachine());
|
|
return vmProxy.getVirtualMachine() == vm;
|
|
}
|
|
|
|
private static int getInvokePolicy(SuspendContext suspendContext) {
|
|
if (suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD ||
|
|
isResumeOnlyCurrentThread() ||
|
|
ThreadBlockedMonitor.getSingleThreadedEvaluationThreshold() > 0) {
|
|
return ObjectReference.INVOKE_SINGLE_THREADED;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
public void waitFor() {
|
|
LOG.assertTrue(!DebuggerManagerThreadImpl.isManagerThread());
|
|
myWaitFor.waitFor();
|
|
}
|
|
|
|
@Override
|
|
public void waitFor(long timeout) {
|
|
LOG.assertTrue(!DebuggerManagerThreadImpl.isManagerThread());
|
|
myWaitFor.waitFor(timeout);
|
|
}
|
|
|
|
private abstract class InvokeCommand<E extends Value> {
|
|
private final Method myMethod;
|
|
private final List<Value> myArgs;
|
|
|
|
protected InvokeCommand(@NotNull Method method, @NotNull List<? extends Value> args) {
|
|
myMethod = method;
|
|
myArgs = new ArrayList<>(args);
|
|
}
|
|
|
|
public String toString() {
|
|
return "INVOKE: " + super.toString();
|
|
}
|
|
|
|
protected abstract E invokeMethod(int invokePolicy, Method method, List<? extends Value> args) throws InvocationException,
|
|
ClassNotLoadedException,
|
|
IncompatibleThreadStateException,
|
|
InvalidTypeException;
|
|
|
|
|
|
E start(EvaluationContextImpl evaluationContext, boolean internalEvaluate) throws EvaluateException {
|
|
while (true) {
|
|
try {
|
|
return startInternal(evaluationContext, internalEvaluate);
|
|
}
|
|
catch (ClassNotLoadedException e) {
|
|
ReferenceType loadedClass = null;
|
|
try {
|
|
if (evaluationContext.isAutoLoadClasses()) {
|
|
loadedClass = loadClass(evaluationContext, e.className(), evaluationContext.getClassLoader());
|
|
}
|
|
}
|
|
catch (Exception ignored) {
|
|
loadedClass = null;
|
|
}
|
|
if (loadedClass == null) {
|
|
throw EvaluateExceptionUtil.createEvaluateException(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
E startInternal(EvaluationContextImpl evaluationContext, boolean internalEvaluate)
|
|
throws EvaluateException, ClassNotLoadedException {
|
|
DebuggerManagerThreadImpl.assertIsManagerThread();
|
|
SuspendContextImpl suspendContext = evaluationContext.getSuspendContext();
|
|
SuspendManagerUtil.assertSuspendContext(suspendContext);
|
|
|
|
ThreadReferenceProxyImpl invokeThread = suspendContext.getThread();
|
|
|
|
if (SuspendManagerUtil.isEvaluating(getSuspendManager(), invokeThread)) {
|
|
throw EvaluateExceptionUtil.NESTED_EVALUATION_ERROR;
|
|
}
|
|
|
|
if (!suspendContext.suspends(invokeThread)) {
|
|
throw EvaluateExceptionUtil.THREAD_WAS_RESUMED;
|
|
}
|
|
|
|
Set<SuspendContextImpl> suspendingContexts = SuspendManagerUtil.getSuspendingContexts(getSuspendManager(), invokeThread);
|
|
final ThreadReference invokeThreadRef = invokeThread.getThreadReference();
|
|
|
|
myEvaluationDispatcher.getMulticaster().evaluationStarted(suspendContext);
|
|
beforeMethodInvocation(suspendContext, myMethod, internalEvaluate);
|
|
|
|
Object resumeData = null;
|
|
try {
|
|
for (SuspendContextImpl suspendingContext : suspendingContexts) {
|
|
final ThreadReferenceProxyImpl suspendContextThread = suspendingContext.getThread();
|
|
if (suspendContextThread != invokeThread) {
|
|
if (LOG.isDebugEnabled()) {
|
|
LOG.debug("Resuming " + invokeThread + " that is paused by " + suspendContextThread);
|
|
}
|
|
LOG.assertTrue(suspendContextThread == null || !invokeThreadRef.equals(suspendContextThread.getThreadReference()));
|
|
getSuspendManager().resumeThread(suspendingContext, invokeThread);
|
|
}
|
|
}
|
|
|
|
resumeData = SuspendManagerUtil.prepareForResume(suspendContext);
|
|
suspendContext.setIsEvaluating(evaluationContext);
|
|
|
|
getVirtualMachineProxy().clearCaches();
|
|
|
|
return invokeMethodAndFork(suspendContext);
|
|
}
|
|
catch (InvocationException | InternalException | UnsupportedOperationException | ObjectCollectedException |
|
|
InvalidTypeException | IncompatibleThreadStateException e) {
|
|
throw EvaluateExceptionUtil.createEvaluateException(e);
|
|
}
|
|
finally {
|
|
suspendContext.setIsEvaluating(null);
|
|
if (resumeData != null) {
|
|
SuspendManagerUtil.restoreAfterResume(suspendContext, resumeData);
|
|
}
|
|
for (SuspendContextImpl suspendingContext : mySuspendManager.getEventContexts()) {
|
|
if (suspendingContexts.contains(suspendingContext) &&
|
|
!suspendingContext.isEvaluating() &&
|
|
!suspendingContext.suspends(invokeThread)) {
|
|
mySuspendManager.suspendThread(suspendingContext, invokeThread);
|
|
}
|
|
}
|
|
|
|
LOG.debug("getVirtualMachine().clearCaches()");
|
|
getVirtualMachineProxy().clearCaches();
|
|
afterMethodInvocation(suspendContext, internalEvaluate);
|
|
|
|
myEvaluationDispatcher.getMulticaster().evaluationFinished(suspendContext);
|
|
}
|
|
}
|
|
|
|
private E invokeMethodAndFork(final SuspendContextImpl context) throws InvocationException,
|
|
ClassNotLoadedException,
|
|
IncompatibleThreadStateException,
|
|
InvalidTypeException {
|
|
Ref<Exception> exception = Ref.create();
|
|
Ref<E> result = Ref.create();
|
|
getManagerThread().startLongProcessAndFork(() -> {
|
|
ThreadReferenceProxyImpl thread = context.getThread();
|
|
ThreadBlockedMonitor.InvocationWatcher invocationWatcher = null;
|
|
try {
|
|
try {
|
|
if (LOG.isDebugEnabled()) {
|
|
final VirtualMachineProxyImpl virtualMachineProxy = getVirtualMachineProxy();
|
|
virtualMachineProxy.logThreads();
|
|
LOG.debug("Invoke in " + thread.name());
|
|
assertThreadSuspended(thread, context);
|
|
}
|
|
|
|
if (myMethod.isVarArgs()) {
|
|
// See IDEA-63581
|
|
// if vararg parameter array is of interface type and Object[] is expected, JDI wrap it into another array,
|
|
// in this case we have to unroll the array manually and pass its elements to the method instead of array object
|
|
int lastIndex = myMethod.argumentTypeNames().size() - 1;
|
|
if (lastIndex >= 0 && myArgs.size() > lastIndex) { // at least one varargs param
|
|
Object firstVararg = myArgs.get(lastIndex);
|
|
if (myArgs.size() == lastIndex + 1) { // only one vararg param
|
|
if (firstVararg instanceof ArrayReference arrayRef) {
|
|
if (((ArrayType)arrayRef.referenceType()).componentType() instanceof InterfaceType) {
|
|
List<String> argTypes = myMethod.argumentTypeNames();
|
|
if (argTypes.size() > lastIndex && argTypes.get(lastIndex).startsWith(CommonClassNames.JAVA_LANG_OBJECT)) {
|
|
// unwrap array of interfaces for vararg param
|
|
myArgs.remove(lastIndex);
|
|
myArgs.addAll(arrayRef.getValues());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Patches.IBM_JDK_DISABLE_COLLECTION_BUG) {
|
|
// ensure args are not collected
|
|
StreamEx.of(myArgs).select(ObjectReference.class).forEach(DebuggerUtilsEx::disableCollection);
|
|
}
|
|
|
|
if (Patches.JDK_BUG_ID_21275177 && (ourTraceMask & VirtualMachine.TRACE_SENDS) != 0) {
|
|
//noinspection ResultOfMethodCallIgnored
|
|
myArgs.forEach(Object::toString);
|
|
}
|
|
|
|
// workaround for jdi hang in trace mode, see IDEA-183387
|
|
if (Patches.JDK_BUG_WITH_TRACE_SEND && (getTraceMask() & VirtualMachine.TRACE_SENDS) != 0) {
|
|
StreamEx.of(myArgs).findAny(ThreadReference.class::isInstance).ifPresent(t -> {
|
|
//noinspection UseOfSystemOutOrSystemErr
|
|
System.err.println("[JDI: workaround for invocation of " + myMethod + "]");
|
|
myMethod.virtualMachine().setDebugTraceMode(getTraceMask() & ~VirtualMachine.TRACE_SENDS);
|
|
});
|
|
}
|
|
|
|
int invokePolicy = getInvokePolicy(context);
|
|
invocationWatcher = myThreadBlockedMonitor.startInvokeWatching(invokePolicy, thread, context);
|
|
|
|
result.set(invokeMethod(invokePolicy, myMethod, myArgs));
|
|
}
|
|
finally {
|
|
if (invocationWatcher != null) {
|
|
invocationWatcher.invocationFinished();
|
|
}
|
|
if (Patches.JDK_BUG_WITH_TRACE_SEND && (getTraceMask() & VirtualMachine.TRACE_SENDS) != 0) {
|
|
myMethod.virtualMachine().setDebugTraceMode(getTraceMask());
|
|
}
|
|
// assertThreadSuspended(thread, context);
|
|
if (!Patches.IBM_JDK_DISABLE_COLLECTION_BUG) {
|
|
// ensure args are not collected
|
|
StreamEx.of(myArgs).select(ObjectReference.class).forEach(DebuggerUtilsEx::enableCollection);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
exception.set(e);
|
|
}
|
|
});
|
|
|
|
Exception ex = exception.get();
|
|
if (ex != null) {
|
|
if (ex instanceof InvocationException) {
|
|
throw (InvocationException)ex;
|
|
}
|
|
else if (ex instanceof ClassNotLoadedException) {
|
|
throw (ClassNotLoadedException)ex;
|
|
}
|
|
else if (ex instanceof IncompatibleThreadStateException) {
|
|
throw (IncompatibleThreadStateException)ex;
|
|
}
|
|
else if (ex instanceof InvalidTypeException) {
|
|
throw (InvalidTypeException)ex;
|
|
}
|
|
else if (ex instanceof RuntimeException) {
|
|
throw (RuntimeException)ex;
|
|
}
|
|
else {
|
|
LOG.error("Unexpected exception", new Throwable(ex));
|
|
}
|
|
}
|
|
|
|
return result.get();
|
|
}
|
|
|
|
private void assertThreadSuspended(final ThreadReferenceProxyImpl thread, final SuspendContextImpl context) {
|
|
LOG.assertTrue(context.isEvaluating());
|
|
try {
|
|
final boolean isSuspended = thread.isSuspended();
|
|
LOG.assertTrue(isSuspended, thread);
|
|
}
|
|
catch (ObjectCollectedException ignored) {
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Value invokeMethod(@NotNull EvaluationContext evaluationContext,
|
|
@NotNull ObjectReference objRef,
|
|
@NotNull Method method,
|
|
@NotNull List<? extends Value> args) throws EvaluateException {
|
|
return invokeInstanceMethod(evaluationContext, objRef, method, args, 0);
|
|
}
|
|
|
|
@Override
|
|
public Value invokeInstanceMethod(@NotNull EvaluationContext evaluationContext,
|
|
@NotNull final ObjectReference objRef,
|
|
@NotNull Method method,
|
|
@NotNull List<? extends Value> args,
|
|
final int invocationOptions) throws EvaluateException {
|
|
return invokeInstanceMethod(evaluationContext, objRef, method, args, invocationOptions, false);
|
|
}
|
|
|
|
public Value invokeInstanceMethod(@NotNull EvaluationContext evaluationContext,
|
|
@NotNull final ObjectReference objRef,
|
|
@NotNull Method method,
|
|
@NotNull List<? extends Value> args,
|
|
final int invocationOptions,
|
|
boolean internalEvaluate) throws EvaluateException {
|
|
final ThreadReference thread = getEvaluationThread(evaluationContext);
|
|
return new InvokeCommand<>(method, args) {
|
|
@Override
|
|
protected Value invokeMethod(int invokePolicy, Method method, List<? extends Value> args)
|
|
throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException {
|
|
if (LOG.isDebugEnabled()) {
|
|
LOG.debug("Invoking " + objRef.type().name() + "." + method.name());
|
|
}
|
|
return objRef.invokeMethod(thread, method, args, invokePolicy | invocationOptions);
|
|
}
|
|
}.start((EvaluationContextImpl)evaluationContext, internalEvaluate);
|
|
}
|
|
|
|
private static ThreadReference getEvaluationThread(final EvaluationContext evaluationContext) throws EvaluateException {
|
|
ThreadReferenceProxy evaluationThread = evaluationContext.getSuspendContext().getThread();
|
|
if (evaluationThread == null) {
|
|
throw EvaluateExceptionUtil.NULL_STACK_FRAME;
|
|
}
|
|
return evaluationThread.getThreadReference();
|
|
}
|
|
|
|
@Override
|
|
public Value invokeMethod(EvaluationContext evaluationContext,
|
|
ClassType classType,
|
|
Method method,
|
|
List<? extends Value> args) throws EvaluateException {
|
|
return invokeMethod(evaluationContext, classType, method, args, 0, false);
|
|
}
|
|
|
|
public Value invokeMethod(@NotNull EvaluationContext evaluationContext,
|
|
@NotNull final ClassType classType,
|
|
@NotNull Method method,
|
|
@NotNull List<? extends Value> args,
|
|
boolean internalEvaluate) throws EvaluateException {
|
|
return invokeMethod(evaluationContext, classType, method, args, 0, internalEvaluate);
|
|
}
|
|
|
|
public Value invokeMethod(@NotNull EvaluationContext evaluationContext,
|
|
@NotNull final ClassType classType,
|
|
@NotNull Method method,
|
|
@NotNull List<? extends Value> args,
|
|
int extraInvocationOptions,
|
|
boolean internalEvaluate) throws EvaluateException {
|
|
final ThreadReference thread = getEvaluationThread(evaluationContext);
|
|
return new InvokeCommand<>(method, args) {
|
|
@Override
|
|
protected Value invokeMethod(int invokePolicy, Method method, List<? extends Value> args) throws InvocationException,
|
|
ClassNotLoadedException,
|
|
IncompatibleThreadStateException,
|
|
InvalidTypeException {
|
|
if (LOG.isDebugEnabled()) {
|
|
LOG.debug("Invoking " + classType.name() + "." + method.name());
|
|
}
|
|
return classType.invokeMethod(thread, method, args, invokePolicy | extraInvocationOptions);
|
|
}
|
|
}.start((EvaluationContextImpl)evaluationContext, internalEvaluate);
|
|
}
|
|
|
|
public Value invokeMethod(EvaluationContext evaluationContext,
|
|
InterfaceType interfaceType,
|
|
Method method,
|
|
List<? extends Value> args) throws EvaluateException {
|
|
final ThreadReference thread = getEvaluationThread(evaluationContext);
|
|
return new InvokeCommand<>(method, args) {
|
|
@Override
|
|
protected Value invokeMethod(int invokePolicy, Method method, List<? extends Value> args) throws InvocationException,
|
|
ClassNotLoadedException,
|
|
IncompatibleThreadStateException,
|
|
InvalidTypeException {
|
|
if (LOG.isDebugEnabled()) {
|
|
LOG.debug("Invoking " + interfaceType.name() + "." + method.name());
|
|
}
|
|
|
|
try {
|
|
return interfaceType.invokeMethod(thread, method, args, invokePolicy);
|
|
}
|
|
catch (LinkageError e) {
|
|
throw new IllegalStateException("Interface method invocation is not supported in JVM " +
|
|
SystemInfo.JAVA_VERSION +
|
|
". Use JVM 1.8.0_45 or higher to run " +
|
|
ApplicationNamesInfo.getInstance().getFullProductName());
|
|
}
|
|
}
|
|
}.start((EvaluationContextImpl)evaluationContext, false);
|
|
}
|
|
|
|
|
|
@Override
|
|
public ArrayReference newInstance(final ArrayType arrayType,
|
|
final int dimension)
|
|
throws EvaluateException {
|
|
try {
|
|
return arrayType.newInstance(dimension);
|
|
}
|
|
catch (Exception e) {
|
|
throw EvaluateExceptionUtil.createEvaluateException(e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ObjectReference newInstance(@NotNull final EvaluationContext evaluationContext,
|
|
@NotNull final ClassType classType,
|
|
@NotNull Method method,
|
|
@NotNull List<? extends Value> args) throws EvaluateException {
|
|
return newInstance(evaluationContext, classType, method, args, 0, false);
|
|
}
|
|
|
|
public ObjectReference newInstance(@NotNull final EvaluationContext evaluationContext,
|
|
@NotNull final ClassType classType,
|
|
@NotNull Method method,
|
|
@NotNull List<? extends Value> args,
|
|
final int invocationOptions,
|
|
boolean internalEvaluate) throws EvaluateException {
|
|
final ThreadReference thread = getEvaluationThread(evaluationContext);
|
|
InvokeCommand<ObjectReference> invokeCommand = new InvokeCommand<>(method, args) {
|
|
@Override
|
|
protected ObjectReference invokeMethod(int invokePolicy, Method method, List<? extends Value> args)
|
|
throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException {
|
|
if (LOG.isDebugEnabled()) {
|
|
LOG.debug("New instance " + classType.name() + "." + method.name());
|
|
}
|
|
return classType.newInstance(thread, method, args, invokePolicy | invocationOptions);
|
|
}
|
|
};
|
|
return invokeCommand.start((EvaluationContextImpl)evaluationContext, internalEvaluate);
|
|
}
|
|
|
|
public void clearCashes(@MagicConstant(flagsFromClass = EventRequest.class) int suspendPolicy) {
|
|
if (!isAttached()) return;
|
|
switch (suspendPolicy) {
|
|
case EventRequest.SUSPEND_ALL, EventRequest.SUSPEND_EVENT_THREAD -> getVirtualMachineProxy().clearCaches();
|
|
}
|
|
}
|
|
|
|
protected void beforeSuspend(SuspendContextImpl suspendContext) {
|
|
clearCashes(suspendContext.getSuspendPolicy());
|
|
}
|
|
|
|
private void beforeMethodInvocation(SuspendContextImpl suspendContext, Method method, boolean internalEvaluate) {
|
|
if (LOG.isDebugEnabled()) {
|
|
LOG.debug(
|
|
"before invocation in thread " + suspendContext.getThread().name() + " method " + (method == null ? "null" : method.name()));
|
|
}
|
|
|
|
if (!internalEvaluate) {
|
|
if (method != null) {
|
|
showStatusText(JavaDebuggerBundle.message("progress.evaluating", DebuggerUtilsEx.methodName(method)));
|
|
}
|
|
else {
|
|
showStatusText(JavaDebuggerBundle.message("title.evaluating"));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void afterMethodInvocation(SuspendContextImpl suspendContext, boolean internalEvaluate) {
|
|
if (LOG.isDebugEnabled()) {
|
|
LOG.debug("after invocation in thread " + suspendContext.getThread().name());
|
|
}
|
|
if (!internalEvaluate) {
|
|
showStatusText("");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ReferenceType findClass(@Nullable EvaluationContext evaluationContext, String className,
|
|
ClassLoaderReference classLoader) throws EvaluateException {
|
|
try {
|
|
DebuggerManagerThreadImpl.assertIsManagerThread();
|
|
ReferenceType result;
|
|
List<ReferenceType> types = ContainerUtil.filter(getVirtualMachineProxy().classesByName(className), ReferenceType::isPrepared);
|
|
// first try to quickly find the equal classloader only
|
|
result = ContainerUtil.find(types, refType -> Objects.equals(classLoader, refType.classLoader()));
|
|
// now do the full visibility check
|
|
if (result == null && classLoader != null) {
|
|
result = ContainerUtil.find(types, refType -> isVisibleFromClassLoader(classLoader, refType));
|
|
}
|
|
if (result == null && evaluationContext != null) {
|
|
EvaluationContextImpl evalContext = (EvaluationContextImpl)evaluationContext;
|
|
if (evalContext.isAutoLoadClasses()) {
|
|
return loadClass(evalContext, className, classLoader);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
catch (InvocationException | InvalidTypeException | IncompatibleThreadStateException | ClassNotLoadedException e) {
|
|
throw EvaluateExceptionUtil.createEvaluateException(e);
|
|
}
|
|
}
|
|
|
|
private static boolean isVisibleFromClassLoader(@NotNull ClassLoaderReference fromLoader, final ReferenceType refType) {
|
|
// IMPORTANT! Even if the refType is already loaded by some parent or bootstrap loader, it may not be visible from the given loader.
|
|
// For example because there were no accesses yet from this loader to this class. So the loader is not in the list of "initialing" loaders
|
|
// for this refType and the refType is not visible to the loader.
|
|
// Attempt to evaluate method with this refType will yield ClassNotLoadedException.
|
|
// The only way to say for sure whether the class is _visible_ to the given loader, is to use the following API call
|
|
if (fromLoader instanceof ClassLoaderReferenceImpl) {
|
|
return ((ClassLoaderReferenceImpl)fromLoader).isVisible(refType);
|
|
}
|
|
return fromLoader.visibleClasses().contains(refType);
|
|
}
|
|
|
|
private static String reformatArrayName(String className) {
|
|
if (className.indexOf('[') == -1) return className;
|
|
|
|
int dims = 0;
|
|
while (className.endsWith("[]")) {
|
|
className = className.substring(0, className.length() - 2);
|
|
dims++;
|
|
}
|
|
|
|
StringBuilder buffer = new StringBuilder();
|
|
StringUtil.repeatSymbol(buffer, '[', dims);
|
|
String primitiveSignature = JVMNameUtil.getPrimitiveSignature(className);
|
|
if (primitiveSignature != null) {
|
|
buffer.append(primitiveSignature);
|
|
}
|
|
else {
|
|
buffer.append('L');
|
|
buffer.append(className);
|
|
buffer.append(';');
|
|
}
|
|
return buffer.toString();
|
|
}
|
|
|
|
@SuppressWarnings({"SpellCheckingInspection"})
|
|
public ReferenceType loadClass(EvaluationContextImpl evaluationContext, String qName, ClassLoaderReference classLoader)
|
|
throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException, EvaluateException {
|
|
|
|
DebuggerManagerThreadImpl.assertIsManagerThread();
|
|
qName = reformatArrayName(qName);
|
|
VirtualMachineProxyImpl virtualMachine = getVirtualMachineProxy();
|
|
ClassType classClassType = (ClassType)ContainerUtil.getFirstItem(virtualMachine.classesByName(CommonClassNames.JAVA_LANG_CLASS));
|
|
if (classClassType == null) {
|
|
LOG.error("Unable to find loaded class " + CommonClassNames.JAVA_LANG_CLASS);
|
|
return null;
|
|
}
|
|
final Method forNameMethod;
|
|
List<Value> args = new ArrayList<>(); // do not use unmodifiable lists because the list is modified by JPDA
|
|
args.add(virtualMachine.mirrorOf(qName));
|
|
if (classLoader != null) {
|
|
//forNameMethod = classClassType.concreteMethodByName("forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;");
|
|
forNameMethod = DebuggerUtils.findMethod(classClassType, "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;");
|
|
args.add(virtualMachine.mirrorOf(true));
|
|
args.add(classLoader);
|
|
}
|
|
else {
|
|
//forNameMethod = classClassType.concreteMethodByName("forName", "(Ljava/lang/String;)Ljava/lang/Class;");
|
|
forNameMethod = DebuggerUtils.findMethod(classClassType, "forName", "(Ljava/lang/String;)Ljava/lang/Class;");
|
|
}
|
|
if (forNameMethod == null) {
|
|
LOG.error("Unable to find forName method in " + classClassType);
|
|
return null;
|
|
}
|
|
Value classReference = invokeMethod(evaluationContext, classClassType, forNameMethod, args, MethodImpl.SKIP_ASSIGNABLE_CHECK, true);
|
|
if (classReference instanceof ClassObjectReference) {
|
|
ReferenceType refType = ((ClassObjectReference)classReference).reflectedType();
|
|
if (classLoader instanceof ClassLoaderReferenceImpl) {
|
|
((ClassLoaderReferenceImpl)classLoader).addVisible(refType);
|
|
}
|
|
return refType;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void logThreads() {
|
|
if (LOG.isDebugEnabled()) {
|
|
try {
|
|
Collection<ThreadReferenceProxyImpl> allThreads = getVirtualMachineProxy().allThreads();
|
|
for (ThreadReferenceProxyImpl threadReferenceProxy : allThreads) {
|
|
LOG.debug("Thread name=" + threadReferenceProxy.name() + " suspendCount()=" + threadReferenceProxy.getSuspendCount());
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
LOG.debug(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void onHotSwapFinished() {
|
|
getPositionManager().clearCache();
|
|
StackCapturingLineBreakpoint.clearCaches(this);
|
|
}
|
|
|
|
@NotNull
|
|
public SuspendManager getSuspendManager() {
|
|
return mySuspendManager;
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public CompoundPositionManager getPositionManager() {
|
|
return myPositionManager;
|
|
}
|
|
//ManagerCommands
|
|
|
|
@Override
|
|
public void stop(boolean forceTerminate) {
|
|
myIsStopped.set(true);
|
|
stopConnecting(); // does this first place in case debugger manager hanged accepting debugger connection (forever)
|
|
getManagerThread().terminateAndInvoke(createStopCommand(forceTerminate), ApplicationManager.getApplication().isUnitTestMode() ? 0 : DebuggerManagerThreadImpl.COMMAND_TIMEOUT);
|
|
}
|
|
|
|
@NotNull
|
|
public StopCommand createStopCommand(boolean forceTerminate) {
|
|
return new StopCommand(forceTerminate);
|
|
}
|
|
|
|
protected class StopCommand extends DebuggerCommandImpl {
|
|
private final boolean myIsTerminateTargetVM;
|
|
|
|
public StopCommand(boolean isTerminateTargetVM) {
|
|
myIsTerminateTargetVM = isTerminateTargetVM;
|
|
}
|
|
|
|
@Override
|
|
public Priority getPriority() {
|
|
return Priority.HIGH;
|
|
}
|
|
|
|
@Override
|
|
protected void action() {
|
|
if (isAttached()) {
|
|
VirtualMachineProxyImpl virtualMachineProxy = getVirtualMachineProxy();
|
|
|
|
if (!virtualMachineProxy.canBeModified()) {
|
|
closeProcess(false);
|
|
return;
|
|
}
|
|
|
|
if (myIsTerminateTargetVM) {
|
|
virtualMachineProxy.exit(-1);
|
|
}
|
|
else {
|
|
// some VMs (like IBM VM 1.4.2 bundled with WebSphere) does not resume threads on dispose() like it should
|
|
try {
|
|
virtualMachineProxy.resume();
|
|
}
|
|
finally {
|
|
virtualMachineProxy.dispose();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
try {
|
|
stopConnecting();
|
|
}
|
|
finally {
|
|
closeProcess(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public class StepOutCommand extends StepCommand {
|
|
private final int myStepSize;
|
|
|
|
public StepOutCommand(SuspendContextImpl suspendContext, int stepSize) {
|
|
super(suspendContext, null);
|
|
myStepSize = stepSize;
|
|
}
|
|
|
|
@Override
|
|
public void contextAction(@NotNull SuspendContextImpl suspendContext) {
|
|
showStatusText(JavaDebuggerBundle.message("status.step.out"));
|
|
final ThreadReferenceProxyImpl thread = getContextThread();
|
|
RequestHint hint = getHint(suspendContext, thread, null);
|
|
applyThreadFilter(thread);
|
|
startWatchingMethodReturn(thread);
|
|
step(suspendContext, thread, hint);
|
|
super.contextAction(suspendContext);
|
|
}
|
|
|
|
@Override
|
|
@NotNull
|
|
public RequestHint getHint(SuspendContextImpl suspendContext, ThreadReferenceProxyImpl thread, @Nullable RequestHint parentHint) {
|
|
RequestHint hint = new RequestHint(thread, suspendContext, StepRequest.STEP_LINE, StepRequest.STEP_OUT, null, parentHint);
|
|
hint.setIgnoreFilters(mySession.shouldIgnoreSteppingFilters());
|
|
return hint;
|
|
}
|
|
|
|
@Override
|
|
public void step(SuspendContextImpl suspendContext, ThreadReferenceProxyImpl stepThread, RequestHint hint) {
|
|
doStep(suspendContext, stepThread, myStepSize, StepRequest.STEP_OUT, hint);
|
|
}
|
|
}
|
|
|
|
public class StepIntoCommand extends StepCommand {
|
|
private final boolean myForcedIgnoreFilters;
|
|
@Nullable
|
|
private final StepIntoBreakpoint myBreakpoint;
|
|
private final int myStepSize;
|
|
|
|
public StepIntoCommand(SuspendContextImpl suspendContext, boolean ignoreFilters, @Nullable final MethodFilter methodFilter,
|
|
int stepSize) {
|
|
super(suspendContext, methodFilter);
|
|
myForcedIgnoreFilters = ignoreFilters || methodFilter != null;
|
|
myBreakpoint = methodFilter instanceof BreakpointStepMethodFilter ?
|
|
DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().addStepIntoBreakpoint(((BreakpointStepMethodFilter)methodFilter)) :
|
|
null;
|
|
myStepSize = stepSize;
|
|
}
|
|
|
|
@Override
|
|
public void contextAction(@NotNull SuspendContextImpl suspendContext) {
|
|
showStatusText(JavaDebuggerBundle.message("status.step.into"));
|
|
final ThreadReferenceProxyImpl stepThread = getContextThread();
|
|
final RequestHint hint = getHint(suspendContext, stepThread, null);
|
|
if (myForcedIgnoreFilters) {
|
|
mySession.setIgnoreStepFiltersFlag(getFrameCount(stepThread, suspendContext));
|
|
}
|
|
hint.setIgnoreFilters(myForcedIgnoreFilters || mySession.shouldIgnoreSteppingFilters());
|
|
applyThreadFilter(stepThread);
|
|
if (myBreakpoint != null) {
|
|
prepareAndSetSteppingBreakpoint(suspendContext, myBreakpoint, hint, false);
|
|
}
|
|
step(suspendContext, stepThread, hint);
|
|
super.contextAction(suspendContext);
|
|
}
|
|
|
|
@Override
|
|
@NotNull
|
|
public RequestHint getHint(SuspendContextImpl suspendContext, ThreadReferenceProxyImpl stepThread, @Nullable RequestHint parentHint) {
|
|
final RequestHint hint = new RequestHint(stepThread, suspendContext, StepRequest.STEP_LINE, StepRequest.STEP_INTO, myMethodFilter, parentHint);
|
|
hint.setResetIgnoreFilters(myMethodFilter != null && !mySession.shouldIgnoreSteppingFilters());
|
|
return hint;
|
|
}
|
|
|
|
@Override
|
|
public void step(SuspendContextImpl suspendContext, ThreadReferenceProxyImpl stepThread, RequestHint hint) {
|
|
doStep(suspendContext, stepThread, myStepSize, StepRequest.STEP_INTO, hint);
|
|
}
|
|
}
|
|
|
|
public class StepOverCommand extends StepCommand {
|
|
private final boolean myIsIgnoreBreakpoints;
|
|
private final int myStepSize;
|
|
|
|
public StepOverCommand(SuspendContextImpl suspendContext,
|
|
boolean ignoreBreakpoints,
|
|
@Nullable MethodFilter methodFilter,
|
|
int stepSize) {
|
|
super(suspendContext, methodFilter);
|
|
myIsIgnoreBreakpoints = ignoreBreakpoints;
|
|
myStepSize = stepSize;
|
|
}
|
|
|
|
public StepOverCommand(SuspendContextImpl suspendContext, boolean ignoreBreakpoints, int stepSize) {
|
|
this(suspendContext, ignoreBreakpoints, null, stepSize);
|
|
}
|
|
|
|
protected int getStepDepth() {
|
|
return StepRequest.STEP_OVER;
|
|
}
|
|
|
|
@NotNull
|
|
protected @Nls String getStatusText() {
|
|
return JavaDebuggerBundle.message("status.step.over");
|
|
}
|
|
|
|
@Override
|
|
@NotNull
|
|
public RequestHint getHint(SuspendContextImpl suspendContext, ThreadReferenceProxyImpl stepThread, @Nullable RequestHint parentHint) {
|
|
// need this hint while stepping over for JSR45 support:
|
|
// several lines of generated java code may correspond to a single line in the source file,
|
|
// from which the java code was generated
|
|
RequestHint hint = new RequestHint(stepThread, suspendContext, StepRequest.STEP_LINE, StepRequest.STEP_OVER, myMethodFilter, parentHint);
|
|
hint.setRestoreBreakpoints(myIsIgnoreBreakpoints);
|
|
hint.setIgnoreFilters(myIsIgnoreBreakpoints || mySession.shouldIgnoreSteppingFilters());
|
|
return hint;
|
|
}
|
|
|
|
@Override
|
|
public void contextAction(@NotNull SuspendContextImpl suspendContext) {
|
|
showStatusText(getStatusText());
|
|
|
|
ThreadReferenceProxyImpl stepThread = getContextThread();
|
|
|
|
RequestHint hint = getHint(suspendContext, stepThread, null);
|
|
|
|
applyThreadFilter(stepThread);
|
|
|
|
startWatchingMethodReturn(stepThread);
|
|
|
|
step(suspendContext, stepThread, hint);
|
|
|
|
if (myIsIgnoreBreakpoints) {
|
|
DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().disableBreakpoints(DebugProcessImpl.this);
|
|
}
|
|
super.contextAction(suspendContext);
|
|
}
|
|
|
|
@Override
|
|
public void step(SuspendContextImpl suspendContext, ThreadReferenceProxyImpl stepThread, RequestHint hint) {
|
|
doStep(suspendContext, stepThread, myStepSize, getStepDepth(), hint);
|
|
}
|
|
}
|
|
|
|
private final class RunToCursorCommand extends StepCommand {
|
|
private final RunToCursorBreakpoint myRunToCursorBreakpoint;
|
|
private final boolean myIgnoreBreakpoints;
|
|
|
|
private RunToCursorCommand(SuspendContextImpl suspendContext, @NotNull XSourcePosition position, final boolean ignoreBreakpoints) {
|
|
super(suspendContext, null);
|
|
myIgnoreBreakpoints = ignoreBreakpoints;
|
|
myRunToCursorBreakpoint =
|
|
DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().addRunToCursorBreakpoint(position, ignoreBreakpoints);
|
|
}
|
|
|
|
@Override
|
|
public void contextAction(@NotNull SuspendContextImpl context) {
|
|
showStatusText(JavaDebuggerBundle.message("status.run.to.cursor"));
|
|
cancelRunToCursorBreakpoint();
|
|
if (myRunToCursorBreakpoint == null) {
|
|
return;
|
|
}
|
|
if (myIgnoreBreakpoints) {
|
|
DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().disableBreakpoints(DebugProcessImpl.this);
|
|
}
|
|
applyThreadFilter(getContextThread());
|
|
prepareAndSetSteppingBreakpoint(context, myRunToCursorBreakpoint, null, false);
|
|
final DebugProcessImpl debugProcess = context.getDebugProcess();
|
|
|
|
if (debugProcess.getRequestsManager().getWarning(myRunToCursorBreakpoint) == null) {
|
|
super.contextAction(context);
|
|
}
|
|
else {
|
|
DebuggerInvocationUtil.swingInvokeLater(myProject, () -> {
|
|
Messages.showErrorDialog(
|
|
JavaDebuggerBundle.message("error.running.to.cursor.no.executable.code",
|
|
myRunToCursorBreakpoint.getSourcePosition().getFile().getName(),
|
|
myRunToCursorBreakpoint.getLineIndex() + 1),
|
|
UIUtil.removeMnemonic(ActionsBundle.actionText(XDebuggerActions.RUN_TO_CURSOR)));
|
|
DebuggerSession session = debugProcess.getSession();
|
|
session.getContextManager().setState(DebuggerContextUtil.createDebuggerContext(session, context),
|
|
DebuggerSession.State.PAUSED, DebuggerSession.Event.CONTEXT, null);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
public abstract class StepCommand extends ResumeCommand {
|
|
@Nullable
|
|
protected final MethodFilter myMethodFilter;
|
|
|
|
StepCommand(SuspendContextImpl suspendContext, @Nullable MethodFilter filter) {
|
|
super(suspendContext);
|
|
myMethodFilter = filter;
|
|
}
|
|
|
|
@Override
|
|
protected void resumeAction() {
|
|
SuspendContextImpl context = getSuspendContext();
|
|
if (context != null &&
|
|
(context.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD || isResumeOnlyCurrentThread())) {
|
|
myThreadBlockedMonitor.startWatching(myContextThread);
|
|
}
|
|
if (context != null
|
|
&& isResumeOnlyCurrentThread()
|
|
&& context.getSuspendPolicy() == EventRequest.SUSPEND_ALL
|
|
&& myContextThread != null) {
|
|
getSuspendManager().resumeThread(context, myContextThread);
|
|
}
|
|
else {
|
|
super.resumeAction();
|
|
}
|
|
}
|
|
|
|
public void step(SuspendContextImpl suspendContext, ThreadReferenceProxyImpl stepThread, RequestHint hint) {
|
|
}
|
|
|
|
@Nullable
|
|
public RequestHint getHint(SuspendContextImpl suspendContext, ThreadReferenceProxyImpl stepThread, @Nullable RequestHint parentHint) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public abstract class ResumeCommand extends SuspendContextCommandImpl {
|
|
@Nullable protected final ThreadReferenceProxyImpl myContextThread;
|
|
|
|
public ResumeCommand(SuspendContextImpl suspendContext) {
|
|
super(suspendContext);
|
|
final ThreadReferenceProxyImpl thread = suspendContext != null ? suspendContext.getThread() : null;
|
|
myContextThread = thread != null ? thread : getDebuggerContext().getThreadProxy();
|
|
}
|
|
|
|
@Override
|
|
public Priority getPriority() {
|
|
return Priority.HIGH;
|
|
}
|
|
|
|
@Override
|
|
public void contextAction(@NotNull SuspendContextImpl suspendContext) {
|
|
showStatusText(JavaDebuggerBundle.message("status.process.resumed"));
|
|
resumeAction();
|
|
myDebugProcessDispatcher.getMulticaster().resumed(suspendContext);
|
|
}
|
|
|
|
protected void resumeAction() {
|
|
getSuspendManager().resume(getSuspendContext());
|
|
}
|
|
|
|
@Nullable
|
|
public ThreadReferenceProxyImpl getContextThread() {
|
|
return myContextThread;
|
|
}
|
|
|
|
protected void applyThreadFilter(ThreadReferenceProxy thread) {
|
|
if (getSuspendContext().getSuspendPolicy() == EventRequest.SUSPEND_ALL) {
|
|
// there could be explicit resume as a result of call to voteSuspend()
|
|
// e.g. when breakpoint was considered invalid, in that case the filter will be applied _after_
|
|
// resuming and all breakpoints in other threads will be ignored.
|
|
// As resume() implicitly cleares the filter, the filter must be always applied _before_ any resume() action happens
|
|
final BreakpointManager breakpointManager = DebuggerManagerEx.getInstanceEx(getProject()).getBreakpointManager();
|
|
breakpointManager.applyThreadFilter(DebugProcessImpl.this, thread.getThreadReference());
|
|
}
|
|
}
|
|
}
|
|
|
|
private class PauseCommand extends DebuggerCommandImpl {
|
|
PauseCommand() {
|
|
}
|
|
|
|
@Override
|
|
public void action() {
|
|
if (!isAttached() || getVirtualMachineProxy().isPausePressed()) {
|
|
return;
|
|
}
|
|
logThreads();
|
|
getVirtualMachineProxy().suspend();
|
|
logThreads();
|
|
SuspendContextImpl suspendContext = mySuspendManager.pushSuspendContext(EventRequest.SUSPEND_ALL, 0);
|
|
myDebugProcessDispatcher.getMulticaster().paused(suspendContext);
|
|
}
|
|
}
|
|
|
|
private class ResumeThreadCommand extends SuspendContextCommandImpl {
|
|
private final ThreadReferenceProxyImpl myThread;
|
|
|
|
ResumeThreadCommand(SuspendContextImpl suspendContext, @NotNull ThreadReferenceProxyImpl thread) {
|
|
super(suspendContext);
|
|
myThread = thread;
|
|
}
|
|
|
|
@Override
|
|
public void contextAction(@NotNull SuspendContextImpl context) {
|
|
// handle unfreeze through the regular context resume
|
|
if (false && getSuspendManager().isFrozen(myThread)) {
|
|
getSuspendManager().unfreezeThread(myThread);
|
|
return;
|
|
}
|
|
|
|
final Set<SuspendContextImpl> suspendingContexts = SuspendManagerUtil.getSuspendingContexts(getSuspendManager(), myThread);
|
|
for (SuspendContextImpl suspendContext : suspendingContexts) {
|
|
if (suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD && suspendContext.getThread() == myThread) {
|
|
getSession().getXDebugSession().sessionResumed();
|
|
getManagerThread().invoke(createResumeCommand(suspendContext));
|
|
}
|
|
else {
|
|
getSuspendManager().resumeThread(suspendContext, myThread);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private class FreezeThreadCommand extends DebuggerCommandImpl {
|
|
private final ThreadReferenceProxyImpl myThread;
|
|
|
|
FreezeThreadCommand(ThreadReferenceProxyImpl thread) {
|
|
myThread = thread;
|
|
}
|
|
|
|
@Override
|
|
protected void action() {
|
|
SuspendManager suspendManager = getSuspendManager();
|
|
if (!suspendManager.isFrozen(myThread)) {
|
|
suspendManager.freezeThread(myThread);
|
|
SuspendContextImpl suspendContext = mySuspendManager.pushSuspendContext(EventRequest.SUSPEND_EVENT_THREAD, 0);
|
|
suspendContext.setThread(myThread.getThreadReference());
|
|
myDebugProcessDispatcher.getMulticaster().paused(suspendContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
private class PopFrameCommand extends DebuggerContextCommandImpl {
|
|
private final StackFrameProxyImpl myStackFrame;
|
|
|
|
PopFrameCommand(DebuggerContextImpl context, StackFrameProxyImpl frameProxy) {
|
|
super(context, frameProxy.threadProxy());
|
|
myStackFrame = frameProxy;
|
|
}
|
|
|
|
@Override
|
|
public void threadAction(@NotNull SuspendContextImpl suspendContext) {
|
|
final ThreadReferenceProxyImpl thread = myStackFrame.threadProxy();
|
|
try {
|
|
if (!getSuspendManager().isSuspended(thread)) {
|
|
notifyCancelled();
|
|
return;
|
|
}
|
|
}
|
|
catch (ObjectCollectedException ignored) {
|
|
notifyCancelled();
|
|
return;
|
|
}
|
|
|
|
if (!suspendContext.suspends(thread)) {
|
|
suspendContext.postponeCommand(this);
|
|
return;
|
|
}
|
|
|
|
if (myStackFrame.isBottom()) {
|
|
DebuggerInvocationUtil.swingInvokeLater(myProject, () -> Messages.showMessageDialog(myProject, JavaDebuggerBundle
|
|
.message("error.pop.bottom.stackframe"), XDebuggerBundle.message("xdebugger.reset.frame.title"), Messages.getErrorIcon()));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
thread.popFrames(myStackFrame);
|
|
getSuspendManager().popFrame(suspendContext);
|
|
}
|
|
catch (final EvaluateException e) {
|
|
DebuggerInvocationUtil.swingInvokeLater(myProject, () ->
|
|
Messages.showMessageDialog(myProject, JavaDebuggerBundle.message("error.pop.stackframe", e.getLocalizedMessage()),
|
|
XDebuggerBundle.message("xdebugger.reset.frame.title"), Messages.getErrorIcon()));
|
|
LOG.info(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
@NotNull
|
|
public GlobalSearchScope getSearchScope() {
|
|
LOG.assertTrue(mySession != null, "Accessing debug session before its initialization");
|
|
return mySession.getSearchScope();
|
|
}
|
|
|
|
public void reattach(final DebugEnvironment environment) {
|
|
reattach(environment, false, () -> {});
|
|
}
|
|
|
|
public void reattach(final DebugEnvironment environment, boolean keepCurrentVM, Runnable vmReadyCallback) {
|
|
reattach(environment, () -> {
|
|
if (keepCurrentVM) {
|
|
detachProcess(false, true, vmData -> {
|
|
myStashedVirtualMachines.addFirst(vmData);
|
|
myDebuggerManagerThread = new DebuggerManagerThreadImpl(myDisposable, myProject);
|
|
});
|
|
}
|
|
else {
|
|
closeProcess(false);
|
|
}
|
|
}, vmReadyCallback);
|
|
}
|
|
|
|
private void unstashAndReattach() {
|
|
VirtualMachineData vmData = myStashedVirtualMachines.pollFirst();
|
|
if (vmData != null && vmData.vm != null) {
|
|
myDebuggerManagerThread = vmData.debuggerManagerThread;
|
|
reattach(vmData.connection, () -> {}, () -> {
|
|
afterProcessStarted(() -> getManagerThread().schedule(new DebuggerCommandImpl() {
|
|
@Override
|
|
protected void action() {
|
|
try {
|
|
commitVM(vmData.vm.getVirtualMachine());
|
|
}
|
|
catch (VMDisconnectedException e) {
|
|
fail();
|
|
}
|
|
}
|
|
}));
|
|
});
|
|
}
|
|
}
|
|
|
|
private void reattach(DebugEnvironment environment, Runnable detachVm, Runnable vmReadyCallback) {
|
|
reattach(environment.getRemoteConnection(), detachVm, () -> {
|
|
createVirtualMachine(environment);
|
|
vmReadyCallback.run();
|
|
});
|
|
}
|
|
|
|
private void reattach(RemoteConnection connection, Runnable detachVm, Runnable attachVm) {
|
|
if (!myIsStopped.get()) {
|
|
getManagerThread().schedule(new DebuggerCommandImpl() {
|
|
@Override
|
|
protected void action() {
|
|
detachVm.run();
|
|
getManagerThread().processRemaining();
|
|
doReattach();
|
|
}
|
|
|
|
@Override
|
|
protected void commandCancelled() {
|
|
doReattach(); // if the original process is already finished
|
|
}
|
|
|
|
private void doReattach() {
|
|
DebuggerInvocationUtil.swingInvokeLater(myProject, () -> {
|
|
((XDebugSessionImpl)getXdebugProcess().getSession()).reset();
|
|
myState.set(State.INITIAL);
|
|
myConnection = connection;
|
|
getManagerThread().restartIfNeeded();
|
|
attachVm.run();
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
public ExecutionResult attachVirtualMachine(final DebugEnvironment environment,
|
|
final DebuggerSession session) throws ExecutionException {
|
|
mySession = session;
|
|
myWaitFor.down();
|
|
|
|
ApplicationManager.getApplication().assertIsDispatchThread();
|
|
LOG.assertTrue(isInInitialState());
|
|
|
|
myConnection = environment.getRemoteConnection();
|
|
|
|
// in client mode start target process before the debugger to reduce polling
|
|
if (!(myConnection instanceof RemoteConnectionStub) &&
|
|
!(myConnection instanceof DelayedRemoteConnection) &&
|
|
myConnection.isServerMode()) {
|
|
createVirtualMachine(environment);
|
|
if (myIsFailed.get()) {
|
|
myExecutionResult = null;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
ExecutionResult executionResult;
|
|
try {
|
|
synchronized (myProcessListeners) {
|
|
executionResult = environment.createExecutionResult();
|
|
myExecutionResult = executionResult;
|
|
if (executionResult == null) {
|
|
fail();
|
|
return null;
|
|
}
|
|
for (ProcessListener processListener : myProcessListeners) {
|
|
executionResult.getProcessHandler().addProcessListener(processListener);
|
|
}
|
|
myProcessListeners.clear();
|
|
if (myTextBeforeStart.length() > 0) {
|
|
printToConsoleImpl(myTextBeforeStart.toString());
|
|
myTextBeforeStart.setLength(0);
|
|
}
|
|
}
|
|
}
|
|
catch (ExecutionException e) {
|
|
fail();
|
|
throw e;
|
|
}
|
|
|
|
if (myConnection instanceof DelayedRemoteConnection) {
|
|
((DelayedRemoteConnection)myConnection).setAttachRunnable(() -> createVirtualMachine(environment));
|
|
}
|
|
else if (!(myConnection instanceof RemoteConnectionStub) && !myConnection.isServerMode()) {
|
|
createVirtualMachine(environment);
|
|
if (myIsFailed.get()) {
|
|
myExecutionResult = null;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return executionResult;
|
|
}
|
|
|
|
private void fail() {
|
|
// need this in order to prevent calling stop() twice
|
|
if (myIsFailed.compareAndSet(false, true)) {
|
|
stop(false);
|
|
}
|
|
}
|
|
|
|
private void createVirtualMachine(final DebugEnvironment environment) {
|
|
final String sessionName = environment.getSessionName();
|
|
final long pollTimeout = environment.getPollTimeout();
|
|
final Semaphore semaphore = new Semaphore();
|
|
semaphore.down();
|
|
|
|
final AtomicBoolean connectorIsReady = new AtomicBoolean(false);
|
|
myDebugProcessDispatcher.addListener(new DebugProcessListener() {
|
|
@Override
|
|
public void connectorIsReady() {
|
|
connectorIsReady.set(true);
|
|
semaphore.up();
|
|
myDebugProcessDispatcher.removeListener(this);
|
|
}
|
|
});
|
|
|
|
getManagerThread().schedule(new DebuggerCommandImpl() {
|
|
@Override
|
|
protected void action() {
|
|
VirtualMachine vm = null;
|
|
|
|
try {
|
|
final long time = System.currentTimeMillis();
|
|
do {
|
|
try {
|
|
vm = createVirtualMachineInt();
|
|
break;
|
|
}
|
|
catch (final ExecutionException e) {
|
|
if (pollTimeout > 0 && !myConnection.isServerMode() && e.getCause() instanceof IOException) {
|
|
synchronized (this) {
|
|
try {
|
|
wait(500);
|
|
}
|
|
catch (InterruptedException ignored) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
ProcessHandler processHandler = getProcessHandler();
|
|
boolean terminated =
|
|
processHandler != null && (processHandler.isProcessTerminating() || processHandler.isProcessTerminated());
|
|
|
|
fail();
|
|
DebuggerInvocationUtil.swingInvokeLater(myProject, () -> {
|
|
// propagate exception only in case we succeeded to obtain execution result,
|
|
// otherwise if the error is induced by the fact that there is nothing to debug, and there is no need to show
|
|
// this problem to the user
|
|
if (((myExecutionResult != null && !terminated) || !connectorIsReady.get()) &&
|
|
!ApplicationManager.getApplication().isHeadlessEnvironment()) {
|
|
ExecutionUtil.handleExecutionError(myProject, ToolWindowId.DEBUG, sessionName, e);
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
while (System.currentTimeMillis() - time < pollTimeout);
|
|
}
|
|
finally {
|
|
semaphore.up();
|
|
}
|
|
|
|
if (vm != null) {
|
|
final VirtualMachine vm1 = vm;
|
|
afterProcessStarted(() -> getManagerThread().schedule(new DebuggerCommandImpl() {
|
|
@Override
|
|
protected void action() {
|
|
try {
|
|
commitVM(vm1);
|
|
}
|
|
catch (VMDisconnectedException e) {
|
|
fail();
|
|
}
|
|
}
|
|
}));
|
|
}
|
|
else {
|
|
fail();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void commandCancelled() {
|
|
try {
|
|
super.commandCancelled();
|
|
}
|
|
finally {
|
|
semaphore.up();
|
|
}
|
|
}
|
|
});
|
|
|
|
semaphore.waitFor();
|
|
}
|
|
|
|
private void afterProcessStarted(final Runnable run) {
|
|
class MyProcessAdapter extends ProcessAdapter {
|
|
private boolean alreadyRun = false;
|
|
|
|
public synchronized void run() {
|
|
if (!alreadyRun) {
|
|
alreadyRun = true;
|
|
run.run();
|
|
}
|
|
removeProcessListener(this);
|
|
}
|
|
|
|
@Override
|
|
public void startNotified(@NotNull ProcessEvent event) {
|
|
run();
|
|
}
|
|
}
|
|
MyProcessAdapter processListener = new MyProcessAdapter();
|
|
addProcessListener(processListener);
|
|
if (myExecutionResult != null) {
|
|
if (myExecutionResult.getProcessHandler().isStartNotified()) {
|
|
processListener.run();
|
|
}
|
|
}
|
|
}
|
|
|
|
@NotNull
|
|
public DebuggerCommandImpl createPauseCommand() {
|
|
return new PauseCommand();
|
|
}
|
|
|
|
@NotNull
|
|
public ResumeCommand createResumeCommand(SuspendContextImpl suspendContext) {
|
|
return createResumeCommand(suspendContext, PrioritizedTask.Priority.HIGH);
|
|
}
|
|
|
|
@NotNull
|
|
public ResumeCommand createResumeCommand(SuspendContextImpl suspendContext, final PrioritizedTask.Priority priority) {
|
|
final BreakpointManager breakpointManager = DebuggerManagerEx.getInstanceEx(getProject()).getBreakpointManager();
|
|
return new ResumeCommand(suspendContext) {
|
|
@Override
|
|
public void contextAction(@NotNull SuspendContextImpl suspendContext) {
|
|
breakpointManager.applyThreadFilter(DebugProcessImpl.this, null); // clear the filter on resume
|
|
if (myReturnValueWatcher != null) {
|
|
myReturnValueWatcher.clear();
|
|
}
|
|
super.contextAction(suspendContext);
|
|
}
|
|
|
|
@Override
|
|
public Priority getPriority() {
|
|
return priority;
|
|
}
|
|
};
|
|
}
|
|
|
|
@NotNull
|
|
public ResumeCommand createStepOverCommand(SuspendContextImpl suspendContext, boolean ignoreBreakpoints) {
|
|
return createStepOverCommand(suspendContext, ignoreBreakpoints, StepRequest.STEP_LINE);
|
|
}
|
|
|
|
@NotNull
|
|
public ResumeCommand createStepOverCommand(SuspendContextImpl suspendContext, boolean ignoreBreakpoints, int stepSize) {
|
|
return createStepOverCommand(suspendContext, ignoreBreakpoints, null, StepRequest.STEP_LINE);
|
|
}
|
|
|
|
@NotNull
|
|
public ResumeCommand createStepOverCommand(SuspendContextImpl suspendContext,
|
|
boolean ignoreBreakpoints,
|
|
@Nullable MethodFilter methodFilter,
|
|
int stepSize) {
|
|
return new StepOverCommand(suspendContext, ignoreBreakpoints, methodFilter, stepSize);
|
|
}
|
|
|
|
@NotNull
|
|
public ResumeCommand createStepOutCommand(SuspendContextImpl suspendContext) {
|
|
return createStepOutCommand(suspendContext, StepRequest.STEP_LINE);
|
|
}
|
|
|
|
@NotNull
|
|
public ResumeCommand createStepOutCommand(SuspendContextImpl suspendContext, int stepSize) {
|
|
return new StepOutCommand(suspendContext, stepSize);
|
|
}
|
|
|
|
@NotNull
|
|
public ResumeCommand createStepIntoCommand(SuspendContextImpl suspendContext, boolean ignoreFilters, final MethodFilter smartStepFilter) {
|
|
return createStepIntoCommand(suspendContext, ignoreFilters, smartStepFilter, StepRequest.STEP_LINE);
|
|
}
|
|
|
|
@NotNull
|
|
public ResumeCommand createStepIntoCommand(SuspendContextImpl suspendContext, boolean ignoreFilters, final MethodFilter smartStepFilter,
|
|
int stepSize) {
|
|
return new StepIntoCommand(suspendContext, ignoreFilters, smartStepFilter, stepSize);
|
|
}
|
|
|
|
@NotNull
|
|
public ResumeCommand createRunToCursorCommand(SuspendContextImpl suspendContext,
|
|
@NotNull XSourcePosition position,
|
|
boolean ignoreBreakpoints)
|
|
throws EvaluateException {
|
|
RunToCursorCommand runToCursorCommand = new RunToCursorCommand(suspendContext, position, ignoreBreakpoints);
|
|
if (runToCursorCommand.myRunToCursorBreakpoint == null) {
|
|
PsiFile psiFile = PsiManager.getInstance(myProject).findFile(position.getFile());
|
|
throw new EvaluateException(
|
|
JavaDebuggerBundle.message("error.running.to.cursor.no.executable.code", psiFile != null ? psiFile.getName() : "<No File>",
|
|
position.getLine()), null);
|
|
}
|
|
return runToCursorCommand;
|
|
}
|
|
|
|
@NotNull
|
|
public DebuggerCommandImpl createFreezeThreadCommand(ThreadReferenceProxyImpl thread) {
|
|
return new FreezeThreadCommand(thread);
|
|
}
|
|
|
|
@NotNull
|
|
public SuspendContextCommandImpl createResumeThreadCommand(SuspendContextImpl suspendContext, @NotNull ThreadReferenceProxyImpl thread) {
|
|
return new ResumeThreadCommand(suspendContext, thread);
|
|
}
|
|
|
|
@NotNull
|
|
public SuspendContextCommandImpl createPopFrameCommand(DebuggerContextImpl context, StackFrameProxyImpl stackFrame) {
|
|
return new PopFrameCommand(context, stackFrame);
|
|
}
|
|
|
|
//public void setBreakpointsMuted(final boolean muted) {
|
|
// XDebugSession session = mySession.getXDebugSession();
|
|
// if (isAttached()) {
|
|
// getManagerThread().schedule(new DebuggerCommandImpl() {
|
|
// @Override
|
|
// protected void action() {
|
|
// // set the flag before enabling/disabling cause it affects if breakpoints will create requests
|
|
// if (myBreakpointsMuted.getAndSet(muted) != muted) {
|
|
// final BreakpointManager breakpointManager = DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager();
|
|
// if (muted) {
|
|
// breakpointManager.disableBreakpoints(DebugProcessImpl.this);
|
|
// }
|
|
// else {
|
|
// breakpointManager.enableBreakpoints(DebugProcessImpl.this);
|
|
// }
|
|
// }
|
|
// }
|
|
// });
|
|
// }
|
|
// else {
|
|
// session.setBreakpointMuted(muted);
|
|
// }
|
|
//}
|
|
|
|
@NotNull
|
|
public DebuggerContextImpl getDebuggerContext() {
|
|
return mySession.getContextManager().getContext();
|
|
}
|
|
|
|
public void setXDebugProcess(JavaDebugProcess XDebugProcess) {
|
|
myXDebugProcess = XDebugProcess;
|
|
}
|
|
|
|
@Nullable
|
|
public JavaDebugProcess getXdebugProcess() {
|
|
return myXDebugProcess;
|
|
}
|
|
|
|
public boolean areBreakpointsMuted() {
|
|
XDebugSession session = mySession.getXDebugSession();
|
|
return session != null && session.areBreakpointsMuted();
|
|
}
|
|
|
|
public DebuggerSession getSession() {
|
|
return mySession;
|
|
}
|
|
|
|
static boolean isResumeOnlyCurrentThread() {
|
|
return DebuggerSettings.getInstance().RESUME_ONLY_CURRENT_THREAD;
|
|
}
|
|
|
|
public boolean isEvaluationPossible() {
|
|
return getSuspendManager().getPausedContext() != null;
|
|
}
|
|
|
|
public boolean isEvaluationPossible(SuspendContextImpl suspendContext) {
|
|
return mySuspendManager.hasPausedContext(suspendContext);
|
|
}
|
|
|
|
public void startWatchingMethodReturn(ThreadReferenceProxyImpl thread) {
|
|
if (myReturnValueWatcher != null) {
|
|
myReturnValueWatcher.enable(thread.getThreadReference());
|
|
}
|
|
}
|
|
|
|
void stopWatchingMethodReturn() {
|
|
if (myReturnValueWatcher != null) {
|
|
myReturnValueWatcher.disable();
|
|
}
|
|
}
|
|
|
|
private record VirtualMachineData(VirtualMachineProxyImpl vm, RemoteConnection connection,
|
|
DebuggerManagerThreadImpl debuggerManagerThread) {
|
|
}
|
|
}
|