blob: c9c79a84418a6c06ff61ddc767854e9a39b61ca0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009,2018 R.Dvorak 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:
* Radek Dvorak - initial API and implementation
* Christopher Gerking - bugs 394498, 431082
*******************************************************************************/
package org.eclipse.m2m.qvt.oml.debug.core.vm;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.m2m.internal.qvt.oml.ast.env.InternalEvaluationEnv;
import org.eclipse.m2m.internal.qvt.oml.ast.env.QvtOperationalEnv;
import org.eclipse.m2m.internal.qvt.oml.ast.env.QvtOperationalEvaluationEnv;
import org.eclipse.m2m.internal.qvt.oml.compiler.CompiledUnit;
import org.eclipse.m2m.internal.qvt.oml.evaluator.InternalEvaluator;
import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtGenericVisitorDecorator;
import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtInterruptedExecutionException;
import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtOperationalEvaluationVisitor;
import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtOperationalEvaluationVisitorImpl;
import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtRuntimeException;
import org.eclipse.m2m.internal.qvt.oml.evaluator.TransformationInstance;
import org.eclipse.m2m.internal.qvt.oml.expressions.ImperativeOperation;
import org.eclipse.m2m.internal.qvt.oml.expressions.Module;
import org.eclipse.m2m.internal.qvt.oml.expressions.OperationalTransformation;
import org.eclipse.m2m.qvt.oml.debug.core.DebugOptions;
import org.eclipse.m2m.qvt.oml.debug.core.QVTODebugCore;
import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMRequest;
import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMResumeEvent;
import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMResumeRequest;
import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMStartEvent;
import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMSuspendEvent;
import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMSuspendRequest;
import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMTerminateRequest;
import org.eclipse.m2m.qvt.oml.ecore.ImperativeOCL.AssignExp;
import org.eclipse.ocl.expressions.LoopExp;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.ocl.expressions.Variable;
import org.eclipse.ocl.utilities.ASTNode;
public final class QVTODebugEvaluator extends QvtOperationalEvaluationVisitorImpl {
private final IQVTODebuggerShell fDebugShell;
private final VMBreakpointManager fBPM;
private final IterateBreakpointHelper fIterateBPHelper;
private final List<UnitLocation> fLocationStack;
private UnitLocation fCurrentLocation;
private int fCurrentStepMode;
private QVTODebugEvaluator(QvtOperationalEvaluationVisitorImpl parent,
QvtOperationalEvaluationEnv nestedEvalEnv) {
super(parent, nestedEvalEnv);
QVTODebugEvaluator debugParent = (QVTODebugEvaluator) parent;
fDebugShell = debugParent.fDebugShell;
fBPM = debugParent.fBPM;
fIterateBPHelper = debugParent.fIterateBPHelper;
fLocationStack = debugParent.fLocationStack;
fCurrentLocation = debugParent.fCurrentLocation;
fCurrentStepMode = debugParent.fCurrentStepMode;
}
public QVTODebugEvaluator(QvtOperationalEnv env,
QvtOperationalEvaluationEnv evalEnv, IQVTODebuggerShell shell) {
super(env, evalEnv);
fDebugShell = shell;
fBPM = shell.getBreakPointManager();
fIterateBPHelper = new IterateBreakpointHelper(fBPM);
fLocationStack = new ArrayList<UnitLocation>();
fCurrentLocation = null;
fCurrentStepMode = DebugEvent.UNSPECIFIED;
fDebugShell.sessionStarted(this);
VMRequest request = null;
try {
// suspend to let others to wake up us on demand
QVTODebugCore.TRACE.trace(DebugOptions.EVALUATOR,
"Debug evaluator going to initial SUSPEND state"); //$NON-NLS-1$
request = shell.waitAndPopRequest(new VMStartEvent(getMainModuleName(), true));
} catch (InterruptedException e) {
Thread.interrupted();
terminate();
}
if(request instanceof VMResumeRequest == false) {
// TODO - decide a set of request we can handle during initial SUSPEND mode,
// or report fError
terminate();
}
}
public QvtOperationalEvaluationVisitor createDebugInterceptor() {
return new DebugInterceptor(this);
}
@Override
protected InternalEvaluator createNestedEvaluationVisitor(
QvtOperationalEvaluationVisitorImpl parent,
QvtOperationalEvaluationEnv nestedEvalEnv) {
return new QVTODebugEvaluator(parent, nestedEvalEnv).createInterruptibleVisitor();
}
@Override
protected void poppedStack() {
popLocation();
}
@Override
protected void pushedStack(QvtOperationalEvaluationEnv env) {
InternalEvaluationEnv internEnv = env.getAdapter(InternalEvaluationEnv.class);
ASTNode currentIP = (ASTNode) internEnv.getCurrentIP();
UnitLocation startLocation = newLocalLocation(env, currentIP, currentIP.getStartPosition(),
currentIP.getEndPosition() - currentIP.getStartPosition());
pushLocation(startLocation);
}
@Override
protected void addToEnv(String varName, Object value, EClassifier declaredType) {
getOperationalEvaluationEnv().add(varName, value, declaredType);
}
@Override
protected void replaceInEnv(String varName, Object value, EClassifier declaredType) {
getOperationalEvaluationEnv().replace(varName, value, declaredType);
}
@Override
protected void processDeferredTasks() {
QvtOperationalEvaluationEnv evalEnv = getOperationalEvaluationEnv();
TransformationInstance transformation = evalEnv.getAdapter(
InternalEvaluationEnv.class).getCurrentTransformation();
Module module = transformation.getModule();
UnitLocation startLocation = newLocalLocation(evalEnv, module, module
.getEndPosition(), 0);
try {
pushLocation(startLocation);
super.processDeferredTasks();
} finally {
popLocation();
}
}
@Override
public void notifyAfterDeferredAssign(AssignExp asssignExp,
Object assignLeftValue) {
QvtOperationalEvaluationEnv evalEnv = getOperationalEvaluationEnv();
UnitLocation startLocation = newLocalLocation(evalEnv, asssignExp,
asssignExp.getStartPosition(), getNodeLength(asssignExp));
setCurrentLocation(asssignExp, startLocation, false);
processDebugRequest(startLocation);
UnitLocation endLocation = newLocalLocation(evalEnv, asssignExp,
asssignExp.getStartPosition() + getNodeLength(asssignExp) - 1,
1);
setCurrentLocation(asssignExp, endLocation, true);
}
public Object navigateProperty(EStructuralFeature property, Object target) {
OCLExpression<EClassifier> body = getPropertyBody(property);
if (body != null) {
return super.navigate(property, body, target);
}
return getEvaluationEnvironment().navigateProperty(property, null, target);
}
@Override
public Object visitVariable(Variable<EClassifier, EParameter> vd) {
Object result = super.visitVariable(vd);
EClassifier declaredType = vd.getType();
String name = vd.getName();
QvtOperationalEvaluationEnv env = getOperationalEvaluationEnv();
env.replace(name, env.getValueOf(name), declaredType);
return result;
}
public List<UnitLocation> getLocationStack() {
return fLocationStack;
}
protected Object preElementVisit(ASTNode element) {
setCurrentEnvInstructionPointer(element);
QvtOperationalEvaluationEnv evalEnv = getOperationalEvaluationEnv();
if (element instanceof Module) {
fCurrentLocation = newLocalLocation(evalEnv, element, element
.getStartPosition(), getNodeLength(element));
} else if (element instanceof ImperativeOperation) {
// nothing to do before visit
// only end location visit supported
} else if (element instanceof EStructuralFeature) {
// result = null;
} else if (element instanceof LoopExp<?, ?>) {
@SuppressWarnings("unchecked")
LoopExp<EClassifier, EParameter> loop = (LoopExp<EClassifier, EParameter>) element;
UnitLocation topLocation = getCurrentLocation();
boolean skipIterate = (fCurrentStepMode == DebugEvent.UNSPECIFIED)
|| ((fCurrentStepMode == DebugEvent.STEP_OVER) &&
(topLocation.getStackDepth() > fCurrentLocation.getStackDepth()));
if (!skipIterate) {
return fIterateBPHelper.stepIterateElement(loop, topLocation);
}
} else if (ValidBreakpointLocator.isBreakpointableElementStart(element)) {
UnitLocation startLocation = newLocalLocation(evalEnv, element,
element.getStartPosition(), getNodeLength(element));
setCurrentLocation(element, startLocation, false);
// FIXME - review, should process the debug request in all cases
processDebugRequest(startLocation);
} else {
setCurrentLocation(element, newLocalLocation(evalEnv, element,
element.getStartPosition(), getNodeLength(element)),
false);
}
return result;
}
protected Object postElementVisit(ASTNode element, Object preState, Object result) {
QvtOperationalEvaluationEnv evalEnv = getOperationalEvaluationEnv();
if (element instanceof Module) {
//
} else if (element instanceof ImperativeOperation) {
UnitLocation endLocation = newLocalLocation(evalEnv, element,
element.getStartPosition() + getNodeLength(element), 1);
setCurrentLocation(element, endLocation, true);
} else if (element instanceof EStructuralFeature) {
// result = null;
} else if (element instanceof LoopExp<?, ?>) {
if (preState instanceof VMBreakpoint) {
fIterateBPHelper.removeIterateBreakpoint((VMBreakpoint) preState);
}
} else {
UnitLocation el = newLocalLocation(
evalEnv, element, element
.getStartPosition()
+ getNodeLength(element) - 1, 1);
setCurrentLocation(element, el, true);
}
return result;
}
private void processDebugRequest(UnitLocation location) {
VMRequest event = fDebugShell.popRequest();
if (event == null) {
return;
}
doProcessRequest(location, event);
}
private void doProcessRequest(UnitLocation location, VMRequest request) {
if (request instanceof VMResumeRequest) {
VMResumeRequest resumeRequest = (VMResumeRequest) request;
fCurrentLocation = getCurrentLocation();
fCurrentStepMode = resumeRequest.detail;
if (fCurrentStepMode == DebugEvent.UNSPECIFIED) {
fIterateBPHelper.removeAllIterateBreakpoints();
}
} else if (request instanceof VMSuspendRequest) {
VMSuspendRequest suspendRequest = (VMSuspendRequest) request;
suspendAndWaitForResume(location, suspendRequest.detail);
} else if (request instanceof VMTerminateRequest) {
terminate();
} else {
throw new IllegalArgumentException(
"Unsupported debug request: " + request); //$NON-NLS-1$
}
}
protected void handleLocationChanged(ASTNode element, UnitLocation location, boolean isElementEnd) {
if (fCurrentLocation == null) {
return;
}
if(false == (!isElementEnd ? ValidBreakpointLocator.isBreakpointableElementStart(element) :
ValidBreakpointLocator.isBreakpointableElementEnd(element))) {
return;
}
switch (fCurrentStepMode) {
case DebugEvent.STEP_OVER:
if (location.getStackDepth() <= fCurrentLocation.getStackDepth()
&& (!location.isTheSameLine(fCurrentLocation)
/*|| repeatedInIterator(location, fCurrentLocation)*/ )) {
fCurrentLocation = null;
suspendAndWaitForResume(location, fCurrentStepMode);
return;
}
break;
case DebugEvent.STEP_INTO:
if (!location.isTheSameLine(fCurrentLocation) /*|| repeatedInIterator(location, fCurrentLocation)*/) {
fCurrentLocation = null;
suspendAndWaitForResume(location, fCurrentStepMode);
return;
}
break;
case DebugEvent.STEP_RETURN:
if (location.getStackDepth() < fCurrentLocation.getStackDepth()) {
fCurrentLocation = null;
suspendAndWaitForResume(location, fCurrentStepMode);
return;
}
break;
}
// check if we trigger a registered breakpoint
for (VMBreakpoint breakpoint : fBPM.getBreakpoints(element)) {
if (breakpoint.getLineNumber() != location.getLineNum()) {
//TODO - faster to indicate in and or beginning enablement in VMBreakpoint ?
//|| !((!isElementEnd) ? ValidBreakpointLocator.isBreakpointableElementStart(element) :
//ValidBreakpointLocator.isBreakpointableElementEnd(element))) {
// no breakpoint can be triggered
continue;
}
Boolean isTriggered = null;
try {
isTriggered = Boolean.valueOf(breakpoint.hitAndCheckIfTriggered(this));
} catch(CoreException e) {
IStatus status = e.getStatus();
String reason = null; //$NON-NLS-1$
if(status.getCode() == ConditionChecker.ERR_CODE_COMPILATION) {
reason = "Breakpoint condition compilation failed";
} else if(status.getCode() == ConditionChecker.ERR_CODE_EVALUATION) {
reason = "Breakpoint condition evaluation failed";
}
if(reason != null) {
// breakpoint condition parsing or evaluation failed, notify the debug client
VMSuspendEvent suspendOnBpConditionErrr = createVMSuspendEvent(VMSuspendEvent.BREAKPOINT_CONDITION_ERR);
suspendOnBpConditionErrr.setBreakpointID(breakpoint.getID());
suspendOnBpConditionErrr.setReason(reason, status.getMessage());
// suspend VM and wait for resolution by the debug client
suspendAndWaitForResume(location, suspendOnBpConditionErrr);
} else {
QVTODebugCore.log(e.getStatus());
}
continue;
}
if (Boolean.TRUE.equals(isTriggered)) {
boolean isIterateBp = fIterateBPHelper.isIterateBreakpoint(breakpoint);
int eventDetail = isIterateBp ? fCurrentStepMode : DebugEvent.BREAKPOINT;
// let the VM suspend and wait for resume request
suspendAndWaitForResume(location, eventDetail);
if (isIterateBp) {
fBPM.removeBreakpoint(breakpoint);
}
}
}
}
private VMSuspendEvent createVMSuspendEvent(int eventDetail) {
// build the VM stack frames
VMStackFrame[] vmStack = VMStackFrame.create(getLocationStack());
assert vmStack.length > 0;
return new VMSuspendEvent(vmStack, eventDetail);
}
private void suspendAndWaitForResume(UnitLocation location, int eventDetail) {
suspendAndWaitForResume(location, createVMSuspendEvent(eventDetail));
}
private void suspendAndWaitForResume(UnitLocation location, VMSuspendEvent suspendEvent) {
try {
VMSuspendEvent vmSuspend = suspendEvent;
// send to the client runner, wait for resume
VMRequest nextRequest = fDebugShell.waitAndPopRequest(vmSuspend);
assert nextRequest != null;
if(nextRequest instanceof VMResumeRequest) {
fDebugShell.handleVMEvent(new VMResumeEvent());
}
doProcessRequest(location, nextRequest);
} catch (InterruptedException e) {
terminate();
}
}
private UnitLocation newLocalLocation(QvtOperationalEvaluationEnv evalEnv,
ASTNode node, int offset, int length) {
return new UnitLocation(offset, evalEnv, node);
}
private void setCurrentLocation(ASTNode element, UnitLocation newLocation,
boolean atEnd) {
if (fLocationStack.isEmpty()) {
return;
}
// do not change to position-less locations
if (newLocation.getOffset() < 0) {
return;
}
fLocationStack.set(0, newLocation);
handleLocationChanged(element, newLocation, atEnd);
}
private void pushLocation(UnitLocation location) {
fLocationStack.add(0, location);
}
private UnitLocation popLocation() {
UnitLocation removed = fLocationStack.remove(0);
return removed;
}
UnitLocation getCurrentLocation() {
return (!fLocationStack.isEmpty()) ? fLocationStack.get(0) : null;
}
static int getNodeLength(ASTNode element) {
return element.getEndPosition() - element.getStartPosition();
}
private void terminate() throws QvtInterruptedExecutionException {
InternalEvaluationEnv currentEnv = getOperationalEvaluationEnv().getAdapter(InternalEvaluationEnv.class);
currentEnv.throwQVTException(new QvtInterruptedExecutionException("User termination request"));
}
private String getMainModuleName() {
CompiledUnit mainUnit = fBPM.getUnitManager().getMainUnit();
if(mainUnit.getModules().isEmpty()) {
return "<null>"; //$NON-NLS-1$
}
return mainUnit.getModules().get(0).getName();
}
@Override
protected InternalEvaluator createInterruptibleVisitor() {
return (DebugInterceptor)createDebugInterceptor();
}
private final class DebugInterceptor extends QvtGenericVisitorDecorator {
private DebugInterceptor(QVTODebugEvaluator qvtExtVisitor) {
super(qvtExtVisitor);
}
public Object execute(OperationalTransformation transformation) throws QvtRuntimeException {
fCurrentLocation = newLocalLocation(getOperationalEvaluationEnv(), transformation, transformation
.getStartPosition(), getNodeLength(transformation));
return getInternalEvalDelegate().execute(transformation);
}
@Override
protected Object genericPreVisitAST(ASTNode visited) {
if (getContext().getProgressMonitor() != null && getContext().getProgressMonitor().isCanceled()) {
throwQVTException(new QvtInterruptedExecutionException());
}
return preElementVisit(visited);
}
@Override
protected Object genericPostVisitAST(ASTNode element, Object preVisitState, Object result) {
return postElementVisit(element, preVisitState, result);
}
}
}