blob: 74503725ebd66cda7bd86d04910b2b9c0d540c2a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.debugger.jpda.truffle.access;
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.ClassType;
import com.sun.jdi.InvocationException;
import com.sun.jdi.StringReference;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException;
import java.io.InvalidObjectException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.locks.Lock;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.api.debugger.DebuggerManager;
import org.netbeans.api.debugger.jpda.CallStackFrame;
import org.netbeans.api.debugger.jpda.Field;
import org.netbeans.api.debugger.jpda.InvalidExpressionException;
import org.netbeans.api.debugger.jpda.JPDABreakpoint;
import org.netbeans.api.debugger.jpda.JPDAClassType;
import org.netbeans.api.debugger.jpda.JPDADebugger;
import org.netbeans.api.debugger.jpda.JPDAThread;
import org.netbeans.api.debugger.jpda.LocalVariable;
import org.netbeans.api.debugger.jpda.MethodBreakpoint;
import org.netbeans.api.debugger.jpda.ObjectVariable;
import org.netbeans.api.debugger.jpda.Variable;
import org.netbeans.api.debugger.jpda.event.JPDABreakpointEvent;
import org.netbeans.api.debugger.jpda.event.JPDABreakpointListener;
import org.netbeans.modules.debugger.jpda.expr.InvocationExceptionTranslated;
import org.netbeans.modules.debugger.jpda.expr.JDIVariable;
import org.netbeans.modules.debugger.jpda.models.JPDAClassTypeImpl;
import org.netbeans.modules.debugger.jpda.models.JPDAThreadImpl;
import org.netbeans.modules.debugger.jpda.truffle.LanguageName;
import org.netbeans.modules.debugger.jpda.truffle.RemoteServices;
import org.netbeans.modules.debugger.jpda.truffle.TruffleDebugManager;
import org.netbeans.modules.debugger.jpda.truffle.actions.StepActionProvider;
import org.netbeans.modules.debugger.jpda.truffle.ast.TruffleNode;
import org.netbeans.modules.debugger.jpda.truffle.frames.TruffleStackFrame;
import org.netbeans.modules.debugger.jpda.truffle.frames.TruffleStackInfo;
import org.netbeans.modules.debugger.jpda.truffle.source.Source;
import org.netbeans.modules.debugger.jpda.truffle.source.SourcePosition;
import org.netbeans.modules.debugger.jpda.truffle.vars.TruffleScope;
import org.netbeans.modules.debugger.jpda.truffle.vars.TruffleStackVariable;
import org.netbeans.modules.debugger.jpda.truffle.vars.TruffleVariable;
import org.netbeans.modules.debugger.jpda.util.WeakHashMapActive;
import org.openide.util.Exceptions;
/**
* Access to the backend <code>JPDATruffleAccessor</code> class.
*/
public class TruffleAccess implements JPDABreakpointListener {
private static final Logger LOG = Logger.getLogger(TruffleAccess.class.getName());
public static final String BASIC_CLASS_NAME = "org.netbeans.modules.debugger.jpda.backend.truffle.JPDATruffleAccessor"; // NOI18N
private static final String METHOD_EXEC_HALTED = "executionHalted"; // NOI18N
private static final String METHOD_EXEC_STEP_INTO = "executionStepInto"; // NOI18N
private static final String METHOD_DEBUGGER_ACCESS = "debuggerAccess"; // NOI18N
private static final String VAR_NODE = "astNode"; // NOI18N
private static final String VAR_FRAME = "frame"; // NOI18N
private static final String VAR_SRC_ID = "id"; // NOI18N
private static final String VAR_SRC_URI = "uri"; // NOI18N
private static final String VAR_SRC_NAME = "name"; // NOI18N
private static final String VAR_SRC_PATH = "path"; // NOI18N
private static final String VAR_SRC_SOURCESECTION = "sourceSection"; // NOI18N
private static final String VAR_SRC_CODE = "code";
private static final String VAR_STACK_TRACE = "stackTrace";
private static final String VAR_TOP_FRAME = "topFrame"; // NOI18N
private static final String VAR_TOP_VARS = "topVariables"; // NOI18N
private static final String VAR_THIS_OBJECT = "thisObject"; // NOI18N
private static final String METHOD_GET_VARIABLES = "getVariables"; // NOI18N
private static final String METHOD_GET_VARIABLES_SGN = "(Lcom/oracle/truffle/api/debug/DebugStackFrame;)[Ljava/lang/Object;"; // NOI18N
private static final String METHOD_GET_SCOPE_VARIABLES = "getScopeVariables";// NOI18N
private static final String METHOD_GET_SCOPE_VARIABLES_SGN = "(Lcom/oracle/truffle/api/debug/DebugScope;)[Ljava/lang/Object;"; // NOI18N
private static final String METHOD_SET_UNWIND = "setUnwind";// NOI18N
private static final String METHOD_SET_UNWIND_SGN = "(I)Z"; // NOI18N
private static final String METHOD_GET_AST = "getTruffleAST"; // NOI18N
private static final String METHOD_GET_AST_SGN = "(I)[Ljava/lang/Object;";
private static final Map<JPDAThread, ThreadInfo> currentPCInfos = new WeakHashMap<>();
private static final PropertyChangeListener threadResumeListener = new ThreadResumeListener();
private static final TruffleAccess DEFAULT = new TruffleAccess();
private final Map<JPDADebugger, JPDABreakpoint> execHaltedBP = new WeakHashMapActive<>();
private final Map<JPDADebugger, JPDABreakpoint> execStepIntoBP = new WeakHashMapActive<>();
private final Map<JPDADebugger, JPDABreakpoint> dbgAccessBP = new WeakHashMapActive<>();
private final Object methodCallAccessLock = new Object();//new ReentrantReadWriteLock(true).writeLock();
private MethodCallsAccess methodCallsRunnable;
private static final MethodCallsAccess METHOD_CALLS_SUCCESSFUL = new MethodCallsAccess(){@Override public void callMethods(JPDAThread thread) {}};
private TruffleAccess() {}
public static void init() {
DEFAULT.initBPs();
}
public static void assureBPSet(JPDADebugger debugger, ClassType accessorClass) {
DEFAULT.execHaltedBP.put(debugger, DEFAULT.createBP(accessorClass.name(), METHOD_EXEC_HALTED, debugger));
DEFAULT.execStepIntoBP.put(debugger, DEFAULT.createBP(accessorClass.name(), METHOD_EXEC_STEP_INTO, debugger));
DEFAULT.dbgAccessBP.put(debugger, DEFAULT.createBP(accessorClass.name(), METHOD_DEBUGGER_ACCESS, debugger));
}
private void initBPs() {
// Init debugger session-independent breakpoints
}
private JPDABreakpoint createBP(String className, String methodName, JPDADebugger debugger) {
final MethodBreakpoint mb = MethodBreakpoint.create(className, methodName);
mb.setBreakpointType(MethodBreakpoint.TYPE_METHOD_ENTRY);
mb.setHidden(true);
mb.setSession(debugger);
mb.addJPDABreakpointListener(this);
DebuggerManager.getDebuggerManager().addBreakpoint(mb);
return mb;
}
public static CurrentPCInfo getCurrentPCInfo(JPDAThread thread) {
ThreadInfo info;
synchronized (currentPCInfos) {
info = currentPCInfos.get(thread);
}
if (info != null) {
return info.cpi;
} else {
return null;
}
}
private static CurrentPCInfo getSomePCInfo(JPDADebugger dbg) {
synchronized (currentPCInfos) {
for (Map.Entry<JPDAThread, ThreadInfo> pce : currentPCInfos.entrySet()) {
if (((JPDAThreadImpl) pce.getKey()).getDebugger() == dbg) {
CurrentPCInfo cpi = pce.getValue().cpi;
if (cpi != null) {
return cpi;
}
}
}
}
return null;
}
@Override
public void breakpointReached(JPDABreakpointEvent event) {
Object bp = event.getSource();
JPDADebugger debugger = event.getDebugger();
if (execHaltedBP.get(debugger) == bp) {
LOG.log(Level.FINE, "TruffleAccessBreakpoints.breakpointReached({0}), exec halted.", event);
StepActionProvider.killJavaStep(debugger);
setCurrentPosition(debugger, event.getThread());
} else if (execStepIntoBP.get(debugger) == bp) {
LOG.log(Level.FINE, "TruffleAccessBreakpoints.breakpointReached({0}), exec step into.", event);
StepActionProvider.killJavaStep(debugger);
setCurrentPosition(debugger, event.getThread());
} else if (dbgAccessBP.get(debugger) == bp) {
LOG.log(Level.FINE, "TruffleAccessBreakpoints.breakpointReached({0}), debugger access.", event);
try {
synchronized (methodCallAccessLock) {
if (methodCallsRunnable != null) {
invokeMethodCalls(event.getThread(), methodCallsRunnable);
}
methodCallsRunnable = METHOD_CALLS_SUCCESSFUL;
methodCallAccessLock.notifyAll();
}
} finally {
event.resume();
}
}
}
private void setCurrentPosition(JPDADebugger debugger, JPDAThread thread) {
CurrentPCInfo cpci = getCurrentPosition(debugger, thread);
synchronized (currentPCInfos) {
ThreadInfo info = currentPCInfos.get(thread);
if (info == null) {
((JPDAThreadImpl) thread).addPropertyChangeListener(JPDAThreadImpl.PROP_SUSPENDED, threadResumeListener);
info = new ThreadInfo();
currentPCInfos.put(thread, info);
}
info.cpi = cpci;
}
}
private CurrentPCInfo getCurrentPosition(JPDADebugger debugger, JPDAThread thread) {
try {
CallStackFrame csf = thread.getCallStack(0, 1)[0];
LocalVariable[] localVariables = csf.getLocalVariables();
ExecutionHaltedInfo haltedInfo = ExecutionHaltedInfo.get(localVariables);
//JPDAClassType debugAccessor = TruffleDebugManager.getDebugAccessorJPDAClass(debugger);
ObjectVariable sourcePositionVar = haltedInfo.sourcePositions;
SourcePosition sp = getSourcePosition(debugger, sourcePositionVar);
ObjectVariable frameInfoVar = haltedInfo.frameInfo;
ObjectVariable frame = (ObjectVariable) frameInfoVar.getField(VAR_FRAME);
ObjectVariable topVars = (ObjectVariable) frameInfoVar.getField(VAR_TOP_VARS);
TruffleScope[] scopes = createScopes(debugger, topVars);
ObjectVariable stackTrace = (ObjectVariable) frameInfoVar.getField(VAR_STACK_TRACE);
String topFrameDescription = (String) frameInfoVar.getField(VAR_TOP_FRAME).createMirrorObject();
ObjectVariable thisObject = null;// TODO: (ObjectVariable) frameInfoVar.getField("thisObject");
TruffleStackFrame topFrame = new TruffleStackFrame(debugger, thread, 0, frame, topFrameDescription, null/*code*/, scopes, thisObject, true);
TruffleStackInfo stack = new TruffleStackInfo(debugger, thread, stackTrace);
return new CurrentPCInfo(haltedInfo.stepCmd, thread, sp, scopes, topFrame, stack, depth -> {
return getTruffleAST(debugger, (JPDAThreadImpl) thread, depth, sp, stack);
});
} catch (AbsentInformationException | IllegalStateException ex) {
Exceptions.printStackTrace(ex);
return null;
}
}
private static TruffleNode getTruffleAST(JPDADebugger debugger, JPDAThreadImpl thread, int depth, SourcePosition topPosition, TruffleStackInfo stack) {
JPDAClassType debugAccessor = TruffleDebugManager.getDebugAccessorJPDAClass(debugger);
Lock lock = thread.accessLock.writeLock();
lock.lock();
try {
if (thread.getState() == JPDAThread.STATE_ZOMBIE) {
return null;
}
final boolean[] suspended = new boolean[] { false };
PropertyChangeListener threadChange = (event) -> {
synchronized (suspended) {
suspended[0] = (Boolean) event.getNewValue();
suspended.notifyAll();
}
};
thread.addPropertyChangeListener(JPDAThreadImpl.PROP_SUSPENDED, threadChange);
while (!thread.isSuspended()) {
lock.unlock();
lock = null;
synchronized (suspended) {
if (!suspended[0]) {
try {
suspended.wait();
} catch (InterruptedException ex) {}
}
}
lock = thread.accessLock.writeLock();
lock.lock();
}
thread.removePropertyChangeListener(JPDAThreadImpl.PROP_SUSPENDED, threadChange);
Variable ast = ((JPDAClassTypeImpl) debugAccessor).invokeMethod(thread, METHOD_GET_AST,
METHOD_GET_AST_SGN,
new Variable[] { debugger.createMirrorVar(depth, true) });
Variable[] astInfo = ((ObjectVariable) ast).getFields(0, Integer.MAX_VALUE);
SourcePosition position;
if (depth == 0) {
position = topPosition;
} else {
TruffleStackFrame[] stackFrames = stack.getStackFrames(true);
if (depth >= stackFrames.length) {
return null;
}
position = stackFrames[depth].getSourcePosition();
}
return TruffleNode.newBuilder().nodes((String) astInfo[0].createMirrorObject()).currentPosition(position).build();
} catch (InvalidExpressionException | InvalidObjectException | NoSuchMethodException ex) {
Exceptions.printStackTrace(ex);
return null;
} finally {
if (lock != null) {
lock.unlock();
}
}
}
public static SourcePosition getSourcePosition(JPDADebugger debugger, ObjectVariable sourcePositionVar) {
Field varSrcId = sourcePositionVar.getField(VAR_SRC_ID);
if (varSrcId == null) {
// sourcePositionVar represents null
return null;
}
long id = (Long) varSrcId.createMirrorObject();
String sourceSection = (String) sourcePositionVar.getField(VAR_SRC_SOURCESECTION).createMirrorObject();
Source src = Source.getExistingSource(debugger, id);
if (src == null) {
String name = (String) sourcePositionVar.getField(VAR_SRC_NAME).createMirrorObject();
String path = (String) sourcePositionVar.getField(VAR_SRC_PATH).createMirrorObject();
URI uri = (URI) sourcePositionVar.getField(VAR_SRC_URI).createMirrorObject();
StringReference codeRef = (StringReference) ((JDIVariable) sourcePositionVar.getField(VAR_SRC_CODE)).getJDIValue();
src = Source.getSource(debugger, id, name, path, uri, codeRef);
}
return new SourcePosition(debugger, id, src, sourceSection);
}
private static TruffleScope[] createScopes(JPDADebugger debugger, ObjectVariable varsArrVar) {
Field[] varsArr = varsArrVar.getFields(0, Integer.MAX_VALUE);
List<TruffleScope> scopes = new LinkedList<>();
int n = varsArr.length;
int i = 0;
if (i < n) {
String scopeName = (String) varsArr[i++].createMirrorObject();
boolean scopeFunction = (Boolean) varsArr[i++].createMirrorObject();
int numArgs = (Integer) varsArr[i++].createMirrorObject();
int numVars = (Integer) varsArr[i++].createMirrorObject();
TruffleVariable[] arguments = new TruffleVariable[numArgs];
i = fillVars(debugger, arguments, varsArr, i);
TruffleVariable[] variables = new TruffleVariable[numVars];
i = fillVars(debugger, variables, varsArr, i);
scopes.add(new TruffleScope(scopeName, scopeFunction, arguments, variables));
}
while (i < n) {
// There are further scopes, retrieved lazily
String scopeName = (String) varsArr[i++].createMirrorObject();
boolean scopeFunction = (Boolean) varsArr[i++].createMirrorObject();
boolean hasArgs = (Boolean) varsArr[i++].createMirrorObject();
boolean hasVars = (Boolean) varsArr[i++].createMirrorObject();
ObjectVariable scope = (ObjectVariable) varsArr[i++];
scopes.add(new TruffleScope(scopeName, scopeFunction, hasArgs, hasVars, debugger, scope));
}
return scopes.toArray(new TruffleScope[scopes.size()]);
}
private static int fillVars(JPDADebugger debugger, TruffleVariable[] vars, Field[] varsArr, int i) {
for (int vi = 0; vi < vars.length; vi++) {
String name = (String) varsArr[i++].createMirrorObject();
LanguageName language = LanguageName.parse((String) varsArr[i++].createMirrorObject());
String type = (String) varsArr[i++].createMirrorObject();
boolean readable = (Boolean) varsArr[i++].createMirrorObject();
boolean writable = (Boolean) varsArr[i++].createMirrorObject();
boolean internal = (Boolean) varsArr[i++].createMirrorObject();
String valueStr = (String) varsArr[i++].createMirrorObject();
ObjectVariable valueSourceDef = (ObjectVariable) varsArr[i++];
Supplier<SourcePosition> valueSource = parseSourceLazy(debugger,
valueSourceDef,
(JDIVariable) varsArr[i++]);
ObjectVariable typeSourceDef = (ObjectVariable) varsArr[i++];
Supplier<SourcePosition> typeSource = parseSourceLazy(debugger,
typeSourceDef,
(JDIVariable) varsArr[i++]);
ObjectVariable value = (ObjectVariable) varsArr[i++];
vars[vi] = new TruffleStackVariable(debugger, name, language, type, readable,
writable, internal, valueStr,
valueSourceDef.getUniqueID() != 0L,
valueSource,
typeSourceDef.getUniqueID() != 0L,
typeSource, value);
}
return i;
}
public static TruffleVariable[][] getScopeArgsAndVars(JPDADebugger debugger, ObjectVariable debugScope) {
JPDAClassType debugAccessor = TruffleDebugManager.getDebugAccessorJPDAClass(debugger);
try {
Variable scopeVars = debugAccessor.invokeMethod(METHOD_GET_SCOPE_VARIABLES,
METHOD_GET_SCOPE_VARIABLES_SGN,
new Variable[] { debugScope });
Field[] varsArr = ((ObjectVariable) scopeVars).getFields(0, Integer.MAX_VALUE);
int n = varsArr.length;
int i = 0;
if (i < n) {
int numArgs = (Integer) varsArr[i++].createMirrorObject();
int numVars = (Integer) varsArr[i++].createMirrorObject();
TruffleVariable[] arguments = new TruffleVariable[numArgs];
i = fillVars(debugger, arguments, varsArr, i);
TruffleVariable[] variables = new TruffleVariable[numVars];
i = fillVars(debugger, variables, varsArr, i);
return new TruffleVariable[][] { arguments, variables };
}
} catch (InvalidExpressionException | NoSuchMethodException ex) {
Exceptions.printStackTrace(ex);
}
return new TruffleVariable[][] { new TruffleVariable[] {}, new TruffleVariable[] {} };
}
private static Supplier<SourcePosition> parseSourceLazy(JPDADebugger debugger, Variable sourceDefVar, JDIVariable codeRefVar) {
return () -> parseSource(debugger,
(String) sourceDefVar.createMirrorObject(),
(StringReference) codeRefVar.getJDIValue());
}
private static SourcePosition parseSource(JPDADebugger debugger, String sourceDef, StringReference codeRef) {
if (sourceDef == null) {
return null;
}
int sourceId;
String sourceName;
String sourcePath;
URI sourceURI;
String sourceSection;
try {
int i1 = 0;
int i2 = sourceDef.indexOf('\n', i1);
sourceId = Integer.parseInt(sourceDef.substring(i1, i2));
i1 = i2 + 1;
i2 = sourceDef.indexOf('\n', i1);
sourceName = sourceDef.substring(i1, i2);
i1 = i2 + 1;
i2 = sourceDef.indexOf('\n', i1);
sourcePath = sourceDef.substring(i1, i2);
i1 = i2 + 1;
i2 = sourceDef.indexOf('\n', i1);
try {
sourceURI = new URI(sourceDef.substring(i1, i2));
} catch (URISyntaxException usex) {
throw new IllegalStateException("Bad URI: "+sourceDef.substring(i1, i2), usex);
}
i1 = i2 + 1;
i2 = sourceDef.indexOf('\n', i1);
if (i2 < 0) {
i2 = sourceDef.length();
}
sourceSection = sourceDef.substring(i1, i2);
} catch (IndexOutOfBoundsException ioob) {
throw new IllegalStateException("var source definition='"+sourceDef+"'", ioob);
}
Source src = Source.getSource(debugger, sourceId, sourceName, sourcePath, sourceURI, codeRef);
return new SourcePosition(debugger, sourceId, src, sourceSection);
}
public static TruffleScope[] createFrameScopes(final JPDADebugger debugger,
//final Variable suspendedInfo,
final Variable frameInstance) {
JPDAClassType debugAccessor = TruffleDebugManager.getDebugAccessorJPDAClass(debugger);
try {
Variable frameVars = debugAccessor.invokeMethod(METHOD_GET_VARIABLES,
METHOD_GET_VARIABLES_SGN,
new Variable[] { frameInstance });
TruffleScope[] scopes = createScopes(debugger, (ObjectVariable) frameVars);
return scopes;
} catch (InvalidExpressionException | NoSuchMethodException ex) {
Exceptions.printStackTrace(ex);
return new TruffleScope[] {};
}
}
public static boolean unwind(JPDADebugger debugger, JPDAThread thread, int depth) {
JPDAClassType debugAccessor = TruffleDebugManager.getDebugAccessorJPDAClass(debugger);
try {
Variable arg = debugger.createMirrorVar(depth);
Variable res = ((JPDAClassTypeImpl) debugAccessor).invokeMethod(thread, METHOD_SET_UNWIND, METHOD_SET_UNWIND_SGN, new Variable[] { arg });
return (Boolean) res.createMirrorObject();
} catch (InvalidExpressionException | InvalidObjectException | NoSuchMethodException ex) {
Exceptions.printStackTrace(ex);
return false;
}
}
/**
* Safe access to method calls in the backend accessor class.
* @param methodCalls The runnable, that is called under write lock on the current thread.
* @return <code>true</code> when the runnable with method calls is executed,
* <code>false</code> when method execution is not possible.
*/
public static boolean methodCallingAccess(final JPDADebugger debugger, MethodCallsAccess methodCalls) {
synchronized (DEFAULT.methodCallAccessLock) {
while (DEFAULT.methodCallsRunnable != null) {
// we're already processing some method calls...
try {
DEFAULT.methodCallAccessLock.wait();
} catch (InterruptedException ex) {
return false;
}
}
CurrentPCInfo currentPCInfo = getSomePCInfo(debugger);
if (currentPCInfo != null) {
JPDAThread thread = currentPCInfo.getThread();
if (thread != null) {
boolean success = invokeMethodCalls(thread, methodCalls);
if (success) {
return true;
}
}
}
// Was not able to invoke methods
boolean interrupted = RemoteServices.interruptServiceAccessThread(debugger);
if (!interrupted) {
return false;
}
PropertyChangeListener finishListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (JPDADebugger.STATE_DISCONNECTED == debugger.getState()) {
synchronized (DEFAULT.methodCallAccessLock) {
DEFAULT.methodCallAccessLock.notifyAll();
}
}
}
};
debugger.addPropertyChangeListener(JPDADebugger.PROP_STATE, finishListener);
DEFAULT.methodCallsRunnable = methodCalls;
try {
DEFAULT.methodCallAccessLock.wait();
} catch (InterruptedException ex) {
return false;
} finally {
debugger.removePropertyChangeListener(JPDADebugger.PROP_STATE, finishListener);
}
boolean success = (DEFAULT.methodCallsRunnable == METHOD_CALLS_SUCCESSFUL);
DEFAULT.methodCallsRunnable = null;
return success;
}
}
private static boolean invokeMethodCalls(JPDAThread thread, MethodCallsAccess methodCalls) {
assert Thread.holdsLock(DEFAULT.methodCallAccessLock);
boolean invoking = false;
InvocationException iex = null;
try {
((JPDAThreadImpl) thread).notifyMethodInvoking();
invoking = true;
methodCalls.callMethods(thread);
return true;
} catch (PropertyVetoException pvex) {
return false;
} catch (InvocationException ex) {
iex = ex;
} finally {
if (invoking) {
((JPDAThreadImpl) thread).notifyMethodInvokeDone();
}
}
if (iex != null) {
Throwable ex = new InvocationExceptionTranslated(iex, ((JPDAThreadImpl) thread).getDebugger()).preload((JPDAThreadImpl) thread);
Exceptions.printStackTrace(Exceptions.attachMessage(ex, "Invoking "+methodCalls));
}
return false;
}
public static interface MethodCallsAccess {
void callMethods(JPDAThread thread) throws InvocationException;
}
private static final class ThreadInfo {
volatile CurrentPCInfo cpi;
}
private static final class ThreadResumeListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
JPDAThreadImpl t = (JPDAThreadImpl) evt.getSource();
if (!(Boolean) evt.getNewValue() && !t.isMethodInvoking()) { // not suspended, resumed
synchronized (currentPCInfos) {
ThreadInfo info = currentPCInfos.get(t);
if (info != null) {
info.cpi = null;
}
}
}
}
}
}