| /* |
| * 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.Method; |
| import com.sun.jdi.ObjectReference; |
| import com.sun.jdi.StringReference; |
| import com.sun.jdi.ThreadReference; |
| import com.sun.jdi.Value; |
| import com.sun.jdi.event.ClassPrepareEvent; |
| import com.sun.jdi.event.Event; |
| import com.sun.jdi.event.EventSet; |
| import com.sun.jdi.event.LocatableEvent; |
| import com.sun.jdi.request.EventRequest; |
| 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.Arrays; |
| import java.util.Collections; |
| 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.Function; |
| 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.LineBreakpoint; |
| 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.JPDADebuggerImpl; |
| |
| import org.netbeans.modules.debugger.jpda.expr.InvocationExceptionTranslated; |
| import org.netbeans.modules.debugger.jpda.expr.JDIVariable; |
| import org.netbeans.modules.debugger.jpda.jdi.ClassTypeWrapper; |
| import org.netbeans.modules.debugger.jpda.jdi.IllegalThreadStateExceptionWrapper; |
| import org.netbeans.modules.debugger.jpda.jdi.InternalExceptionWrapper; |
| import org.netbeans.modules.debugger.jpda.jdi.LocatableWrapper; |
| import org.netbeans.modules.debugger.jpda.jdi.LocationWrapper; |
| import org.netbeans.modules.debugger.jpda.jdi.ObjectCollectedExceptionWrapper; |
| import org.netbeans.modules.debugger.jpda.jdi.ThreadReferenceWrapper; |
| import org.netbeans.modules.debugger.jpda.jdi.VMDisconnectedExceptionWrapper; |
| import org.netbeans.modules.debugger.jpda.jdi.event.ClassPrepareEventWrapper; |
| import org.netbeans.modules.debugger.jpda.jdi.event.EventSetWrapper; |
| import org.netbeans.modules.debugger.jpda.jdi.event.EventWrapper; |
| import org.netbeans.modules.debugger.jpda.jdi.event.LocatableEventWrapper; |
| import org.netbeans.modules.debugger.jpda.jdi.request.EventRequestWrapper; |
| 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.Utils; |
| 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.breakpoints.impl.HitBreakpointInfo; |
| import org.netbeans.modules.debugger.jpda.truffle.breakpoints.impl.TruffleBreakpointOutput; |
| 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.impl.TruffleScope; |
| import org.netbeans.modules.debugger.jpda.truffle.vars.impl.TruffleStackVariable; |
| import org.netbeans.modules.debugger.jpda.truffle.vars.TruffleVariable; |
| import org.netbeans.modules.debugger.jpda.util.Executor; |
| 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_MIMETYPE = "mimeType"; // NOI18N |
| private static final String VAR_SRC_NAME = "name"; // NOI18N |
| private static final String VAR_SRC_HOST_METHOD = "hostMethodName"; // 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_SUSPEND_HERE = "suspendHere"; // NOI18N |
| private static final String METHOD_SUSPEND_HERE_SGN = "()[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 Map<JPDAThread, ThreadInfo> suspendHerePCInfos = 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) { |
| CurrentPCInfo cpi = getCurrentGuestPCInfo(thread); |
| if (cpi == null) { |
| cpi = getCurrentSuspendHereInfo(thread); |
| } |
| return cpi; |
| } |
| |
| public static CurrentPCInfo getCurrentGuestPCInfo(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; |
| } |
| |
| public static CurrentPCInfo getCurrentSuspendHereInfo(JPDAThread thread) { |
| synchronized (suspendHerePCInfos) { |
| ThreadInfo info = suspendHerePCInfos.get(thread); |
| if (info != null) { |
| return info.cpi; |
| } |
| } |
| return null; |
| } |
| |
| public static CurrentPCInfo getSuspendHere(JPDAThread thread) { |
| if (getCurrentGuestPCInfo(thread) != null) { |
| // Suspended in the guest language already. |
| return null; |
| } |
| ThreadInfo info; |
| synchronized (suspendHerePCInfos) { |
| info = suspendHerePCInfos.get(thread); |
| if (info != null) { |
| if (info.cpi != null) { |
| return info.cpi; |
| } else if (info.noCpi) { |
| return null; |
| } |
| } |
| if (info == null) { |
| ((JPDAThreadImpl) thread).addPropertyChangeListener(JPDAThreadImpl.PROP_SUSPENDED, threadResumeListener); |
| info = new ThreadInfo(); |
| suspendHerePCInfos.put(thread, info); |
| } |
| } |
| synchronized (info) { |
| if (info.cpi != null) { |
| return info.cpi; |
| } |
| if (info.noCpi) { |
| return null; |
| } |
| JPDADebugger debugger = ((JPDAThreadImpl) thread).getDebugger(); |
| ClassType debugAccessorClass = TruffleDebugManager.getDebugAccessorClass(debugger); |
| if (debugAccessorClass == null) { |
| info.noCpi = true; |
| return null; |
| } |
| ObjectVariable haltInfo = invokeSuspendHere(debugAccessorClass, thread); |
| if (haltInfo == null) { |
| info.noCpi = true; |
| return null; |
| } |
| CurrentPCInfo cpi = getCurrentPosition(debugger, thread, ((ObjectVariable) haltInfo).getFields(0, Integer.MAX_VALUE)); |
| cpi.setSelectedStackFrame(null); |
| info.cpi = cpi; |
| return cpi; |
| } |
| } |
| |
| private static ObjectVariable invokeSuspendHere(ClassType debugAccessorClass, JPDAThread thread) { |
| JPDAThreadImpl threadImpl = (JPDAThreadImpl) thread; |
| ThreadReference tr = threadImpl.getThreadReference(); |
| try { |
| Method suspendHereMethod = ClassTypeWrapper.concreteMethodByName(debugAccessorClass, METHOD_SUSPEND_HERE, METHOD_SUSPEND_HERE_SGN); |
| JPDADebuggerImpl debugger = threadImpl.getDebugger(); |
| Value haltInfo; |
| threadImpl.notifyMethodInvoking(); |
| Runnable cleanup = null; |
| try { |
| cleanup = skipSuspendedEventClearLeakingReferences(debugger, thread); |
| haltInfo = ClassTypeWrapper.invokeMethod(debugAccessorClass, tr, suspendHereMethod, Collections.emptyList(), ObjectReference.INVOKE_SINGLE_THREADED); |
| } finally { |
| try { |
| cleanup.run(); |
| } finally { |
| threadImpl.notifyMethodInvokeDone(); |
| } |
| } |
| if (haltInfo instanceof ObjectReference) { |
| return (ObjectVariable) debugger.getVariable(haltInfo); |
| } |
| } catch (InvocationException ex) { |
| Exceptions.printStackTrace(ex); |
| } catch (Exception ex) { |
| LOG.log(Level.CONFIG, "Invoking " + METHOD_SUSPEND_HERE, ex); |
| } |
| return null; |
| } |
| |
| private static final String CLEAR_REFERENCES_CLASS = "com.oracle.truffle.api.debug.SuspendedEvent"; |
| private static final String CLEAR_REFERENCES_METHOD = "clearLeakingReferences"; |
| |
| private static Runnable skipSuspendedEventClearLeakingReferences(JPDADebugger debugger, JPDAThread thread) { |
| ThreadReference tr = ((JPDAThreadImpl) thread).getThreadReference(); |
| MethodBreakpoint clearLeakingReferencesBreakpoint = MethodBreakpoint.create(CLEAR_REFERENCES_CLASS, CLEAR_REFERENCES_METHOD); |
| clearLeakingReferencesBreakpoint.setBreakpointType(MethodBreakpoint.TYPE_METHOD_ENTRY); |
| clearLeakingReferencesBreakpoint.setThreadFilters(debugger, new JPDAThread[] { thread }); |
| clearLeakingReferencesBreakpoint.setHidden(true); |
| Function<EventSet, Boolean> breakpointEventInterceptor = eventSet -> { |
| try { |
| ThreadReference etr = null; |
| Method method = null; |
| for (Event e: eventSet) { |
| if (e instanceof ClassPrepareEvent) { |
| etr = ClassPrepareEventWrapper.thread((ClassPrepareEvent) e); |
| } else if (e instanceof LocatableEvent) { |
| etr = LocatableEventWrapper.thread((LocatableEvent) e); |
| method = LocationWrapper.method(LocatableWrapper.location((LocatableEvent) e)); |
| } |
| } |
| if (tr.equals(etr) && method != null && CLEAR_REFERENCES_METHOD.equals(method.name()) && CLEAR_REFERENCES_CLASS.equals(method.declaringType().name())) { |
| boolean resume = true; |
| for (Event e: eventSet) { |
| EventRequest r = EventWrapper.request(e); |
| Executor exec = (r != null) ? (Executor) EventRequestWrapper.getProperty (r, "executor") : null; |
| resume = resume & exec.exec(e); |
| } |
| if (resume) { |
| try { |
| EventSetWrapper.resume(eventSet); |
| } catch (IllegalThreadStateExceptionWrapper | ObjectCollectedExceptionWrapper ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } catch (InternalExceptionWrapper | VMDisconnectedExceptionWrapper ex) { |
| return false; |
| } |
| }; |
| clearLeakingReferencesBreakpoint.addJPDABreakpointListener(event -> { |
| DebuggerManager.getDebuggerManager().removeBreakpoint(clearLeakingReferencesBreakpoint); |
| try { |
| ThreadReferenceWrapper.forceEarlyReturn(tr, tr.virtualMachine().mirrorOfVoid()); |
| } catch (Exception ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| event.resume(); |
| ((JPDADebuggerImpl) debugger).getOperator().removeEventInterceptor(breakpointEventInterceptor); |
| }); |
| ((JPDADebuggerImpl) debugger).getOperator().addEventInterceptor(breakpointEventInterceptor); |
| DebuggerManager.getDebuggerManager().addBreakpoint(clearLeakingReferencesBreakpoint); |
| return () -> { // Cleanup |
| ((JPDADebuggerImpl) debugger).getOperator().removeEventInterceptor(breakpointEventInterceptor); |
| DebuggerManager.getDebuggerManager().removeBreakpoint(clearLeakingReferencesBreakpoint); |
| }; |
| } |
| |
| @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); |
| if (!setCurrentPosition(debugger, event.getThread())) { |
| event.resume(); |
| } else { |
| StepActionProvider.killJavaStep(debugger); |
| } |
| } else if (execStepIntoBP.get(debugger) == bp) { |
| LOG.log(Level.FINE, "TruffleAccessBreakpoints.breakpointReached({0}), exec step into.", event); |
| if (!setCurrentPosition(debugger, event.getThread())) { |
| event.resume(); |
| } else { |
| StepActionProvider.killJavaStep(debugger); |
| } |
| } 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 boolean setCurrentPosition(JPDADebugger debugger, JPDAThread thread) { |
| CurrentPCInfo cpci = getCurrentPosition(debugger, thread); |
| if (cpci == null) { |
| return false; |
| } |
| 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; |
| } |
| return true; |
| } |
| |
| private static CurrentPCInfo getCurrentPosition(JPDADebugger debugger, JPDAThread thread) { |
| try { |
| CallStackFrame csf = thread.getCallStack(0, 1)[0]; |
| LocalVariable[] localVariables = csf.getLocalVariables(); |
| return getCurrentPosition(debugger, thread, localVariables); |
| } catch (AbsentInformationException | IllegalStateException ex) { |
| Exceptions.printStackTrace(ex); |
| return null; |
| } |
| } |
| |
| private static CurrentPCInfo getCurrentPosition(JPDADebugger debugger, JPDAThread thread, Variable[] haltVars) { |
| try { |
| ExecutionHaltedInfo haltedInfo = ExecutionHaltedInfo.get(haltVars); |
| 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, haltedInfo.supportsJavaFrames); |
| HitBreakpointInfo[] breakpointInfos = getBreakpointInfos(haltedInfo, thread); |
| CurrentPCInfo cpi = new CurrentPCInfo(haltedInfo.stepCmd, thread, sp, scopes, topFrame,stack, depth -> { |
| return getTruffleAST(debugger, (JPDAThreadImpl) thread, depth, sp, stack); |
| }); |
| if (breakpointInfos != null) { |
| TruffleBreakpointOutput.breakpointsHit(breakpointInfos, cpi); |
| } |
| return cpi; |
| } catch (IllegalStateException ex) { |
| Exceptions.printStackTrace(ex); |
| return null; |
| } |
| } |
| |
| private static HitBreakpointInfo[] getBreakpointInfos(ExecutionHaltedInfo haltedInfo, JPDAThread thread) { |
| ObjectVariable[] breakpointsHit = haltedInfo.breakpointsHit; |
| ObjectVariable[] breakpointConditionExceptions = haltedInfo.breakpointConditionExceptions; |
| int n = (breakpointsHit != null) ? breakpointsHit.length : 0; |
| HitBreakpointInfo[] breakpointInfos = null; |
| for (int i = 0; i < n; i++) { |
| ObjectVariable exception = (breakpointConditionExceptions != null) ? breakpointConditionExceptions[i] : null; |
| HitBreakpointInfo breakpointInfo = HitBreakpointInfo.create(breakpointsHit[i], exception); |
| if (breakpointInfo != null) { |
| if (breakpointInfos == null) { |
| breakpointInfos = new HitBreakpointInfo[] { breakpointInfo }; |
| } else { |
| // There will rarely be more than one breakpoint hit |
| breakpointInfos = Arrays.copyOf(breakpointInfos, breakpointInfos.length + 1); |
| breakpointInfos[breakpointInfos.length - 1] = breakpointInfo; |
| } |
| } |
| } |
| return breakpointInfos; |
| } |
| |
| 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(); |
| Field varSourceSection = sourcePositionVar.getField(VAR_SRC_SOURCESECTION); |
| String sourceSection = (String) varSourceSection.createMirrorObject(); |
| if (sourceSection == null) { |
| // No source section information |
| return null; |
| } |
| Source src = Source.getExistingSource(debugger, id); |
| if (src == null) { |
| String name = (String) sourcePositionVar.getField(VAR_SRC_NAME).createMirrorObject(); |
| String hostMethodName = (String) sourcePositionVar.getField(VAR_SRC_HOST_METHOD).createMirrorObject(); |
| String path = (String) sourcePositionVar.getField(VAR_SRC_PATH).createMirrorObject(); |
| URI uri = (URI) sourcePositionVar.getField(VAR_SRC_URI).createMirrorObject(); |
| String mimeType = (String) sourcePositionVar.getField(VAR_SRC_MIMETYPE).createMirrorObject(); |
| StringReference codeRef = (StringReference) ((JDIVariable) sourcePositionVar.getField(VAR_SRC_CODE)).getJDIValue(); |
| src = Source.getSource(debugger, id, name, hostMethodName, path, uri, mimeType, 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; |
| while (i < n) { |
| String scopeName = (String) varsArr[i++].createMirrorObject(); |
| boolean hasReceiver = (Boolean) varsArr[i++].createMirrorObject(); |
| int numVars = (Integer) varsArr[i++].createMirrorObject(); |
| TruffleVariable[] variables = new TruffleVariable[numVars]; |
| i = fillVars(debugger, variables, varsArr, hasReceiver, i); |
| scopes.add(new TruffleScope(scopeName, variables)); |
| } |
| return scopes.toArray(new TruffleScope[scopes.size()]); |
| } |
| |
| private static int fillVars(JPDADebugger debugger, TruffleVariable[] vars, Field[] varsArr, boolean hasReceiver, 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, hasReceiver, value); |
| hasReceiver = false; |
| } |
| return i; |
| } |
| |
| 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 hostMethodName; |
| String sourcePath; |
| URI sourceURI; |
| String mimeType; |
| 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); |
| hostMethodName = Utils.stringOrNull(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); |
| String uriStr = Utils.stringOrNull(sourceDef.substring(i1, i2)); |
| if (uriStr != null) { |
| try { |
| sourceURI = new URI(uriStr); |
| } catch (URISyntaxException usex) { |
| Exceptions.printStackTrace(new IllegalStateException("Bad URI: "+sourceDef.substring(i1, i2), usex)); |
| sourceURI = null; |
| } |
| } else { |
| sourceURI = null; |
| } |
| i1 = i2 + 1; |
| i2 = sourceDef.indexOf('\n', i1); |
| mimeType = Utils.stringOrNull(sourceDef.substring(i1, i2)); |
| 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, hostMethodName, sourcePath, sourceURI, mimeType, 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; |
| volatile boolean noCpi; |
| } |
| |
| 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) { |
| clear(currentPCInfos.get(t)); |
| } |
| synchronized (suspendHerePCInfos) { |
| clear(suspendHerePCInfos.get(t)); |
| } |
| } |
| } |
| |
| private void clear(ThreadInfo info) { |
| if (info != null) { |
| info.cpi = null; |
| info.noCpi = false; |
| } |
| } |
| } |
| } |