blob: d9dc4ae48b2a852d34c6c6e8a319c98da58c2294 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2020 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Jesper Steen Moller - Bugs 341232, 427089
* Chris West (Faux) - Bug 45507
*******************************************************************************/
package org.eclipse.jdt.internal.debug.eval.ast.engine;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IDebugEventFilter;
import org.eclipse.debug.core.model.ITerminate;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.debug.core.model.IVariable;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.debug.core.IEvaluationRunnable;
import org.eclipse.jdt.debug.core.IJavaArray;
import org.eclipse.jdt.debug.core.IJavaArrayType;
import org.eclipse.jdt.debug.core.IJavaDebugTarget;
import org.eclipse.jdt.debug.core.IJavaObject;
import org.eclipse.jdt.debug.core.IJavaReferenceType;
import org.eclipse.jdt.debug.core.IJavaStackFrame;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.core.IJavaType;
import org.eclipse.jdt.debug.core.IJavaValue;
import org.eclipse.jdt.debug.core.IJavaVariable;
import org.eclipse.jdt.debug.eval.IAstEvaluationEngine;
import org.eclipse.jdt.debug.eval.ICompiledExpression;
import org.eclipse.jdt.debug.eval.IEvaluationListener;
import org.eclipse.jdt.debug.eval.IEvaluationResult;
import org.eclipse.jdt.internal.debug.core.JDIDebugOptions;
import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
import org.eclipse.jdt.internal.debug.core.logicalstructures.JDILambdaVariable;
import org.eclipse.jdt.internal.debug.core.logicalstructures.JDIReturnValueVariable;
import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;
import org.eclipse.jdt.internal.debug.core.model.JDIThisVariable;
import org.eclipse.jdt.internal.debug.core.model.JDIThread;
import org.eclipse.jdt.internal.debug.core.model.JDIValue;
import org.eclipse.jdt.internal.debug.core.model.LambdaUtils;
import org.eclipse.jdt.internal.debug.core.model.SyntheticVariableUtils;
import org.eclipse.jdt.internal.debug.eval.EvaluationResult;
import org.eclipse.jdt.internal.debug.eval.ast.instructions.InstructionSequence;
import com.sun.jdi.InvocationException;
import com.sun.jdi.ObjectReference;
public class ASTEvaluationEngine implements IAstEvaluationEngine {
public static final String ANONYMOUS_VAR_PREFIX = "val$"; //$NON-NLS-1$
private static final int EVALUATION_DETAIL_BITMASK = DebugEvent.EVALUATION | DebugEvent.EVALUATION_IMPLICIT;
private static final String QN_OBJECT = "java.lang.Object"; //$NON-NLS-1$
private IJavaProject fProject;
private IJavaDebugTarget fDebugTarget;
/**
* Regex to find occurrences of 'this' in a code snippet
*/
private static Pattern fgThisPattern = Pattern
.compile("(.*[^a-zA-Z0-9]+|^)(this)([^a-zA-Z0-9]+|$).*"); //$NON-NLS-1$
/**
* Filters variable change events during an evaluation to avoid refreshing
* the variables view until done.
*/
class EventFilter implements IDebugEventFilter {
/*
* (non-Javadoc)
*
* @see
* org.eclipse.debug.core.IDebugEventFilter#filterDebugEvents(org.eclipse
* .debug.core.DebugEvent[])
*/
@Override
public DebugEvent[] filterDebugEvents(DebugEvent[] events) {
if (events.length == 1) {
DebugEvent event = events[0];
if (event.getSource() instanceof IJavaVariable
&& event.getKind() == DebugEvent.CHANGE) {
if (((IJavaVariable) event.getSource()).getDebugTarget()
.equals(getDebugTarget())) {
return null;
}
}
}
return events;
}
}
public ASTEvaluationEngine(IJavaProject project,
IJavaDebugTarget debugTarget) {
setJavaProject(project);
setDebugTarget(debugTarget);
}
public void setJavaProject(IJavaProject project) {
fProject = project;
}
public void setDebugTarget(IJavaDebugTarget debugTarget) {
fDebugTarget = debugTarget;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.eval.IEvaluationEngine#evaluate(java.lang.String,
* org.eclipse.jdt.debug.core.IJavaStackFrame,
* org.eclipse.jdt.debug.eval.IEvaluationListener, int, boolean)
*/
@Override
public void evaluate(String snippet, IJavaStackFrame frame,
IEvaluationListener listener, int evaluationDetail,
boolean hitBreakpoints) throws DebugException {
traceCaller(snippet, frame.getThread());
ICompiledExpression expression = getCompiledExpression(snippet, frame);
evaluateExpression(expression, frame, listener, evaluationDetail,
hitBreakpoints);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.eval.IEvaluationEngine#evaluate(java.lang.String,
* org.eclipse.jdt.debug.core.IJavaObject,
* org.eclipse.jdt.debug.core.IJavaThread,
* org.eclipse.jdt.debug.eval.IEvaluationListener, int, boolean)
*/
@Override
public void evaluate(String snippet, IJavaObject thisContext,
IJavaThread thread, IEvaluationListener listener,
int evaluationDetail, boolean hitBreakpoints) throws DebugException {
traceCaller(snippet, thread);
ICompiledExpression expression = getCompiledExpression(snippet,
thisContext);
evaluateExpression(expression, thisContext, thread, listener,
evaluationDetail, hitBreakpoints);
}
/**
* Writes a stack dump to trace the calling thread.
*
* @param snippet
* expression to evaluate
* @param thread
* thread to evaluate in
*/
private void traceCaller(String snippet, IThread thread) {
if (JDIDebugOptions.DEBUG_AST_EVAL_THREAD_TRACE) {
StringBuilder buf = new StringBuilder();
buf.append(JDIDebugOptions.FORMAT.format(new Date()));
buf.append(" : Evaluation Request Trace - Expression: "); //$NON-NLS-1$
buf.append(snippet);
buf.append("\n\tThread: "); //$NON-NLS-1$
try {
String name = thread.getName();
buf.append('[');
buf.append(name);
buf.append("] "); //$NON-NLS-1$
} catch (DebugException e) {
buf.append(thread.toString());
}
JDIDebugOptions.trace(buf.toString());
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.eval.IAstEvaluationEngine#evaluateExpression(org
* .eclipse.jdt.debug.eval.ICompiledExpression,
* org.eclipse.jdt.debug.core.IJavaStackFrame,
* org.eclipse.jdt.debug.eval.IEvaluationListener, int, boolean)
*/
@Override
public void evaluateExpression(ICompiledExpression expression,
IJavaStackFrame frame, IEvaluationListener listener,
int evaluationDetail, boolean hitBreakpoints) throws DebugException {
traceCaller(expression.getSnippet(), frame.getThread());
RuntimeContext context = new RuntimeContext(getJavaProject(), frame);
doEvaluation(expression, context, (IJavaThread) frame.getThread(),
listener, evaluationDetail, hitBreakpoints);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.eval.IAstEvaluationEngine#evaluateExpression(org
* .eclipse.jdt.debug.eval.ICompiledExpression,
* org.eclipse.jdt.debug.core.IJavaObject,
* org.eclipse.jdt.debug.core.IJavaThread,
* org.eclipse.jdt.debug.eval.IEvaluationListener, int, boolean)
*/
@Override
public void evaluateExpression(ICompiledExpression expression,
IJavaObject thisContext, IJavaThread thread,
IEvaluationListener listener, int evaluationDetail,
boolean hitBreakpoints) throws DebugException {
traceCaller(expression.getSnippet(), thread);
IRuntimeContext context = null;
if (thisContext instanceof IJavaArray) {
context = new ArrayRuntimeContext((IJavaArray) thisContext, thread,
getJavaProject());
} else {
context = new JavaObjectRuntimeContext(thisContext,
getJavaProject(), thread);
}
doEvaluation(expression, context, thread, listener, evaluationDetail,
hitBreakpoints);
}
/**
* Evaluates the given expression in the given thread and the given runtime
* context.
*/
private void doEvaluation(ICompiledExpression expression,
IRuntimeContext context, IJavaThread thread,
IEvaluationListener listener, int evaluationDetail,
boolean hitBreakpoints) throws DebugException {
if (expression instanceof InstructionSequence) {
// don't queue explicit evaluation if the thread is all ready
// performing an evaluation.
if (thread.isSuspended() && ((JDIThread) thread).isInvokingMethod()
|| thread.isPerformingEvaluation()
&& evaluationDetail == DebugEvent.EVALUATION) {
EvaluationResult result = new EvaluationResult(this,
expression.getSnippet(), thread);
result.addError(EvaluationEngineMessages.ASTEvaluationEngine_Cannot_perform_nested_evaluations);
listener.evaluationComplete(result);
return;
}
thread.queueRunnable(new EvalRunnable(
(InstructionSequence) expression, thread, context,
listener, evaluationDetail, hitBreakpoints));
} else {
throw new DebugException(
new Status(
IStatus.ERROR,
JDIDebugPlugin.getUniqueIdentifier(),
IStatus.OK,
EvaluationEngineMessages.ASTEvaluationEngine_AST_evaluation_engine_cannot_evaluate_expression,
null));
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.eval.IAstEvaluationEngine#getCompiledExpression
* (java.lang.String, org.eclipse.jdt.debug.core.IJavaStackFrame)
*/
@Override
public ICompiledExpression getCompiledExpression(String snippet,
IJavaStackFrame frame) {
IJavaProject javaProject = getJavaProject();
RuntimeContext context = new RuntimeContext(javaProject, frame);
EvaluationSourceGenerator mapper = null;
CompilationUnit unit = null;
try {
List<IJavaVariable> localsVar = new ArrayList<>();
localsVar.addAll(Arrays.asList(context.getLocals()));
IJavaObject thisClass = context.getThis();
IVariable[] innerClassFields; // For anonymous classes, getting variables from outer class
if (null != thisClass) {
innerClassFields = extractVariables(thisClass);
} else {
innerClassFields = new IVariable[0];
}
List<IVariable> lambdaFrameVariables = LambdaUtils.getLambdaFrameVariables(frame);
int numLocalsVar = localsVar.size();
Set<String> names = new HashSet<>();
// ******
// to hide problems with local variable declare as instance of Local
// Types
// and to remove locals with duplicate names
// IJavaVariable[] locals = new IJavaVariable[numLocalsVar];
IJavaVariable[] locals = new IJavaVariable[numLocalsVar + innerClassFields.length + lambdaFrameVariables.size()];
String[] localVariablesWithNull = new String[numLocalsVar + innerClassFields.length + lambdaFrameVariables.size()];
int numLocals = 0;
for (int i = 0; i < numLocalsVar; i++) {
IJavaVariable variable = localsVar.get(i);
if (!isLocalType(variable.getSignature()) && !names.contains(variable.getName())) {
locals[numLocals] = variable;
names.add(variable.getName());
localVariablesWithNull[numLocals++] = variable.getName();
}
}
/*
* If we are in a lambda frame, the variable context is not complete; names of outer-scope variables are mangled by the compiler. So we
* check variables one stack frame above the lambda frames, in order to also include outer-scope variables. This is necessary to use local
* variables defined in a method, within a breakpoint condition inside a lambda also defined in that method.
*/
for (IVariable variable : lambdaFrameVariables) {
if (variable instanceof IJavaVariable && !isLambdaOrImplicitVariable(variable)) {
IJavaVariable javaVariable = (IJavaVariable) variable;
final boolean lambdaField = LambdaUtils.isLambdaField(variable);
String name = variable.getName();
String variableName = (lambdaField && name.startsWith(ANONYMOUS_VAR_PREFIX)) ? name.substring(ANONYMOUS_VAR_PREFIX.length()) : name;
if (variableName != null && (!variableName.contains("$") || lambdaField)) { //$NON-NLS-1$
if (!isLocalType(javaVariable.getSignature()) && !names.contains(variableName)) {
locals[numLocals] = javaVariable;
names.add(variableName);
localVariablesWithNull[numLocals++] = variableName;
}
}
}
}
// Adding outer class variables to inner class scope
for (IVariable var : innerClassFields) {
if (var instanceof IJavaVariable && var.getName().startsWith(ANONYMOUS_VAR_PREFIX)) {
String name = var.getName().substring(ANONYMOUS_VAR_PREFIX.length());
if (!names.contains(name)) {
locals[numLocals] = (IJavaVariable) var;
names.add(name);
localVariablesWithNull[numLocals++] = name;
}
}
}
// to solve and remove
// ******
String[] localTypesNames = new String[numLocals];
for (int i = 0; i < numLocals; i++) {
localTypesNames[i] = getFixedUnresolvableGenericTypes(locals[i]);
}
// Copying local variables removing the nulls in the last
// String[] localVariables = Arrays.clonesub(localVariablesWithNull, names.size());
String[] localVariables = new String[names.size()];
System.arraycopy(localVariablesWithNull, 0, localVariables, 0, localVariables.length);
mapper = new EvaluationSourceGenerator(localTypesNames,
localVariables, snippet, getJavaProject());
// Compile in context of declaring type to get proper visibility of
// locals and members.
// Compiling in context of receiving type potentially provides
// access to more members,
// but does not allow access to privates members in declaring type
IJavaReferenceType receivingType = frame.getReferenceType();
// currently disabled - see bugs 99416 and 106492
// if (frame.isStatic()) {
// receivingType= frame.getReferenceType();
// } else {
// receivingType= (IJavaReferenceType)
// frame.getThis().getJavaType();
// }
unit = parseCompilationUnit(
mapper.getSource(receivingType, frame.getLineNumber(), javaProject,
frame.isStatic()).toCharArray(),
mapper.getCompilationUnitName(), javaProject);
} catch (CoreException e) {
InstructionSequence expression = new InstructionSequence(snippet);
expression.addError(e.getStatus().getMessage());
return expression;
}
return createExpressionFromAST(snippet, mapper, unit);
}
private IVariable[] extractVariables(IJavaObject thisClass) throws DebugException {
IVariable[] vars = thisClass.getVariables();
List<IVariable> varList = new ArrayList<>(Arrays.asList(vars));
varList.addAll(Arrays.asList(SyntheticVariableUtils.findSyntheticVariables(vars)));
return varList.toArray(new IVariable[0]);
}
private String getFixedUnresolvableGenericTypes(IJavaVariable variable) throws DebugException {
StringBuilder fixedSignature = new StringBuilder();
scanAndFixSignature(variable.getGenericSignature(), Signature.toString(variable.getSignature()), fixedSignature);
return fixedSignature.toString();
}
private void scanAndFixSignature(String genericSignature, String erasureSignature, StringBuilder fixedSignature) {
/*
* This actually fix variables which are type of Generic Types which cannot be resolved to a type in the current content. For example variable
* type like P_OUT in java.util.stream.ReferencePipeline.filter(Predicate<? super P_OUT>)
*
* and also generic signature such as Ljava/util/function/Predicate<+Ljava/util/List<Ljava/lang/Integer;>;>; Ljava/util/Comparator<-TT;>;
* which will fail the properly resolved to the type.
*/
if (genericSignature.startsWith(String.valueOf(Signature.C_TYPE_VARIABLE)) ||
genericSignature.startsWith(String.valueOf(Signature.C_CAPTURE)) ||
genericSignature.startsWith(String.valueOf(Signature.C_STAR)) ||
genericSignature.startsWith(String.valueOf(Signature.C_SUPER)))
{
fixedSignature.append(toDotQualified(erasureSignature));
return;
}
if(genericSignature.startsWith(String.valueOf(Signature.C_EXTENDS))) {
fixedSignature.append(toDotQualified(Signature.toString(getUpperBoundTypeSignature(genericSignature))));
return;
}
// we have a proper type which might be parameterized so extract the type FQN
fixedSignature.append(toDotQualified(Signature.toString(Signature.getTypeErasure(genericSignature))));
String[] typeArguments = Signature.getTypeArguments(genericSignature);
if (typeArguments.length > 0) {
fixedSignature.append(Signature.C_GENERIC_START);
for (int i = 0; i < typeArguments.length; i++) {
if (i > 0) {
fixedSignature.append(',');
}
scanAndFixSignature(typeArguments[i], QN_OBJECT, fixedSignature);
}
fixedSignature.append(Signature.C_GENERIC_END);
}
}
private String toDotQualified(String fqn) {
return fqn.replace('/', '.');
}
private String getUpperBoundTypeSignature(String typeParamaterSignature) {
// +Ljava/util/List<Ljava/lang/Integer;>;
return String.valueOf(getBoudTypeParameterSignature(typeParamaterSignature.toCharArray(), Signature.C_EXTENDS));
}
private char[] getBoudTypeParameterSignature(char[] typeParamaterSignature, char boundType) {
if (typeParamaterSignature.length < 2 || typeParamaterSignature[0] != boundType) {
throw new IllegalArgumentException(Signature.toString(String.valueOf(typeParamaterSignature)));
}
return CharOperation.subarray(typeParamaterSignature, 1, typeParamaterSignature.length);
}
private CompilationUnit parseCompilationUnit(char[] source,
String unitName, IJavaProject project) {
return parseCompilationUnit(source, unitName, project, Collections.EMPTY_MAP);
}
private CompilationUnit parseCompilationUnit(char[] source,
String unitName, IJavaProject project, Map<String, String> extraCompileOptions) {
ASTParser parser = ASTParser.newParser(AST.JLS15);
parser.setSource(source);
parser.setUnitName(unitName);
parser.setProject(project);
parser.setResolveBindings(true);
Map<String, String> options = EvaluationSourceGenerator
.getCompilerOptions(project);
options = new LinkedHashMap<>(options);
for (Entry<String, String> extraCompileOption : extraCompileOptions.entrySet()) {
options.put(extraCompileOption.getKey(), extraCompileOption.getValue());
}
parser.setCompilerOptions(options);
return (CompilationUnit) parser.createAST(null);
}
// ******
// to hide problems with local variable declare as instance of Local Types
private boolean isLocalType(String typeName) {
StringTokenizer strTok = new StringTokenizer(typeName, "$"); //$NON-NLS-1$
strTok.nextToken();
while (strTok.hasMoreTokens()) {
char char0 = strTok.nextToken().charAt(0);
if ('0' <= char0 && char0 <= '9') {
return true;
}
}
return false;
}
// ******
/**
* Returns a compiled expression for an evaluation in the context of an
* array as a receiver.
*/
private ICompiledExpression getCompiledExpression(String snippet,
IJavaArrayType arrayType) {
EvaluationSourceGenerator mapper = null;
CompilationUnit unit = null;
try {
IJavaProject javaProject = getJavaProject();
// replace all occurrences of 'this' with '_a_t'
String newSnippet = replaceThisReferences(snippet);
int dimension = 1;
IJavaType componentType = arrayType.getComponentType();
while (componentType instanceof IJavaArrayType) {
componentType = ((IJavaArrayType) componentType)
.getComponentType();
dimension++;
}
// Primitive arrays are evaluated in the context of Object.
// Arrays with a base component type of a class or interface are
// treated
// as Object arrays and evaluated in Object.
String recTypeName = QN_OBJECT;
String typeName = arrayType.getName();
if (componentType instanceof IJavaReferenceType) {
StringBuilder buf = new StringBuilder();
buf.append(QN_OBJECT);
for (int i = 0; i < dimension; i++) {
buf.append("[]"); //$NON-NLS-1$
}
typeName = buf.toString();
}
String[] localTypesNames = new String[] { typeName };
String[] localVariables = new String[] { ArrayRuntimeContext.ARRAY_THIS_VARIABLE };
mapper = new EvaluationSourceGenerator(localTypesNames,
localVariables, newSnippet, getJavaProject());
int index = typeName.indexOf('$');
// if the argument is an inner type, compile in context of outer
// type so type is visible
if (index >= 0) {
recTypeName = typeName.substring(0, index);
}
IJavaType[] javaTypes = getDebugTarget().getJavaTypes(recTypeName);
if (javaTypes.length > 0) {
IJavaReferenceType recType = (IJavaReferenceType) javaTypes[0];
unit = parseCompilationUnit(
mapper.getSource(recType, -1, getJavaProject(), false)
.toCharArray(),
mapper.getCompilationUnitName(), javaProject);
} else {
IStatus status = new Status(IStatus.ERROR,
JDIDebugPlugin.getUniqueIdentifier(),
JDIDebugPlugin.ERROR,
EvaluationEngineMessages.ASTEvaluationEngine_1, null);
throw new CoreException(status);
}
} catch (CoreException e) {
InstructionSequence expression = new InstructionSequence(snippet);
expression.addError(e.getStatus().getMessage());
return expression;
}
return createExpressionFromAST(snippet, mapper, unit);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.eval.IAstEvaluationEngine#getCompiledExpression
* (java.lang.String, org.eclipse.jdt.debug.core.IJavaObject)
*/
@Override
public ICompiledExpression getCompiledExpression(String snippet,
IJavaObject thisContext) {
try {
if (thisContext instanceof IJavaArray) {
return getCompiledExpression(snippet,
(IJavaArrayType) thisContext.getJavaType());
}
return getCompiledExpression(snippet,
(IJavaReferenceType) thisContext.getJavaType());
} catch (DebugException e) {
InstructionSequence expression = new InstructionSequence(snippet);
expression.addError(e.getStatus().getMessage());
return expression;
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.eval.IAstEvaluationEngine#getCompiledExpression
* (java.lang.String, org.eclipse.jdt.debug.core.IJavaType)
*/
@Override
public ICompiledExpression getCompiledExpression(String snippet,
IJavaReferenceType type) {
return getCompiledExpression(snippet, type, Collections.EMPTY_MAP);
}
@Override
public ICompiledExpression getCompiledExpression(String snippet,
IJavaReferenceType type, Map<String, String> compileOptions) {
if (type instanceof IJavaArrayType) {
return getCompiledExpression(snippet, (IJavaArrayType) type);
}
IJavaProject javaProject = getJavaProject();
EvaluationSourceGenerator mapper = null;
CompilationUnit unit = null;
mapper = new EvaluationSourceGenerator(new String[0], new String[0],
snippet, getJavaProject());
try {
unit = parseCompilationUnit(
mapper.getSource(type, -1, javaProject, false).toCharArray(),
mapper.getCompilationUnitName(), javaProject, compileOptions);
} catch (CoreException e) {
InstructionSequence expression = new InstructionSequence(snippet);
expression.addError(e.getStatus().getMessage());
return expression;
}
return createExpressionFromAST(snippet, mapper, unit);
}
/**
* Creates a compiled expression for the given snippet using the given
* mapper and compilation unit (AST).
*
* @param snippet
* the code snippet to be compiled
* @param mapper
* the object which will be used to create the expression
* @param unit
* the compilation unit (AST) generated for the snippet
*/
private ICompiledExpression createExpressionFromAST(String snippet,
EvaluationSourceGenerator mapper, CompilationUnit unit) {
IProblem[] problems = unit.getProblems();
if (problems.length != 0) {
boolean snippetError = false;
boolean runMethodError = false;
InstructionSequence errorSequence = new InstructionSequence(snippet);
int codeSnippetStart = mapper.getSnippetStart();
int codeSnippetEnd = codeSnippetStart
+ mapper.getSnippet().length();
int runMethodStart = mapper.getRunMethodStart();
int runMethodEnd = runMethodStart + mapper.getRunMethodLength();
for (IProblem problem : problems) {
int errorOffset = problem.getSourceStart();
int problemId = problem.getID();
if (problemId == IProblem.IsClassPathCorrect) {
errorSequence.addError(problem.getMessage());
snippetError = true;
}
if (problemId == IProblem.VoidMethodReturnsValue
|| problemId == IProblem.NotVisibleMethod
|| problemId == IProblem.NotVisibleConstructor
|| problemId == IProblem.NotVisibleField
|| problemId == IProblem.NotVisibleType
|| problemId == IProblem.UnexpectedStaticModifierForMethod) {
continue;
}
if (problem.isError()) {
if (codeSnippetStart <= errorOffset
&& errorOffset <= codeSnippetEnd) {
errorSequence.addError(problem.getMessage());
snippetError = true;
} else if (runMethodStart <= errorOffset
&& errorOffset <= runMethodEnd) {
runMethodError = true;
DebugPlugin.log(new Status(IStatus.WARNING, DebugPlugin.getUniqueIdentifier(), "Compile error during code evaluation: " //$NON-NLS-1$
+ problem.getMessage()));
}
}
}
if (snippetError || runMethodError) {
if (runMethodError) {
errorSequence
.addError(EvaluationEngineMessages.ASTEvaluationEngine_Evaluations_must_contain_either_an_expression_or_a_block_of_well_formed_statements_1);
}
return errorSequence;
}
}
ASTInstructionCompiler visitor = new ASTInstructionCompiler(
mapper.getSnippetStart(), snippet, getJavaProject());
unit.accept(visitor);
return visitor.getInstructions();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.eval.IEvaluationEngine#getJavaProject()
*/
@Override
public IJavaProject getJavaProject() {
return fProject;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.eval.IEvaluationEngine#getDebugTarget()
*/
@Override
public IJavaDebugTarget getDebugTarget() {
return fDebugTarget;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.eval.IEvaluationEngine#dispose()
*/
@Override
public void dispose() {
}
class EvalRunnable implements Runnable {
private InstructionSequence fExpression;
private IJavaThread fThread;
private int fEvaluationDetail;
private boolean fHitBreakpoints;
private IRuntimeContext fContext;
private IEvaluationListener fListener;
private boolean fDisableGcOnResult;
public EvalRunnable(InstructionSequence expression, IJavaThread thread,
IRuntimeContext context, IEvaluationListener listener,
int evaluationDetail, boolean hitBreakpoints) {
fExpression = expression;
fThread = thread;
fContext = context;
fListener = listener;
fEvaluationDetail = (evaluationDetail & EVALUATION_DETAIL_BITMASK);
fHitBreakpoints = hitBreakpoints;
fDisableGcOnResult = (evaluationDetail & IAstEvaluationEngine.DISABLE_GC_ON_RESULT) != 0;
}
@Override
public void run() {
if (JDIDebugOptions.DEBUG_AST_EVAL) {
StringBuilder buf = new StringBuilder();
buf.append(JDIDebugOptions.FORMAT.format(new Date()));
buf.append(" : AST Evaluation"); //$NON-NLS-1$
buf.append("\n\tExpression: "); //$NON-NLS-1$
buf.append(fExpression.getSnippet());
buf.append("\n\tThread: "); //$NON-NLS-1$
try {
String name = fThread.getName();
buf.append('[');
buf.append(name);
buf.append("] "); //$NON-NLS-1$
} catch (DebugException e) {
}
buf.append(fThread.toString());
buf.append("\n\tDetail: "); //$NON-NLS-1$
if (fEvaluationDetail == DebugEvent.EVALUATION) {
buf.append("EVALUATION"); //$NON-NLS-1$
} else if (fEvaluationDetail == DebugEvent.EVALUATION_IMPLICIT) {
buf.append("EVALUATION_IMPLICIT"); //$NON-NLS-1$
} else {
buf.append(fEvaluationDetail);
}
buf.append(" Hit Breakpoints: "); //$NON-NLS-1$
buf.append(fHitBreakpoints);
JDIDebugOptions.trace(buf.toString());
}
EvaluationResult result = new EvaluationResult(
ASTEvaluationEngine.this, fExpression.getSnippet(), fThread);
if (fExpression.hasErrors()) {
String[] errors = fExpression.getErrorMessages();
for (String error : errors) {
result.addError(error);
}
evaluationFinished(result);
if (JDIDebugOptions.DEBUG_AST_EVAL) {
StringBuilder buf = new StringBuilder();
buf.append("\tErrors: "); //$NON-NLS-1$
for (int i = 0; i < errors.length; i++) {
if (i > 0) {
buf.append('\n');
}
buf.append("\t\t"); //$NON-NLS-1$
buf.append(errors[i]);
}
JDIDebugOptions.trace(buf.toString());
}
return;
}
final Interpreter interpreter = new Interpreter(fExpression,
fContext);
class EvaluationRunnable implements IEvaluationRunnable, ITerminate {
CoreException fException;
boolean fTerminated = false;
@Override
public void run(IJavaThread jt, IProgressMonitor pm) {
EventFilter filter = new EventFilter();
try {
DebugPlugin.getDefault().addDebugEventFilter(filter);
interpreter.execute(fDisableGcOnResult);
} catch (CoreException exception) {
fException = exception;
if (fEvaluationDetail == DebugEvent.EVALUATION
&& exception.getStatus().getException() instanceof InvocationException) {
// print the stack trace for the exception if an
// *explicit* evaluation
InvocationException invocationException = (InvocationException) exception
.getStatus().getException();
ObjectReference exObject = invocationException
.exception();
IJavaObject modelObject = (IJavaObject) JDIValue
.createValue(
(JDIDebugTarget) getDebugTarget(),
exObject);
try {
modelObject
.sendMessage(
"printStackTrace", "()V", null, jt, false); //$NON-NLS-1$ //$NON-NLS-2$
} catch (DebugException e) {
// unable to print stack trace
}
}
} finally {
DebugPlugin.getDefault().removeDebugEventFilter(filter);
}
}
@Override
public void terminate() {
fTerminated = true;
interpreter.stop();
}
@Override
public boolean canTerminate() {
return true;
}
@Override
public boolean isTerminated() {
return false;
}
public CoreException getException() {
return fException;
}
}
EvaluationRunnable er = new EvaluationRunnable();
CoreException exception = null;
long start = System.currentTimeMillis();
try {
fThread.runEvaluation(er, null, fEvaluationDetail,
fHitBreakpoints);
} catch (DebugException e) {
exception = e;
}
long end = System.currentTimeMillis();
IJavaValue value = interpreter.getResult();
if (exception == null) {
exception = er.getException();
}
result.setTerminated(er.fTerminated);
if (exception != null) {
if (JDIDebugOptions.DEBUG_AST_EVAL) {
StringBuilder buf = new StringBuilder();
buf.append("\tException: "); //$NON-NLS-1$
buf.append(exception.toString());
JDIDebugOptions.trace(buf.toString());
}
if (exception instanceof DebugException) {
result.setException((DebugException) exception);
} else {
result.setException(new DebugException(exception
.getStatus()));
}
} else {
if (value != null) {
result.setValue(value);
if (JDIDebugOptions.DEBUG_AST_EVAL) {
StringBuilder buf = new StringBuilder();
buf.append("\tResult: "); //$NON-NLS-1$
buf.append(value);
JDIDebugOptions.trace(buf.toString());
}
} else {
result.addError(EvaluationEngineMessages.ASTEvaluationEngine_An_unknown_error_occurred_during_evaluation);
}
}
if (JDIDebugOptions.DEBUG_AST_EVAL) {
StringBuilder buf = new StringBuilder();
buf.append("\tDuration: "); //$NON-NLS-1$
buf.append(end - start);
buf.append("ms"); //$NON-NLS-1$
JDIDebugOptions.trace(buf.toString());
}
evaluationFinished(result);
}
private void evaluationFinished(IEvaluationResult result) {
// only notify if plug-in not yet shutdown - bug# 8693
if (JDIDebugPlugin.getDefault() != null) {
fListener.evaluationComplete(result);
}
}
}
/**
* Replaces references to 'this' with the 'array_this' variable.
*
* @param snippet
* code snippet
* @return snippet with 'this' references replaced
*/
public static String replaceThisReferences(String snippet) {
// replace all occurrences of 'this' with 'array_this'
StringBuilder updatedSnippet = new StringBuilder();
Matcher matcher = fgThisPattern.matcher(snippet);
int start = 0;
while (matcher.find()) {
int end = matcher.start(2);
updatedSnippet.append(snippet.substring(start, end));
updatedSnippet.append(ArrayRuntimeContext.ARRAY_THIS_VARIABLE);
start = end + 4;
}
if (start < snippet.length()) {
updatedSnippet.append(snippet.substring(start, snippet.length()));
}
return updatedSnippet.toString();
}
private static boolean isLambdaOrImplicitVariable(IVariable variable) {
boolean isLambdaOrImplicitVariable = variable instanceof JDILambdaVariable || variable instanceof JDIReturnValueVariable
|| variable instanceof JDIThisVariable;
return isLambdaOrImplicitVariable;
}
}