Skip to content

Commit

Permalink
fix: better getDeclaration of CtLocalVariable (Fixes INRIA#868)
Browse files Browse the repository at this point in the history
  • Loading branch information
msteinbeck authored and sjd78 committed Nov 3, 2016
1 parent 00d6603 commit 0c1e98a
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,30 @@
package spoon.support.reflect.reference;

import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtStatement;
import spoon.reflect.code.CtStatementList;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtPackage;
import spoon.reflect.declaration.ParentNotInitializedException;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtLocalVariableReference;
import spoon.reflect.visitor.CtVisitor;
import spoon.reflect.visitor.filter.AbstractFilter;

public class CtLocalVariableReferenceImpl<T> extends CtVariableReferenceImpl<T> implements CtLocalVariableReference<T> {
import java.util.List;

/**
* An implementation for {@link CtLocalVariableReference}.
*/
public class CtLocalVariableReferenceImpl<T>
extends CtVariableReferenceImpl<T> implements CtLocalVariableReference<T> {

/**
* Id for serialization.
*/
private static final long serialVersionUID = 1L;

/**
* Default constructor.
*/
public CtLocalVariableReferenceImpl() {
super();
}
Expand All @@ -38,30 +52,62 @@ public void accept(CtVisitor visitor) {

@Override
public CtLocalVariable<T> getDeclaration() {
CtElement element = this;
CtLocalVariable<T> optional = null;
String name = getSimpleName();
// without a factory, we are not able to filter for local variables
final Factory factory = getFactory();
if (factory == null) {
return null;
}
final SimpleNameFilter filter = new SimpleNameFilter(factory);

// successively iterate through all parents of this reference and
// return first result (which must be the closest declaration
// respecting visible scope)
try {
do {
CtStatementList block = element.getParent(CtStatementList.class);
if (block == null) {
return null;
}
for (CtStatement ctStatement : block.getStatements()) {
if (ctStatement instanceof CtLocalVariable && ((CtLocalVariable) ctStatement).getSimpleName().equals(name)) {
optional = (CtLocalVariable) ctStatement;
CtElement parent = getParent();
// stop at `package` level to avoid lookups to foreign Java files
while (parent != null && !(parent instanceof CtPackage)) {
final List<CtLocalVariable<T>> localVariables =
parent.getElements(filter);
// since `parent` may be a class declaring multiple local
// variables with same name in different methods, we have to
// check if any of the findings is visible in current scope by
// validating that the parent of a finding is parent of this
// reference as well
for (final CtLocalVariable<T> lv : localVariables) {
if (hasParent(lv.getParent())) {
return lv;
}
}
element = block;
} while (optional == null);
} catch (ParentNotInitializedException e) {
return null;
parent = parent.getParent();
}
} catch (final ParentNotInitializedException e) {
// handle this case as 'not found'
}
return optional;
return null;
}

@Override
public CtLocalVariableReference<T> clone() {
return (CtLocalVariableReference<T>) super.clone();
}

/**
* A {@link spoon.reflect.visitor.Filter} that filters all
* {@link CtLocalVariable}s with simple name equals to
* {@link #getSimpleName()}.
*/
private final class SimpleNameFilter
extends AbstractFilter<CtLocalVariable<T>> {

@SuppressWarnings("unchecked")
SimpleNameFilter(final Factory pFactory) {
super((Class<CtLocalVariable<T>>)
pFactory.Core().createLocalVariable().getClass());
}

@Override
public boolean matches(final CtLocalVariable<T> element) {
return element.getSimpleName().equals(getSimpleName());
}
}
}
47 changes: 47 additions & 0 deletions src/test/java/spoon/test/reference/VariableAccessTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.junit.Test;
import spoon.Launcher;
import spoon.reflect.code.CtArrayWrite;
import spoon.reflect.code.CtLiteral;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtVariableAccess;
import spoon.reflect.declaration.CtClass;
Expand All @@ -13,6 +14,7 @@
import spoon.reflect.reference.CtLocalVariableReference;
import spoon.reflect.reference.CtParameterReference;
import spoon.reflect.reference.CtVariableReference;
import spoon.reflect.visitor.CtScanner;
import spoon.reflect.visitor.filter.AbstractReferenceFilter;
import spoon.reflect.visitor.filter.TypeFilter;
import spoon.test.reference.testclasses.Pozole;
Expand Down Expand Up @@ -146,6 +148,51 @@ public void testReferences() throws Exception {
System.out.println(make);
}

@Test
public void testReferenceToLocalVariableDeclaredInLoop() {
final class CtLocalVariableReferenceScanner extends CtScanner {
@Override
public <T> void visitCtLocalVariableReference(
final CtLocalVariableReference<T> reference) {
assertNotNull(reference.getDeclaration());
assertEquals(reference.getDeclaration().getSimpleName(),
reference.getSimpleName());
assertEquals(reference.getDeclaration().getType(),
reference.getType());
super.visitCtLocalVariableReference(reference);
}
}

final Launcher launcher = new Launcher();
launcher.getEnvironment().setNoClasspath(true);
launcher.addInputResource("src/test/resources/reference-test/ChangeScanner.java");
launcher.buildModel();
new CtLocalVariableReferenceScanner().scan(launcher.getModel().getRootPackage());
}

@Test
public void testMultipleDeclarationsOfLocalVariable() {
final class CtLocalVariableReferenceScanner extends CtScanner {
@Override
public <T> void visitCtLocalVariableReference(
final CtLocalVariableReference<T> reference) {
assertNotNull(reference.getDeclaration());
final CtLocalVariable decl = reference.getDeclaration();
assertEquals(decl.getPosition().getLine(), 7);
assertTrue(decl.getDefaultExpression() instanceof CtLiteral);
final CtLiteral literal = (CtLiteral) decl.getDefaultExpression();
assertEquals(literal.getValue(), 42);
super.visitCtLocalVariableReference(reference);
}
}

final Launcher launcher = new Launcher();
launcher.getEnvironment().setNoClasspath(true);
launcher.addInputResource("src/test/resources/reference-test/MultipleDeclarationsOfLocalVariable.java");
launcher.buildModel();
new CtLocalVariableReferenceScanner().scan(launcher.getModel().getRootPackage());
}

private CtMethod<Object> getMethod(Launcher launcher, CtClass<Object> a2) {
return a2.getMethod("b", launcher.getFactory().Type().integerPrimitiveType());
}
Expand Down
39 changes: 39 additions & 0 deletions src/test/resources/reference-test/ChangeScanner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package net.sf.jabref.collab;

import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import net.sf.jabref.*;
import net.sf.jabref.groups.*;
import net.sf.jabref.imports.*;


public class ChangeScanner extends Thread {


/**
* Finds the entry in neu best fitting the specified entry in old. If no entries get a score
* above zero, an entry is still returned.
* @param old EntrySorter
* @param neu EntrySorter
* @param index int
* @return BibtexEntry
*/
private BibtexEntry bestFit(EntrySorter old, EntrySorter neu, int index) {
double comp = -1;
int found = 0;
loop: for (int i=0; i<neu.getEntryCount(); i++) {
double res = Util.compareEntriesStrictly(old.getEntryAt(index),
neu.getEntryAt(i));
if (res > comp) {
comp = res;
found = i;
}
if (comp > 1)
break loop;
}
return neu.getEntryAt(found);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
public class MultipleDeclarationsOfLocalVariable {

public static void main(final String[] args) {
{
int j = -1;
}
int j = 42;
int i=j;
}
}

0 comments on commit 0c1e98a

Please sign in to comment.