在 Eclipse JDT 中使用 ASTVisitor 解析重载方法
我目前正在开展一个学术项目,该项目使用 ASTVisitor 来创建基本的调用树。
为此,需要将方法的调用与其声明相关联。
编辑:问题在很大程度上得到解决:所描述的错误仅出现在JUnit-Tests中,但不在独立插件中出现。它似乎与 ASTVisitors 的单元测试工作流程有关。我将在一段时间内进一步调查原因并在此发布答案。
下面的代码显示了一个最小的访问者,它将调用和关联的声明打印到控制台:
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.MethodInvocation;
/**
* Visits all method invocations and prints the declaration of the caller to
* console.
*/
public class InvocationLoggerASTVisitor extends ASTVisitor {
@Override
public boolean visit(MethodInvocation methodInvocation) {
if (methodInvocation.resolveMethodBinding() != null) {
IMethodBinding declarationOfInvokedMethod = methodInvocation
.resolveMethodBinding().getMethodDeclaration();
System.out.println(String.format(
"invocation of \"%s\" is resolved to declaration \"%s\"",
methodInvocation, declarationOfInvokedMethod));
}
return super.visit(methodInvocation);
}
}
该访问者适用于一些测试场景。 但奇怪的是,当将其应用于包含重载方法的编译单元时,某些调用会与错误的声明相关联。让我们访问以下代码:
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashSet;
@SuppressWarnings({ "unchecked", "rawtypes" })
public class OverloadedMethodsAndRawTypes implements ICallGraphTestSource {
void f(HashSet objects) {
f(new ArrayDeque(objects));
}
void f(ArrayDeque objects) {
f(new ArrayList(objects));
}
void f(ArrayList objects) {
f(new HashSet(objects));
}
}
当访问此编译单元时,控制台输出是:
invocation of "f(new ArrayDeque(objects))" is resolved to declaration "void f(HashSet) "
invocation of "f(new ArrayList(objects))" is resolved to declaration "void f(HashSet )"
invocation of "f(new HashSet(list))" is resolved to declaration "void f(HashSet) "
我希望这样输出:
invocation of "f(new ArrayDeque(objects))" is resolved to declaration "void f(ArrayDeque) "
invocation of "f(new ArrayList(objects))" is resolved to declaration "void f(ArrayList )"
invocation of "f(new HashSet(list))" is resolved to declaration "void f(HashSet) "
我注意到,重载方法的调用总是解析为与调用的方法名称匹配的第一个出现的声明,这对我来说似乎不正确。
为了证明方法重载是罪魁祸首,我附加了没有方法重载的相同代码:
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashSet;
@SuppressWarnings({ "unchecked", "rawtypes" })
public class RawTypes implements ICallGraphTestSource {
void f_set(HashSet objects) {
f_deque(new ArrayDeque(objects));
}
void f_deque(ArrayDeque objects) {
f_list(new ArrayList(objects));
}
void f_list(ArrayList objects) {
f_set(new HashSet(objects));
}
}
访问时,它会产生正确的输出:
invocation of "f_deque(new ArrayDeque(objects))" is resolved to declaration "void f_deque(ArrayDeque) "
invocation of "f_list(new ArrayList(objects))" is resolved to declaration "void f_list(ArrayList) "
invocation of "f_set(new HashSet(list))" is resolved to declaration "void f_set(HashSet) "
我的 ASTParser 配置如下:
public static ASTNode getAST(ICompilationUnit compilationUnit) {
ASTParser parser = ASTParser.newParser(AST.JLS3);
parser.setSource(compilationUnit);
parser.setResolveBindings(true);
parser.setBindingsRecovery(true);
parser.setProject(getJavaProject());
return parser.createAST(null);
}
编辑:所描述的设置不起作用在 JUnit-Tests 中,但它确实可以作为独立插件工作。原因一定是在 getJavaProject-Method 中创建了一个临时项目。
I am currently working on an academic project which employs an ASTVisitor to create a basic calltree.
For this purpose it is needed to associate the invocation of a method with its declaration.
EDIT: Problem is solved to a large extent: The described error appears only JUnit-Tests, but but not in the standalone-plugin. It seemingly has to do with the unit-testing workflow of ASTVisitors. I will further investigate the reason in some time and post the answer here.
The code below shows a minimal visitor which prints invocations and associated declarations to console:
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.MethodInvocation;
/**
* Visits all method invocations and prints the declaration of the caller to
* console.
*/
public class InvocationLoggerASTVisitor extends ASTVisitor {
@Override
public boolean visit(MethodInvocation methodInvocation) {
if (methodInvocation.resolveMethodBinding() != null) {
IMethodBinding declarationOfInvokedMethod = methodInvocation
.resolveMethodBinding().getMethodDeclaration();
System.out.println(String.format(
"invocation of \"%s\" is resolved to declaration \"%s\"",
methodInvocation, declarationOfInvokedMethod));
}
return super.visit(methodInvocation);
}
}
The visitor works for some tested scenarios.
But strangely, when applying it to compilation units which contain overloaded methods, some invocations get associated with wrong declarations. Lets visit the following code:
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashSet;
@SuppressWarnings({ "unchecked", "rawtypes" })
public class OverloadedMethodsAndRawTypes implements ICallGraphTestSource {
void f(HashSet objects) {
f(new ArrayDeque(objects));
}
void f(ArrayDeque objects) {
f(new ArrayList(objects));
}
void f(ArrayList objects) {
f(new HashSet(objects));
}
}
When visiting this compilation unit, the console output is:
invocation of "f(new ArrayDeque(objects))" is resolved to declaration "void f(HashSet) "
invocation of "f(new ArrayList(objects))" is resolved to declaration "void f(HashSet )"
invocation of "f(new HashSet(list))" is resolved to declaration "void f(HashSet) "
I would expect this output:
invocation of "f(new ArrayDeque(objects))" is resolved to declaration "void f(ArrayDeque) "
invocation of "f(new ArrayList(objects))" is resolved to declaration "void f(ArrayList )"
invocation of "f(new HashSet(list))" is resolved to declaration "void f(HashSet) "
I've noticed, that invocations of overloaded methods always resolve to the first occurring declaration matching the invoked method name, which seems not right to me.
To prove, that method overloading is to blame, i attach the same code without method overloading:
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashSet;
@SuppressWarnings({ "unchecked", "rawtypes" })
public class RawTypes implements ICallGraphTestSource {
void f_set(HashSet objects) {
f_deque(new ArrayDeque(objects));
}
void f_deque(ArrayDeque objects) {
f_list(new ArrayList(objects));
}
void f_list(ArrayList objects) {
f_set(new HashSet(objects));
}
}
When visited, it yields the correct output:
invocation of "f_deque(new ArrayDeque(objects))" is resolved to declaration "void f_deque(ArrayDeque) "
invocation of "f_list(new ArrayList(objects))" is resolved to declaration "void f_list(ArrayList) "
invocation of "f_set(new HashSet(list))" is resolved to declaration "void f_set(HashSet) "
My ASTParser is configured as following:
public static ASTNode getAST(ICompilationUnit compilationUnit) {
ASTParser parser = ASTParser.newParser(AST.JLS3);
parser.setSource(compilationUnit);
parser.setResolveBindings(true);
parser.setBindingsRecovery(true);
parser.setProject(getJavaProject());
return parser.createAST(null);
}
EDIT: The described setup does not work in JUnit-Tests, but it does work as standalone-plugin. The reason must be therefor in the getJavaProject-Method, where a temporary project is created.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
由于错误行为仅发生在测试用例中,因此 AST 的创建似乎未正确设置。
我找到了一个有效的测试设置:
对于每个测试,我从文件系统中将 CompilationUnit 作为文本读取,并按照从描述 此处。
正如您在原始问题的 ASTParser 配置中看到的,使用
parser.setProject(IJavaProject)
传递一个IJavaProject
实例,保存一个环境来正确解析调用。该实例必须配置类路径:
在我的例子中,我只需要默认的 JRE 库。在您的情况下,可能需要增加类路径。无论如何,这解决了问题。
Since the erroneous behaviour only happens in test cases, the creation of the AST seems to be not set up properly.
I found a testing setup which works:
For each test I read the
CompilationUnit
as text from the file system and create a new project programmatically, following an approach derived from a description here.As you see in the ASTParser configuration in the original question, an
IJavaProject
-instance is passed usingparser.setProject(IJavaProject)
, holding an environment to resolve invocations properly.This instance has to be configured with a classpath:
In my case I need only the default JRE-library. In your case there may be need to augment the classpath. In any case, this solves the problem.