blob: f6a962a12763baca689f130208e372b551990d5a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014, 2018 Willink Transformations and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* R.Dvorak and others - QVTo debugger framework
* E.D.Willink - revised API for OCL debugger framework
*******************************************************************************/
package org.eclipse.ocl.examples.debug.vm;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.debug.core.model.IValue;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.ocl.examples.debug.vm.core.VMDebugTarget;
import org.eclipse.ocl.examples.debug.vm.core.VMLocalValue;
import org.eclipse.ocl.examples.debug.vm.core.VMLocalValue.LocalValue;
import org.eclipse.ocl.examples.debug.vm.data.VMBreakpointData;
import org.eclipse.ocl.examples.debug.vm.data.VMNewBreakpointData;
import org.eclipse.ocl.examples.debug.vm.data.VMStackFrameData;
import org.eclipse.ocl.examples.debug.vm.evaluator.IDebuggableRunnerFactory;
import org.eclipse.ocl.examples.debug.vm.evaluator.VMEvaluationEnvironment;
import org.eclipse.ocl.examples.debug.vm.evaluator.VMEvaluationStepper;
import org.eclipse.ocl.examples.debug.vm.event.VMEvent;
import org.eclipse.ocl.examples.debug.vm.event.VMStartEvent;
import org.eclipse.ocl.examples.debug.vm.event.VMTerminateEvent;
import org.eclipse.ocl.examples.debug.vm.launching.DebuggableRunner;
import org.eclipse.ocl.examples.debug.vm.launching.VMDebuggableExecutorAdapter;
import org.eclipse.ocl.examples.debug.vm.request.VMBreakpointRequest;
import org.eclipse.ocl.examples.debug.vm.request.VMBreakpointRequest.ActionKind;
import org.eclipse.ocl.examples.debug.vm.request.VMDetailRequest;
import org.eclipse.ocl.examples.debug.vm.request.VMRequest;
import org.eclipse.ocl.examples.debug.vm.request.VMStackFrameRequest;
import org.eclipse.ocl.examples.debug.vm.request.VMStartRequest;
import org.eclipse.ocl.examples.debug.vm.request.VMVariableRequest;
import org.eclipse.ocl.examples.debug.vm.response.VMBreakpointResponse;
import org.eclipse.ocl.examples.debug.vm.response.VMDetailResponse;
import org.eclipse.ocl.examples.debug.vm.response.VMResponse;
import org.eclipse.ocl.examples.debug.vm.response.VMStackFrameResponse;
import org.eclipse.ocl.examples.debug.vm.utils.DebugOptions;
import org.eclipse.ocl.pivot.Element;
import org.eclipse.ocl.pivot.evaluation.EvaluationEnvironment;
import org.eclipse.ocl.pivot.utilities.ClassUtil;
import org.eclipse.ocl.pivot.utilities.TracingOption;
public abstract class VMVirtualMachine implements IVMVirtualMachineShell
{
public static final @NonNull TracingOption LOCATION = new TracingOption(DebugVMPlugin.PLUGIN_ID, "location");
public static final @NonNull TracingOption PRE_VISIT = new TracingOption(DebugVMPlugin.PLUGIN_ID, "pre-visit");
public static final @NonNull TracingOption POST_VISIT = new TracingOption(DebugVMPlugin.PLUGIN_ID, "post-visit");
public static final @NonNull TracingOption VISITOR_STACK = new TracingOption(DebugVMPlugin.PLUGIN_ID, "visitorStack");
public static final @NonNull TracingOption VM_EVENT = new TracingOption(DebugVMPlugin.PLUGIN_ID, "vmEvent");
public static final @NonNull TracingOption VM_REQUEST = new TracingOption(DebugVMPlugin.PLUGIN_ID, "vmRequest");
public static final @NonNull TracingOption VM_RESPONSE = new TracingOption(DebugVMPlugin.PLUGIN_ID, "vmResponse");
public static final @NonNull String EXCEPTION_NAME = "$invalid";
public static final @NonNull String PC_NAME = "$pc";
static {
// LOCATION.setState(true);
// PRE_VISIT.setState(true);
// POST_VISIT.setState(true);
// VISITOR_STACK.setState(true);
// VM_EVENT.setState(true);
// VM_REQUEST.setState(true);
// VM_RESPONSE.setState(true);
}
private class DebuggerShell implements IVMDebuggerShell
{
@Override
public @NonNull VMBreakpointManager getBreakPointManager() {
return VMVirtualMachine.this.fBreakpointManager;
}
@Override
public void handleVMEvent(@NonNull VMEvent event) {
if (VM_EVENT.isActive()) {
VM_EVENT.println("?[" + Thread.currentThread().getName() + "] " + event.toString());
}
if(event instanceof VMStartEvent) {
// first start event
synchronized (fStateMonitor) {
fRunning = true;
fStateMonitor.notify();
}
} else if(event instanceof VMTerminateEvent) {
synchronized (fStateMonitor) {
fRunning = false;
fTerminated = true;
fExitCode = ((VMTerminateEvent)event).getExitCode();
fInterpreter = null; // Needed to workaround BUG 468902
fExecutor = null; // Needed to workaround BUG 468902
runner = null; // Needed to workaround BUG 468902
fStateMonitor.notify();
}
}
try {
fEvents.add(event);
} catch(IllegalStateException e) {
// FIXME
System.err.println("Event queue full!!!!");
}
}
@Override
public boolean isSessionStarted() {
return fInterpreter != null;
}
@Override
public VMRequest peekRequest() {
synchronized (fLock) {
return fRequests.isEmpty() ? null : fRequests.get(0);
}
}
@Override
public @Nullable VMRequest popRequest() {
synchronized (fLock) {
return fRequests.isEmpty() ? null : fRequests.remove(0);
}
}
@Override
public void sessionStarted(@NonNull VMEvaluationStepper evaluator) {
fInterpreter = evaluator;
}
@Override
public @Nullable VMRequest waitAndPopRequest(@NonNull VMEvent suspend) throws InterruptedException {
// FIXME - should be locked to ensure none can really send a request until
// we deliver the event
handleVMEvent(suspend);
synchronized (fLock) {
while(fRequests.isEmpty()) {
fLock.wait();
}
VMRequest request = fRequests.remove(0);
if (VM_REQUEST.isActive()) {
VM_REQUEST.println(">[" + Thread.currentThread().getName() + "] " + request);
}
return request;
}
}
}
private static int execute(@NonNull VMDebuggableExecutorAdapter executorAdapter, @NonNull VMStartRequest startRequest) {
int exitCode = 0;
try {
Diagnostic diagnostic = executorAdapter.execute(startRequest);
int severity = diagnostic.getSeverity();
if(severity == Diagnostic.ERROR || severity == Diagnostic.CANCEL) {
System.err.println(diagnostic.toString());
exitCode = -1;
}
} catch (Throwable e) {
exitCode = -2;
// FIXME Auto-generated catch block
e.printStackTrace();
}
return exitCode;
}
public static @Nullable UnitLocation lookupEnvironmentByID(long id, @NonNull List<UnitLocation> stack) {
for (UnitLocation location : stack) {
VMEvaluationEnvironment evalEnv = location.getEvalEnv();
if (evalEnv.getID() == id) {
return location;
}
}
return null;
}
private final List<VMRequest> fRequests = new ArrayList<VMRequest>();
private final BlockingQueue<VMEvent> fEvents = new ArrayBlockingQueue<VMEvent>(50);
private /*final @NonNull*/ DebuggableRunner runner;
private final @NonNull IVMDebuggerShell fDebuggerShell;
private final @NonNull VMBreakpointManager fBreakpointManager;
private @Nullable VMEvaluationStepper fInterpreter;
private /*final @NonNull*/ VMDebuggableExecutorAdapter fExecutor;
private boolean fRunning;
private boolean fTerminated;
private int fExitCode = -3;
private Object fStateMonitor = new Object();
private final Object fLock = new Object();
protected VMVirtualMachine(@NonNull DebuggableRunner runner, @NonNull VMDebuggableExecutorAdapter executorAdapter) {
this.runner = runner;
fExecutor = executorAdapter;
fDebuggerShell = new DebuggerShell();
fBreakpointManager = new VMBreakpointManager(this, executorAdapter.getUnit());
fTerminated = false;
}
private @NonNull Runnable createVMRunnable(final @NonNull VMStartRequest startRequest) {
return new Runnable() {
@Override
public void run() {
int exitCode = -1;
try {
fExecutor.connect(fDebuggerShell);
exitCode = execute(ClassUtil.nonNullState(fExecutor), startRequest);
} catch(Throwable e) {
getDebugCore().log(e);
} finally {
fDebuggerShell.handleVMEvent(new VMTerminateEvent(exitCode));
}
}
};
}
public abstract @NonNull VMBreakpoint createBreakpoint(@NonNull Element element, @NonNull VMNewBreakpointData data, boolean isTemporary);
public abstract @NonNull VMBreakpoint createBreakpoint(@NonNull Element element, long id, int line, @NonNull String targetURI, boolean isTemporary);
public @Nullable VMStackFrameData createStackFrame(long frameID, @NonNull List<UnitLocation> stack) {
UnitLocation location = lookupEnvironmentByID(frameID, stack);
if (location != null) {
return createStackFrame(location);
}
// invalid stack frame
return null;
}
protected abstract @Nullable VMStackFrameData createStackFrame(@NonNull UnitLocation location);
public IValue evaluate(@NonNull String expressionText, VMDebugTarget debugTarget, long frameID) throws CoreException {
VMEvaluationStepper fInterpreter2 = fInterpreter;
if (fInterpreter2 == null) {
return null;
}
Element astNode = fInterpreter2.getCurrentLocation().getElement();
if (astNode == null) {
return null;
}
ConditionChecker localChecker = new ConditionChecker(expressionText, astNode);
LocalValue lv = new LocalValue();
lv.valueObject = localChecker.evaluate(fInterpreter2);
lv.valueType = localChecker.getConditionType();
return new VMLocalValue(debugTarget, frameID, new @NonNull String @NonNull [] {expressionText}, lv,
fInterpreter2.getVMEvaluationEnvironment());
}
public @Nullable EvaluationEnvironment getEvaluationEnv() {
VMEvaluationStepper fInterpreter2 = fInterpreter;
if (fInterpreter2 == null) {
return null;
}
return fInterpreter2.getVMEvaluationEnvironment();
}
public int getExitCode() {
return fExitCode;
}
public @NonNull DebuggableRunner getRunner() {
return ClassUtil.nonNullState(runner);
}
public @NonNull IDebuggableRunnerFactory getRunnerFactory() {
return runner.getRunnerFactory();
}
private @NonNull VMBreakpointResponse handleBreakPointRequest(@NonNull VMBreakpointRequest request) {
ActionKind actionKind = request.getActionKind();
if(actionKind == VMBreakpointRequest.ActionKind.ADD) {
List<VMBreakpointData> allBpData = request.getBreakpointData();
if(allBpData != null) {
List<Long> addedBpIDs = new ArrayList<Long>();
for (VMBreakpointData bpData : allBpData) {
if(bpData instanceof VMNewBreakpointData == false) {
continue;
}
VMNewBreakpointData newBreakpoint = (VMNewBreakpointData) bpData;
VMBreakpoint breakpoint = fBreakpointManager.createBreakpoint(newBreakpoint);
if(breakpoint != null) {
addedBpIDs.add(Long.valueOf(newBreakpoint.getID()));
getDebugCore().getTrace().trace(DebugOptions.VM,
"Installing breakpoint: " + " line:" //$NON-NLS-1$ //$NON-NLS-2$
+ newBreakpoint.getLine() + " " //$NON-NLS-1$
+ newBreakpoint.getTargetURI());
} else {
getDebugCore().getTrace().trace(DebugOptions.VM,
"Failed to create breakpoint: " + " line:" //$NON-NLS-1$ //$NON-NLS-2$
+ newBreakpoint.getLine() + " " //$NON-NLS-1$
+ newBreakpoint.getTargetURI());
}
}
return new VMBreakpointResponse(addedBpIDs);
}
} else if(actionKind == VMBreakpointRequest.ActionKind.REMOVE) {
fBreakpointManager.removeBreakpoint(request.getBreakpointID());
} else if(actionKind == VMBreakpointRequest.ActionKind.CHANGE) {
fBreakpointManager.changeBreakpoint(request.getBreakpointID(), request.getFirstBreakpointData());
}
// TODO
return new VMBreakpointResponse();
}
private @NonNull VMResponse handleStackFrameRequest(@NonNull VMStackFrameRequest request) {
VMEvaluationStepper fInterpreter2 = fInterpreter;
if (fInterpreter2 != null) {
List<UnitLocation> locationStack = fInterpreter2.getLocationStack();
VMStackFrameData frame = createStackFrame(request.frameID, locationStack);
if (frame != null) {
VMStackFrameResponse response = new VMStackFrameResponse(frame);
if (!locationStack.isEmpty()) {
UnitLocation topLocation = locationStack.get(0);
response.isDeferredExecution = topLocation.isDeferredExecution();
}
return response;
}
}
// FIXME
return VMResponse.createERROR();
}
private @Nullable VMResponse handleValueDetailRequest(@NonNull VMDetailRequest request) {
// FIXME - ensure VM is in SUSPEND state, otherwise report fError
VMEvaluationStepper fInterpreter2 = fInterpreter;
if (fInterpreter2 != null) {
VMEvaluationEnvironment vmEvaluationEnvironment = fInterpreter2.getCurrentLocation().getEvalEnv();
VariableFinder variableFinder = VariableFinder.newInstance(vmEvaluationEnvironment, true);
String detail = variableFinder.computeDetail(request.getVariableURI());
return new VMDetailResponse(detail != null ? detail : ""); //$NON-NLS-1$
}
else {
return null;
}
}
private @Nullable VMResponse handleVariableRequest(@NonNull VMVariableRequest request) {
// FIXME - ensure VM is in SUSPEND state, otherwise report fError
VMEvaluationStepper fInterpreter2 = fInterpreter;
if (fInterpreter2 != null) {
VMEvaluationEnvironment vmEvaluationEnvironment = fInterpreter2.getCurrentLocation().getEvalEnv();
VariableFinder variableFinder = VariableFinder.newInstance(vmEvaluationEnvironment, true);
return variableFinder.process(request, fInterpreter2.getLocationStack());
}
else {
return null;
}
}
@Override
public boolean isTerminated() {
return fTerminated;
}
@Override
public VMEvent readVMEvent() throws IOException {
try {
return fEvents.take();
} catch(InterruptedException e) {
throw new IOException("Waiting for event interrupted");
}
}
@Override
public @NonNull VMResponse sendRequest(@NonNull VMRequest request) throws IOException {
VMResponse response = null;
try {
if(request instanceof VMStartRequest) {
if (VM_REQUEST.isActive()) {
VM_REQUEST.println(">[" + Thread.currentThread().getName() + "] " + request);
}
response = start((VMStartRequest)request);
} else if(request instanceof VMBreakpointRequest) {
if (VM_REQUEST.isActive()) {
VM_REQUEST.println(">[" + Thread.currentThread().getName() + "] " + request);
}
response = handleBreakPointRequest((VMBreakpointRequest) request);
} else if(request instanceof VMStackFrameRequest) {
if (VM_REQUEST.isActive()) {
VM_REQUEST.println(">[" + Thread.currentThread().getName() + "] " + request);
}
response = handleStackFrameRequest((VMStackFrameRequest) request);
} else if(request instanceof VMVariableRequest) {
if (VM_REQUEST.isActive()) {
VM_REQUEST.println(">[" + Thread.currentThread().getName() + "] " + request);
}
response = handleVariableRequest((VMVariableRequest) request);
} else if(request instanceof VMDetailRequest) {
if (VM_REQUEST.isActive()) {
VM_REQUEST.println(">[" + Thread.currentThread().getName() + "] " + request);
}
response = handleValueDetailRequest((VMDetailRequest) request);
}
} catch (RuntimeException e) {
getDebugCore().log(e);
response = VMResponse.createERROR();
}
if (response != null) {
if (VM_RESPONSE.isActive()) {
VM_RESPONSE.println("<[" + Thread.currentThread().getName() + "] " + response);
}
return response;
}
if (VM_REQUEST.isActive()) {
VM_REQUEST.println("?[" + Thread.currentThread().getName() + "] " + request);
}
synchronized (fLock) {
fRequests.add(request);
fLock.notifyAll();
}
response = VMResponse.createOK();
if (VM_RESPONSE.isActive()) {
VM_RESPONSE.println("<[" + Thread.currentThread().getName() + "] " + response);
}
return response;
}
private @NonNull VMResponse start(@NonNull VMStartRequest startRequest) {
Thread executorThread = new Thread(createVMRunnable(startRequest), getDebugCore().getVMThreadName());
synchronized (fStateMonitor) {
if(fRunning) {
return VMResponse.createERROR();
}
executorThread.start();
while(!(fRunning || fTerminated)) {
try {
fStateMonitor.wait();
} catch (InterruptedException e) {
getDebugCore().log(getDebugCore().createStatus(IStatus.ERROR, "VM startup process interrupted"));
}
}
}
return VMResponse.createOK();
}
}