Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix access lookup in lambda for noclasspath mode. #814

Merged
merged 25 commits into from
Sep 8, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
634ee11
Enhanced access lookup in lambda for noclasspath mode.
msteinbeck Sep 2, 2016
77b49a1
fixed a comment.
Sep 3, 2016
ae3b66a
Properly handle parameter accesses in anonymous classes.
Sep 3, 2016
99a3ba6
Fixed source position calculation in `createTypeAccessNoClasspath`
Sep 3, 2016
65f2fba
Added first lines to search for statically imported variables and var…
Sep 4, 2016
1de6f8e
Added a simple test checking a type access in a lambda.
Sep 4, 2016
5a3bb7a
Create concrete variable access.
Sep 4, 2016
54b6edf
Added a test showing that a field access is properly handled in lambdas.
Sep 4, 2016
f12fbe8
Find variables declared in super classes/fields.
Sep 4, 2016
91c1211
Added several Tests for fields accesses.
msteinbeck Sep 5, 2016
da0bc7d
Improved variable lookup for fields.
msteinbeck Sep 5, 2016
efcb5b8
Added an example that statically imports a field from an interface.
msteinbeck Sep 5, 2016
fc91399
Added an example that statically imports a field from an unknown class.
msteinbeck Sep 5, 2016
741998b
Avoid Exception in getVariableDeclaration.
msteinbeck Sep 5, 2016
e37b376
Added an example that requires super class/interface lookup for varia…
msteinbeck Sep 5, 2016
838a958
Fixed scope checking for declared variables.
msteinbeck Sep 6, 2016
5a710c4
Use EarlyTerminatingScanner instead of AbstractFilter.
msteinbeck Sep 6, 2016
20a46a9
Consider all types CtVariable and fixed documentation of `createVaria…
msteinbeck Sep 6, 2016
bad5a9d
Create a more accurate CtField from super class/interface.
msteinbeck Sep 6, 2016
249a107
Removed unused import from JDTTreeBuilderHelper.
msteinbeck Sep 6, 2016
bf081de
Added some heuristics for statically imported fields in noclasspath m…
msteinbeck Sep 6, 2016
05fbc89
Added missing EOF.
msteinbeck Sep 7, 2016
2c126dd
Fixed expected and actual value in JUnit tests.
msteinbeck Sep 7, 2016
0420e46
Fixed typo.
msteinbeck Sep 7, 2016
5e5a776
Replaced some spaces with tabs.
msteinbeck Sep 7, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
275 changes: 238 additions & 37 deletions src/main/java/spoon/support/compiler/jdt/ContextBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,42 @@
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import spoon.compiler.Environment;
import spoon.reflect.code.CtCatchVariable;
import spoon.reflect.code.CtConstructorCall;
import spoon.reflect.code.CtExpression;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtStatement;
import spoon.reflect.cu.CompilationUnit;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtExecutable;
import spoon.reflect.declaration.CtField;
import spoon.reflect.declaration.CtPackage;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.CtTypedElement;
import spoon.reflect.declaration.CtVariable;
import spoon.reflect.factory.ClassFactory;
import spoon.reflect.factory.CoreFactory;
import spoon.reflect.factory.FieldFactory;
import spoon.reflect.factory.InterfaceFactory;
import spoon.reflect.factory.TypeFactory;
import spoon.reflect.reference.CtReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.DefaultJavaPrettyPrinter;
import spoon.reflect.visitor.EarlyTerminatingScanner;
import spoon.support.reflect.reference.SpoonClassNotFoundException;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;

import static spoon.reflect.ModelElementContainerDefaultCapacities.CASTS_CONTAINER_DEFAULT_CAPACITY;
import static java.lang.String.format;

public class ContextBuilder {

Expand Down Expand Up @@ -112,56 +129,240 @@ void exit(ASTNode node) {
}
}

@SuppressWarnings("unchecked")
<T> CtLocalVariable<T> getLocalVariableDeclaration(final String name) {
for (ASTPair astPair : this.stack) {
// TODO check if the variable is visible from here

EarlyTerminatingScanner<CtLocalVariable<?>> scanner = new EarlyTerminatingScanner<CtLocalVariable<?>>() {
@Override
public <T> void visitCtLocalVariable(CtLocalVariable<T> localVariable) {
if (name.equals(localVariable.getSimpleName())) {
setResult(localVariable);
terminate();
return;
final Class<CtLocalVariable<T>> clazz = (Class<CtLocalVariable<T>>)
jdtTreeBuilder.getFactory().Core().createLocalVariable().getClass();
final CtLocalVariable<T> localVariable =
this.<T, CtLocalVariable<T>>getVariableDeclaration(name, clazz);
if (localVariable == null) {
// note: this happens when using the new try(vardelc) structure
this.jdtTreeBuilder.getLogger().error(
format("Could not find declaration for local variable %s at %s",
name, stack.peek().element.getPosition()));
}
return localVariable;
}

@SuppressWarnings("unchecked")
<T> CtCatchVariable<T> getCatchVariableDeclaration(final String name) {
final Class<CtCatchVariable<T>> clazz = (Class<CtCatchVariable<T>>)
jdtTreeBuilder.getFactory().Core().createCatchVariable().getClass();
final CtCatchVariable<T> catchVariable =
this.<T, CtCatchVariable<T>>getVariableDeclaration(name, clazz);
if (catchVariable == null) {
// note: this happens when using the new try(vardelc) structure
this.jdtTreeBuilder.getLogger().error(
format("Could not find declaration for catch variable %s at %s",
name, stack.peek().element.getPosition()));
}
return catchVariable;
}

<T> CtVariable<T> getVariableDeclaration(final String name) {
final CtVariable<T> variable = this.<T, CtVariable<T>>getVariableDeclaration(name, null);
if (variable == null) {
// note: this happens when using the new try(vardelc) structure
this.jdtTreeBuilder.getLogger().error(
format("Could not find declaration for variable %s at %s",
name, stack.peek().element.getPosition()));
}
return variable;
}

@SuppressWarnings("unchecked")
private <T, U extends CtVariable<T>> U getVariableDeclaration(
final String name, final Class<U> clazz) {
final CoreFactory coreFactory = jdtTreeBuilder.getFactory().Core();
final TypeFactory typeFactory = jdtTreeBuilder.getFactory().Type();
final ClassFactory classFactory = jdtTreeBuilder.getFactory().Class();
final InterfaceFactory interfaceFactory = jdtTreeBuilder.getFactory().Interface();
final FieldFactory fieldFactory = jdtTreeBuilder.getFactory().Field();
final ReferenceBuilder referenceBuilder = jdtTreeBuilder.getReferencesBuilder();
final Environment environment = jdtTreeBuilder.getFactory().getEnvironment();
// there is some extra work to do if we are looking for CtFields (and subclasses)
final boolean lookingForFields = clazz == null
|| coreFactory.createField().getClass().isAssignableFrom(clazz);

// try to find the variable on stack beginning with the most recent element
for (final ASTPair astPair : stack) {
// the variable may have been declared directly by one of these elements
final ScopeRespectingVariableScanner<U> scanner =
new ScopeRespectingVariableScanner(name, clazz);
astPair.element.accept(scanner);
if (scanner.getResult() != null) {
return scanner.getResult();
}

// the variable may have been declared in a super class/interface
if (lookingForFields && astPair.node instanceof TypeDeclaration) {
final TypeDeclaration nodeDeclaration = (TypeDeclaration) astPair.node;
final Deque<ReferenceBinding> referenceBindings = new ArrayDeque<>();
// add super class if any
if (nodeDeclaration.superclass != null
&& nodeDeclaration.superclass.resolvedType instanceof ReferenceBinding) {
referenceBindings.push((ReferenceBinding) nodeDeclaration.superclass.resolvedType);
}
// add interfaces if any
if (nodeDeclaration.superInterfaces != null) {
for (final TypeReference tr : nodeDeclaration.superInterfaces) {
if (tr.resolvedType instanceof ReferenceBinding) {
referenceBindings.push((ReferenceBinding) tr.resolvedType);
}
}
}

while (!referenceBindings.isEmpty()) {
final ReferenceBinding referenceBinding = referenceBindings.pop();
for (final FieldBinding fieldBinding : referenceBinding.fields()) {
if (name.equals(new String(fieldBinding.readableName()))) {
final String qualifiedNameOfParent =
new String(referenceBinding.readableName());
final CtType parentOfField = referenceBinding.isClass()
? classFactory.create(qualifiedNameOfParent)
: interfaceFactory.create(qualifiedNameOfParent);
return (U) fieldFactory.create(parentOfField,
JDTTreeBuilderQuery.getModifiers(fieldBinding.modifiers),
referenceBuilder.getTypeReference(fieldBinding.type),
name);
}
}
// add super class if any
final ReferenceBinding superclass = referenceBinding.superclass();
if (superclass != null) {
referenceBindings.push(superclass);
}
// add interfaces if any
final ReferenceBinding[] interfaces = referenceBinding.superInterfaces();
if (interfaces != null) {
for (ReferenceBinding rb : interfaces) {
referenceBindings.push(rb);
}
}
}
}
}

// the variable may have been imported statically from another class/interface
if (lookingForFields) {
final CtReference potentialReferenceToField =
referenceBuilder.getDeclaringReferenceFromImports(name.toCharArray());
if (potentialReferenceToField != null
&& potentialReferenceToField instanceof CtTypeReference) {
final CtTypeReference typeReference = (CtTypeReference) potentialReferenceToField;
try {
final Class classOfType = typeReference.getActualClass();
if (classOfType != null) {
final CtType declaringTypeOfField = typeReference.isInterface()
? interfaceFactory.get(classOfType) : classFactory.get(classOfType);
final CtField field = declaringTypeOfField.getField(name);
if (field != null) {
return (U) field;
}
}
} catch (final SpoonClassNotFoundException scnfe) {
// in noclasspath mode we do some heuristics to determine if `name` could be a
// field that has been imported statically from another class (or interface).
if (environment.getNoClasspath()) {
// if `potentialReferenceToField` is a `CtTypeReference` then `name` must
// have been imported statically. Otherwise, `potentialReferenceToField`
// would be a CtPackageReference!

// if `name` consists only of upper case characters separated by '_', we
// assume a constant value according to JLS.
if (name.toUpperCase().equals(name)) {
final CtType parentOfField =
classFactory.create(typeReference.getQualifiedName());
// it is the best thing we can do
final CtField field = coreFactory.createField();
field.setParent(parentOfField);
field.setSimpleName(name);
// it is the best thing we can do
field.setType(typeFactory.nullType());
return (U) field;
}
}
super.visitCtLocalVariable(localVariable);
}
};
astPair.element.accept(scanner);
CtLocalVariable<T> var = (CtLocalVariable<T>) scanner.getResult();
if (var != null) {
return var;
}
}
// note: this happens when using the new try(vardelc) structure
this.jdtTreeBuilder.getLogger().error("could not find declaration for local variable " + name + " at " + this.stack.peek().element.getPosition());

return null;
}

<T> CtCatchVariable<T> getCatchVariableDeclaration(final String name) {
for (ASTPair astPair : this.stack) {
EarlyTerminatingScanner<CtCatchVariable<?>> scanner = new EarlyTerminatingScanner<CtCatchVariable<?>>() {
@Override
public <T> void visitCtCatchVariable(CtCatchVariable<T> catchVariable) {
if (name.equals(catchVariable.getSimpleName())) {
setResult(catchVariable);
terminate();
return;
/**
* An {@link EarlyTerminatingScanner} that is supposed to find a {@link CtVariable} with
* specific name respecting the current scope given by {@link ContextBuilder#stack}.

* @param <T> The actual type of the {@link CtVariable} we are looking for. Examples include
* {@link CtLocalVariable}, {@link CtField}, and so on.
*/
private class ScopeRespectingVariableScanner<T extends CtVariable>
extends EarlyTerminatingScanner<T> {

/**
* The class object of {@link T} that is required to filter particular elements in
* {@link #scan(CtElement)}.
*/
private final Class<T> clazz;

/**
* The name of the variable we are looking for ({@link CtVariable#getSimpleName()}).
*/
final String name;

/**
* Creates a new {@link EarlyTerminatingScanner} that tries to find a {@link CtVariable}
* with name {@code pName} (using {@link CtVariable#getSimpleName()}) and upper type bound
* {@code pType}.
*
* @param pName The name of the variable we are looking for.
* @param pType {@link T}'s class object ({@link Object#getClass()}). {@link null} values
* are permitted and indicate that we are looking for any subclass of
* {@link CtVariable} (including {@link CtVariable} itself).
*/
ScopeRespectingVariableScanner(final String pName, final Class<T> pType) {
clazz = (Class<T>) (pType == null ? CtVariable.class : pType);
name = pName;
}

@Override
public void scan(final CtElement element) {
if (element != null && clazz.isAssignableFrom(element.getClass())) {
final T potentialVariable = (T) element;
if (name.equals(potentialVariable.getSimpleName())) {
// Since the AST is not completely available yet, we can not check if element's
// parent (ep) contains the innermost element of `stack` (ie). Therefore, we
// have to check if one of the following condition holds:
//
// 1) Does `stack` contain `ep`?
// 2) Is `ep` the body of one of `stack`'s CtExecutable elements?
//
// The first condition is easy to see. If `stack` contains `ep` then `ep` and
// all it's declared variables are in scope of `ie`. Unfortunately, there is a
// special case in which a variable (a CtLocalVariable) has been declared in a
// block (CtBlock) of, for instance, a method. Such a block is not contained in
// `stack`. This peculiarity calls for the second condition.
final CtElement parentOfPotentialVariable = potentialVariable.getParent();
for (final ASTPair astPair : stack) {
if (astPair.element == parentOfPotentialVariable) {
finish(potentialVariable);
return;
} else if (astPair.element instanceof CtExecutable) {
final CtExecutable executable = (CtExecutable) astPair.element;
if (executable.getBody() == parentOfPotentialVariable) {
finish(potentialVariable);
return;
}
}
}
super.visitCtCatchVariable(catchVariable);
}
};
astPair.element.accept(scanner);

CtCatchVariable<T> var = (CtCatchVariable<T>) scanner.getResult();
if (var != null) {
return null;
}
super.scan(element);
}
// note: this happens when using the new try(vardelc) structure
this.jdtTreeBuilder.getLogger().error("could not find declaration for catch variable " + name + " at " + this.stack.peek().element.getPosition());

return null;
private void finish(final T element) {
setResult(element);
terminate();
}
}
}
7 changes: 6 additions & 1 deletion src/main/java/spoon/support/compiler/jdt/JDTTreeBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
import spoon.reflect.code.CtCatch;
import spoon.reflect.code.CtConstructorCall;
import spoon.reflect.code.CtContinue;
import spoon.reflect.code.CtExpression;
import spoon.reflect.code.CtInvocation;
import spoon.reflect.code.CtLambda;
import spoon.reflect.code.CtLiteral;
Expand Down Expand Up @@ -1333,7 +1334,11 @@ public boolean visit(SingleNameReference singleNameReference, BlockScope scope)
context.enter(helper.createFieldAccessNoClasspath(singleNameReference), singleNameReference);
}
} else if (singleNameReference.binding == null) {
context.enter(helper.createVariableAccessNoClasspath(singleNameReference), singleNameReference);
CtExpression access = helper.createVariableAccessNoClasspath(singleNameReference);
if (access == null) {
access = helper.createTypeAccessNoClasspath(singleNameReference);
}
context.enter(access, singleNameReference);
}
return true;
}
Expand Down
Loading