blob: 6f9b233e85b4cca4952510a8120dfd213603cb46 [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;
import com.sun.jdi.ClassType;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.Location;
import com.sun.jdi.Method;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.StackFrame;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.Value;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.LocatableEvent;
import com.sun.jdi.request.BreakpointRequest;
import com.sun.jdi.request.EventRequest;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.api.debugger.Breakpoint;
import org.netbeans.api.debugger.DebuggerManager;
import org.netbeans.api.debugger.DebuggerManagerAdapter;
import org.netbeans.api.debugger.LazyDebuggerManagerListener;
import org.netbeans.api.debugger.Session;
import org.netbeans.api.debugger.jpda.ClassLoadUnloadBreakpoint;
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.MethodBreakpoint;
import org.netbeans.api.debugger.jpda.ObjectVariable;
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.JDIVariable;
import org.netbeans.modules.debugger.jpda.jdi.ClassNotPreparedExceptionWrapper;
import org.netbeans.modules.debugger.jpda.jdi.IllegalThreadStateExceptionWrapper;
import org.netbeans.modules.debugger.jpda.jdi.InternalExceptionWrapper;
import org.netbeans.modules.debugger.jpda.jdi.InvalidRequestStateExceptionWrapper;
import org.netbeans.modules.debugger.jpda.jdi.InvalidStackFrameExceptionWrapper;
import org.netbeans.modules.debugger.jpda.jdi.MethodWrapper;
import org.netbeans.modules.debugger.jpda.jdi.ObjectCollectedExceptionWrapper;
import org.netbeans.modules.debugger.jpda.jdi.ReferenceTypeWrapper;
import org.netbeans.modules.debugger.jpda.jdi.StackFrameWrapper;
import org.netbeans.modules.debugger.jpda.jdi.ThreadReferenceWrapper;
import org.netbeans.modules.debugger.jpda.jdi.VMDisconnectedExceptionWrapper;
import org.netbeans.modules.debugger.jpda.jdi.event.LocatableEventWrapper;
import org.netbeans.modules.debugger.jpda.jdi.request.EventRequestManagerWrapper;
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.access.TruffleAccess;
import org.netbeans.modules.debugger.jpda.truffle.actions.PauseInGraalScriptActionProvider;
import org.netbeans.modules.debugger.jpda.util.Executor;
import org.netbeans.modules.javascript2.debug.breakpoints.JSLineBreakpoint;
import org.netbeans.spi.debugger.ActionsProvider;
import org.netbeans.spi.debugger.DebuggerServiceRegistration;
import org.openide.util.Exceptions;
/**
* Initiates guest language debugging, detects Engine in the JVM.
*/
@DebuggerServiceRegistration(types=LazyDebuggerManagerListener.class)
public class TruffleDebugManager extends DebuggerManagerAdapter {
private static final Logger LOG = Logger.getLogger(TruffleDebugManager.class.getName());
private static final String SESSION_CREATION_BP_CLASS = "org.graalvm.polyglot.Engine";
// Breakpoint on this class triggers search of existing engines
private static final String EXISTING_ENGINES_TRIGGER = "com.oracle.truffle.api.frame.FrameSlot";
private JPDABreakpoint debugManagerLoadBP;
private static final Map<JPDADebugger, Boolean> haveExistingEnginesTrigger = new WeakHashMap<>();
private static final Map<JPDADebugger, DebugManagerHandler> dmHandlers = new HashMap<>();
private static final Map<JPDADebugger, JPDABreakpointListener> debugBPListeners = new HashMap<>();
public TruffleDebugManager() {
}
@Override
public Breakpoint[] initBreakpoints() {
initLoadBP();
return new Breakpoint[] { debugManagerLoadBP };
}
private synchronized void initLoadBP() {
if (debugManagerLoadBP != null) {
return ;
}
/* Must NOT use a method exit breakpoint! It caused a massive degradation of application performance.
debugManagerLoadBP = MethodBreakpoint.create(SESSION_CREATION_BP_CLASS, SESSION_CREATION_BP_METHOD);
((MethodBreakpoint) debugManagerLoadBP).setBreakpointType(MethodBreakpoint.TYPE_METHOD_EXIT);
*/
debugManagerLoadBP = ClassLoadUnloadBreakpoint.create(SESSION_CREATION_BP_CLASS, false, ClassLoadUnloadBreakpoint.TYPE_CLASS_LOADED);
debugManagerLoadBP.setHidden(true);
LOG.log(Level.FINE, "TruffleDebugManager.initBreakpoints(): submitted BP {0}", debugManagerLoadBP);
TruffleAccess.init();
}
@Override
public void sessionAdded(Session session) {
JPDADebugger debugger = session.lookupFirst(null, JPDADebugger.class);
if (debugger == null) {
return ;
}
synchronized (dmHandlers) {
if (dmHandlers.containsKey(debugger)) {
// A new session for the same debugger?
return ;
}
}
initLoadBP();
JPDABreakpointListener bpl = addPolyglotEngineCreationBP(debugger);
LOG.log(Level.FINE, "TruffleDebugManager.sessionAdded({0}), adding BP listener to {1}", new Object[]{session, debugManagerLoadBP});
synchronized (debugBPListeners) {
debugBPListeners.put(debugger, bpl);
}
}
@Override
public void sessionRemoved(Session session) {
JPDADebugger debugger = session.lookupFirst(null, JPDADebugger.class);
if (debugger == null) {
return ;
}
JPDABreakpointListener bpl;
synchronized (debugBPListeners) {
bpl = debugBPListeners.remove(debugger);
}
if (bpl != null) {
LOG.log(Level.FINE, "TruffleDebugManager.engineRemoved({0}), removing BP listener from {1}", new Object[]{session, debugManagerLoadBP});
debugManagerLoadBP.removeJPDABreakpointListener(bpl);
}
DebugManagerHandler dmh;
synchronized (dmHandlers) {
dmh = dmHandlers.remove(debugger);
}
if (dmh != null) {
LOG.log(Level.FINE, "TruffleDebugManager.engineRemoved({0}), destroying {1}", new Object[]{session, dmh});
dmh.destroy();
}
}
private JPDABreakpointListener addPolyglotEngineCreationBP(final JPDADebugger debugger) {
JPDABreakpointListener bpl = new JPDABreakpointListener() {
@Override
public void breakpointReached(JPDABreakpointEvent event) {
try {
submitPECreationBP(debugger, event.getReferenceType());
} finally {
event.resume();
}
}
};
debugManagerLoadBP.addJPDABreakpointListener(bpl);
// Submit creation BPs for existing engine classes:
Runnable submitEngineCreation = () -> {
List<JPDAClassType> polyglotEngines = new ArrayList<>();
//polyglotEngines.addAll(debugger.getClassesByName(SESSION_CREATION_BP_CLASS[0]));
List<JPDAClassType> enginePe = debugger.getClassesByName(SESSION_CREATION_BP_CLASS);
polyglotEngines.addAll(enginePe);
for (JPDAClassType pe : polyglotEngines) {
submitPECreationBP(debugger, ((JPDAClassTypeImpl) pe).getType());
// TODO: Find possible existing instances of the engine
// List<ObjectVariable> engines = pe.getInstances(0);
// We have no suspended thread... :-(
}
// Find possible existing instances of the engine
if (!enginePe.isEmpty() && debugger.canGetInstanceInfo()) {
long engineInstances = 0;
for (JPDAClassType pe : enginePe) {
engineInstances += pe.getInstanceCount();
}
if (engineInstances > 0) {
submitExistingEnginesProbe(debugger, enginePe);
}
}
};
if (debugger.getState() > 1) {
submitEngineCreation.run();
} else {
debugger.addPropertyChangeListener(JPDADebugger.PROP_STATE, new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (debugger.getState() > 1) {
submitEngineCreation.run();
debugger.removePropertyChangeListener(JPDADebugger.PROP_STATE, this);
}
}
});
}
return bpl;
}
private void submitPECreationBP(final JPDADebugger debugger, ReferenceType type) {
try {
List<Method> constructors = ReferenceTypeWrapper.methodsByName(type, "<init>");
for (Method c : constructors) {
if (!c.argumentTypeNames().isEmpty()) {
Location lastLocation = null;
Location l;
int i = 0;
// Search for the last (return) statement:
while ((l = MethodWrapper.locationOfCodeIndex(c, i)) != null) {
lastLocation = l;
i++;
}
BreakpointRequest bp = EventRequestManagerWrapper.createBreakpointRequest(lastLocation.virtualMachine().eventRequestManager(), lastLocation);
EventRequestWrapper.setSuspendPolicy(bp, EventRequest.SUSPEND_EVENT_THREAD);
((JPDADebuggerImpl) debugger).getOperator().register(bp, new Executor() {
@Override
public boolean exec(Event event) {
try {
ThreadReference threadReference = LocatableEventWrapper.thread((LocatableEvent) event);
JPDAThreadImpl thread = ((JPDADebuggerImpl) debugger).getThread(threadReference);
StackFrame topFrame = ThreadReferenceWrapper.frame(threadReference, 0);
List<Value> argumentValues = topFrame.getArgumentValues();
if (argumentValues.get(0) == null) {
// An empty constructor used for the builder only.
return true;
}
ObjectReference engine = StackFrameWrapper.thisObject(topFrame);
haveNewPE(debugger, thread, engine);
} catch (InternalExceptionWrapper | VMDisconnectedExceptionWrapper |
ObjectCollectedExceptionWrapper ex) {
} catch (IllegalThreadStateExceptionWrapper |
IncompatibleThreadStateException |
InvalidStackFrameExceptionWrapper ex) {
Exceptions.printStackTrace(ex);
}
return true;
}
@Override
public void removed(EventRequest eventRequest) {
}
});
try {
EventRequestWrapper.enable(bp);
} catch (InvalidRequestStateExceptionWrapper irsx) {
Exceptions.printStackTrace(irsx);
}
}
}
} catch (InternalExceptionWrapper | VMDisconnectedExceptionWrapper |
ObjectCollectedExceptionWrapper | ClassNotPreparedExceptionWrapper ex) {
}
}
private void submitExistingEnginesProbe(final JPDADebugger debugger, List<JPDAClassType> enginePe) {
synchronized (haveExistingEnginesTrigger) {
if (Boolean.TRUE.equals(haveExistingEnginesTrigger.get(debugger))) {
return ;
}
haveExistingEnginesTrigger.put(debugger, Boolean.TRUE);
}
MethodBreakpoint execTrigger = MethodBreakpoint.create(EXISTING_ENGINES_TRIGGER, "*");
execTrigger.setHidden(true);
execTrigger.setSession(debugger);
execTrigger.addJPDABreakpointListener((event) -> {
DebuggerManager.getDebuggerManager().removeBreakpoint(execTrigger);
try {
JPDAThreadImpl thread = (JPDAThreadImpl) event.getThread();
boolean haveSomeEngine = false;
for (JPDAClassType pe : enginePe) {
List<ObjectVariable> instances = pe.getInstances(0);
for (ObjectVariable obj : instances) {
Value value = ((JDIVariable) obj).getJDIValue();
if (value instanceof ObjectReference) {
haveNewPE(debugger, thread, (ObjectReference) value);
haveSomeEngine = true;
}
}
}
if (haveSomeEngine) {
for (ActionsProvider ap : ((JPDADebuggerImpl) debugger).getSession().lookup(null, ActionsProvider.class)) {
if (ap.getActions().contains(PauseInGraalScriptActionProvider.NAME)) {
// Trigger the enabling of the action as there are engines now:
ap.isEnabled(PauseInGraalScriptActionProvider.NAME);
}
}
}
} finally {
event.resume();
}
});
DebuggerManager.getDebuggerManager().addBreakpoint(execTrigger);
}
private void haveNewPE(JPDADebugger debugger, JPDAThreadImpl thread, ObjectReference engine) {
DebugManagerHandler dmh;
synchronized (dmHandlers) {
dmh = dmHandlers.get(debugger);
if (dmh == null) {
dmh = new DebugManagerHandler(debugger);
dmHandlers.put(debugger, dmh);
}
}
dmh.newPolyglotEngineInstance(engine, thread);
}
@Override
public void breakpointAdded(Breakpoint breakpoint) {
if (breakpoint instanceof JSLineBreakpoint) {
Collection<DebugManagerHandler> handlers;
synchronized (dmHandlers) {
handlers = new ArrayList<>(dmHandlers.values());
}
for (DebugManagerHandler dmh : handlers) {
dmh.breakpointAdded((JSLineBreakpoint) breakpoint);
}
}
}
@Override
public void breakpointRemoved(Breakpoint breakpoint) {
if (breakpoint instanceof JSLineBreakpoint) {
Collection<DebugManagerHandler> handlers;
synchronized (dmHandlers) {
handlers = new ArrayList<>(dmHandlers.values());
}
for (DebugManagerHandler dmh : handlers) {
dmh.breakpointRemoved((JSLineBreakpoint) breakpoint);
}
}
}
public static ClassType getDebugAccessorClass(JPDADebugger debugger) {
synchronized (dmHandlers) {
DebugManagerHandler dmh = dmHandlers.get(debugger);
if (dmh != null) {
return dmh.getAccessorClass();
} else {
return null;
}
}
}
public static JPDAClassType getDebugAccessorJPDAClass(JPDADebugger debugger) {
synchronized (dmHandlers) {
DebugManagerHandler dmh = dmHandlers.get(debugger);
if (dmh != null) {
return dmh.getAccessorJPDAClass();
} else {
return null;
}
}
}
}