All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.epsilon.eol.debug.EolDebugger Maven / Gradle / Ivy

The newest version!
/*********************************************************************
 * Copyright (c) 2012-2024 The University of York.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipse.epsilon.eol.debug;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.epsilon.common.module.IModule;
import org.eclipse.epsilon.common.module.ModuleElement;
import org.eclipse.epsilon.common.parse.Position;
import org.eclipse.epsilon.eol.IEolModule;
import org.eclipse.epsilon.eol.dom.ExecutableBlock;
import org.eclipse.epsilon.eol.dom.Import;
import org.eclipse.epsilon.eol.dom.Operation;
import org.eclipse.epsilon.eol.dom.Statement;
import org.eclipse.epsilon.eol.dom.StatementBlock;
import org.eclipse.epsilon.eol.execute.context.IEolContext;

public class EolDebugger implements IEolDebugger {

	private static final Logger LOGGER = Logger.getLogger(EolDebugger.class.getName());

	/**
	 * Lazily-initialized map from module URI to the actual IModule.
	 *
	 * Do not access this field directly: instead, use {@link #getURIToModuleMap()}.
	 */
	private Map uriToModule;

	private IEpsilonDebugTarget target = null;

	protected boolean stepping = false;
	protected ModuleElement currentModuleElement;
	protected ModuleElement stopAfterModuleElement;
	protected Integer stopAfterFrameStackSizeDropsBelow;

	@Override
	public boolean isTerminated() {
		return target != null ? target.isTerminated() : true;
	}

	@Override
	public void dispose() {
		target = null;
	}

	@Override
	public void setTarget(IEpsilonDebugTarget target) {
		this.target = target;
	}
	
	@Override
	public IEpsilonDebugTarget getTarget() {
		return target;
	}

	protected IEolModule getModule() {
		return target != null ? target.getModule() : null;
	}

	@Override
	public void control(ModuleElement ast, IEolContext context) {
		if (target == null || !controls(ast)) {
			return;
		}
		currentModuleElement = ast;

		try {
			if (stepping) {
				stepping = false;
				target.suspend(ast, SuspendReason.STEP);
			} else if (hasBreakpoint(ast)) {
				target.suspend(ast, SuspendReason.BREAKPOINT);
			}
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
	
		if (isTerminated()) return;
	}

	@Override
	public void done(ModuleElement ast, IEolContext context) {
		if (stopAfterModuleElement != null && ast == stopAfterModuleElement) {
			stepping = true;
			stopAfterModuleElement = null;
		}
		if (stopAfterFrameStackSizeDropsBelow != null && frameStackSize() < stopAfterFrameStackSizeDropsBelow) {
			stepping = true;
			stopAfterFrameStackSizeDropsBelow = null;
		}
	}

	@Override
	public void report(IEolContext context) {
		// nothing to do
	}

	@Override
	public void step() {
		stepping = true;
	}

	@Override
	public void stepOver() {
		stopAfterModuleElement = currentModuleElement;
	}

	@Override
	public void stepReturn() {
		stopAfterFrameStackSizeDropsBelow = frameStackSize();
	}

	private boolean controls(ModuleElement ast) {
		// Top level element or block
		if (ast.getParent() == null || ast instanceof StatementBlock) {
			return false;
		}
		return isStatement(ast) || isContainedExpression(ast);
	}

	private int frameStackSize() {
		IEolModule module = getModule();
		return module != null ? module.getContext().getFrameStack().getFrames().size() : 0;
	}

	private ModuleElement getGrandparent(ModuleElement ast) {
		return getParent(getParent(ast));
	}

	private ModuleElement getParent(ModuleElement ast) {
		return ast != null ? ast.getParent() : null;
	}

	private boolean hasBreakpoint(ModuleElement ast) {
		if (target != null && target.hasBreakpointItself(ast)) {
			return true;
		}
		
		if (isFirstStatement(ast)) {
			return hasBreakpoint(getGrandparent(ast));
		}
		else if (isContainedExpression(ast)) {
			return hasBreakpoint(getParent(ast));
		}
		
		if (isStructuralBlock(getParent(ast))) {
			if (isExpressionOrStatementBlockContainer(ast)) {
				return hasBreakpoint(getParent(ast));
			}
			else if (isStructuralBlock(ast)) {
				return hasBreakpoint(getParent(ast));
			}
		}
		
		return false;
	}

	protected boolean isExpressionOrStatementBlockContainer(ModuleElement ast) {
		return ast instanceof Operation || ast instanceof ExecutableBlock;
	}

	protected boolean isStructuralBlock(ModuleElement ast) {
		return false;
	}

	private boolean isContainedExpression(ModuleElement ast) {
		ModuleElement parent = getParent(ast);
		if (parent == null) return false;
		return isExpressionOrStatementBlockContainer(parent) && parent.getChildren().size() == 1;
	}

	private boolean isFirstStatement(ModuleElement ast) {
		ModuleElement parent = getParent(ast);
		if (parent == null) return false;
		if (!(parent instanceof StatementBlock)) return false;
		ModuleElement grandparent = getParent(parent);
		if (!isExpressionOrStatementBlockContainer(grandparent)) return false;
		return parent.getChildren().get(0) == ast;
	}

	private boolean isStatement(ModuleElement ast) {
		return ast instanceof Statement;
	}

	@Override
	public boolean isStepping() {
		return stepping;
	}

	@Override
	public ModuleElement getStopAfterModuleElement() {
		return stopAfterModuleElement;
	}

	@Override
	public Integer getStopAfterFrameStackSizeDropsBelow() {
		return stopAfterFrameStackSizeDropsBelow;
	}

	@Override
	public BreakpointResult verifyBreakpoint(BreakpointRequest request) {
		try {
			// Step 1. Map from request path (likely a file opened in the IDE) to an IEolModule
			IModule resolvedModule = resolveModule(request.getUriToPathMappings(), request.getPath());

			if (resolvedModule != null) {
				// Step 2. Find the first location with actual code from the requested location
				final Position actual = findFirstLineGreaterThanOrEqualTo(resolvedModule, new Position(request.getLine(), request.getColumn() == null ? 0 : request.getColumn()));
				if (actual != null) {
					return BreakpointResult.verified(request, resolvedModule, actual.getLine(), actual.getColumn());
				}
			}
		} catch (IOException e) {
			LOGGER.log(Level.WARNING, "Failed to verify breakpoint due to error: " + e.getMessage(), e);
		}

		return BreakpointResult.failed(request);
	}

	/**
	 * Tries to resolve the specific module referenced by a path reported via DAP.
	 * This will be a file in the developer's IDE, whose URI may not match 1:1 with
	 * the URI of the module being executed (e.g. if the module is being loaded from
	 * the classpath and not from the filesystem).
	 */
	protected IModule resolveModule(final Map uriToPathMappings, final String argsSourcePath) throws IOException {
		// Stop if we don't actually have a path
		if (argsSourcePath == null) {
			return null;
		}

		// Stop if the DAP path does not resolve to a file in our filesystem
		File argsSourceFile = new File(argsSourcePath);
		if (!argsSourceFile.exists()) {
			return null;
		}

		// Collect all the imports
		Map uriToModule = getURIToModuleMap();

		// Try to use a direct file: URI first
		final String argsSourceFileURI = argsSourceFile.toURI().toString();
		IModule resolvedModule = uriToModule.get(argsSourceFileURI);
		if (resolvedModule != null) {
			return resolvedModule;
		}

		// Try to use the URI-to-path mappings in reverse
		for (Entry e : uriToPathMappings.entrySet()) {
			final String baseUri = e.getKey().toString();
			final File canonicalFile = e.getValue().toFile().getCanonicalFile();
			final String basePath = canonicalFile.getPath() + (canonicalFile.isDirectory() ? File.separator : "");

			if (argsSourcePath.startsWith(basePath)) {
				String mappedUri = baseUri + argsSourcePath.substring(basePath.length());
				resolvedModule = uriToModule.get(mappedUri);
				if (resolvedModule != null) {
					return resolvedModule;
				}
			}
		}

		return null;
	}

	protected synchronized Map getURIToModuleMap() throws IOException {
		if (this.uriToModule == null) {
			this.uriToModule = new HashMap<>();
			IEolModule module = getModule();
			if (module != null) {
				addAllModules(uriToModule, module);
			}
		}
		return uriToModule;
	}

	protected void addAllModules(Map pathToModule, IModule module) throws IOException {
		// Note: mini-modules set up to run breakpoint conditions won't have a URI
		String moduleURI = null;
		if (module.getFile() != null) {
			moduleURI = module.getFile().getCanonicalFile().toURI().toString();
		} else if (module.getUri() != null) {
			moduleURI = module.getUri().toString();
		}

		if (moduleURI != null && !pathToModule.containsKey(moduleURI)) {
			pathToModule.put(moduleURI, module);
			if (module instanceof IEolModule) {
				for (Import imp : ((IEolModule) module).getImports()) {
					addAllModules(pathToModule, imp.getModule());
				}
			}
		}
	}

	/**
	 * Finds the first position that has module element that we control, at a
	 * certain position or after it. This handles cases where we set a breakpoint on
	 * an empty line or an import statement.
	 */
	protected Position findFirstLineGreaterThanOrEqualTo(ModuleElement root, Position position) {
		// The current root element starts where we need it: we're done
		final Position rootStartLine = root.getRegion().getStart();
		if (controls(root) && !rootStartLine.isBefore(position)) {
			return rootStartLine;
		}

		/*
		 * Otherwise, find earliest child whose region spans the line or something after
		 * the line. We scan over all elements, because the children of a module element
		 * do not have to be in the same order as they appear on the file.
		 * 
		 * This is the case, for instance, for the operations that appear below a bit of
		 * top-level code, which will appear before that top-level code in the list of
		 * children.
		 */
		Position earliestLine = null;
		for (ModuleElement child : root.getChildren()) {
			Position childLine = findFirstLineGreaterThanOrEqualTo(child, position);
			if (childLine != null && (earliestLine == null || childLine.isBefore(earliestLine))) {
				earliestLine = childLine;
			}
		}
		if (earliestLine != null) {
			return earliestLine;
		}

		// Otherwise, see if at least the region for the root spans the line in question.
		if (controls(root) && root.getRegion().getEnd().isAfter(position)) {
			return rootStartLine;
		}

		return earliestLine;
	}
	
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy