| /******************************************************************************* |
| * Copyright (c) 2009,2014 R.Dvorak and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.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 QvtOperationalEvaluationVisitorImpl createNestedEvaluationVisitor( |
| QvtOperationalEvaluationVisitorImpl parent, |
| QvtOperationalEvaluationEnv nestedEvalEnv) { |
| return new QVTODebugEvaluator(parent, nestedEvalEnv); |
| } |
| |
| @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); |
| } |
| } |
| |
| } |