我如何到达AST表达式的底部

我是AST新手(我第一次写插件)。 表达在现实生活中可能相当复杂。 例如,我想知道如何解决排序的左侧和右侧。

class Visitor extends ASTVisitor
{
    @Override
    public boolean visit(Assignment node)
    {
        //here, how do I get the final name to each each side of the assignment resolves?
    }
}

我还有另一个疑问,我如何获得用于调用方法的实例?

public boolean visit(MethodInvocation node)
{
    //how do I get to know the object used to invoke this method?
    //like, for example, MyClass is a class, and it has a field called myField
    //the type of myField has a method called myMethod.
    //how do I find myField? or for that matter some myLocalVariable used in the same way.
}

假设下面的分配

SomeType.someStaticMethod(params).someInstanceMethod(moreParams).someField =
     [another expression with arbitrary complexity]

我如何从Assigment节点到达someField Assigment

另外, MethodInvocation属性为我提供了用于调用方法的实例吗?

编辑1:鉴于我收到的答案,我的问题显然不清楚。 我不想解决这个特殊的表达。 我希望能够在给定任务的情况下找出它被分配的名称以及分配给第一个的名称(如果不是右值)。

因此,例如,方法调用的参数可以是字段访问或以前声明的局部变量。

SomeType.someStaticMethod(instance.field).someInstanceMethod(type.staticField, localVariable, localField).Field.destinationField

所以,这里有一个充满希望的客观问题:给定任意赋值语句,左右两边都有任意的复杂性,如何获得赋值给的最终字段/变量,以及赋值的最终(如果有的话)字段/变量到它。

编辑2:更具体地说,我想通过@Const注释实现不变性:

/**
* When Applied to a method, ensures the method doesn't change in any
* way the state of the object used to invoke it, i.e., all the fields
* of the object must remain the same, and no field may be returned,
* unless the field itself is marked as {@code @Const} or the field is
* a primitive non-array type. A method  annotated with {@code @Const} 
* can only invoke other {@code @Const} methods of its class, can only 
* use the class's fields to invoke {@code @Const} methods of the fields 
* classes and can only pass fields as parameters to methods that 
* annotate that formal parameter as {@code @Const}.
*
* When applied to a formal parameter, ensures the method will not
* modify the value referenced by the formal parameter. A formal   
* parameter annotated as {@code @Const} will not be aliased inside the
* body of the method. The method is not allowed to invoke another 
* method and pass the annotated parameter, save if the other method 
* also annotates the formal parameter as {@code @Const}. The method is 
* not allowed to use the parameter to invoke any of its type's methods,
* unless the method being invoked is also annotated as {@code @Const}
* 
* When applied to a field, ensures the field cannot be aliased and that
* no code can alter the state of that field, either from inside the   
* class that owns the field or from outside it. Any constructor in any
* derived class is allowed to set the value of the field and invoke any
* methods using it. As for methods, only those annotated as
* {@code @Const} may be invoked using the field. The field may only be
* passed as a parameter to a method if the method annotates the 
* corresponding formal parameter as {@code @Const}
* 
* When applied to a local variable, ensures neither the block where the
* variable is declared or any nested block will alter the value of that 
* local variable. The local variable may be defined only once, at any
* point where it is in scope and cannot be aliased. Only methods
* annotated as {@code @Const} may be invoked using this variable, and 
* the variable  may only be passed as a parameter to another method if 
* said method annotates its corresponding formal parameter as
* {@code @Const}
*
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD,
ElementType.LOCAL_VARIABLE})
@Inherited
public @interface Const
{

}

要做到这一点,我必须做的第一件事情是,任务的左侧标记为@Const (足够简单)的情况。 我还必须检测何时右侧和表达式是标记为@Const的字段,在这种情况下,它只能在相同类型的@Const变量的定义处分配。

问题是我真的很难在表达式的右边找到最终字段,以避免字段混淆并使@Const注释无用。


访问者是一个非常棒的工具,但是针对具体问题的恰当解决方案并不总是让一位访问者耐心等待,直到访问方法被引用为止......您所问的问题就是这种情况的一个例子。

让我们改述你想做的事情:

  • 你想确定识别每个任务(即leftSide = rightSide

  • 对于每个任务,您想要确定左侧的本质(即,它是本地变量还是字段访问),并且如果确实是字段访问,则需要构建一个“路径”,对应于该字段(即源对象,后跟一系列方法调用或字段访问,并以字段访问结束)。

  • 对于每个作业,您想要确定与右侧相对应的类似“路径”。

  • 我认为你已经解决了第一点:你只需创建一个扩展org.eclipse.jdt.core.dom.ASTVisitor的类; 在那里,你重写#visit(Assignment)方法。 最后,在适当的地方,你实例化你的访问者类,并让它访问一个AST树,从哪个节点开始匹配你的需求(很可能是一个CompilationUnitTypeDeclarationMethodDeclaration的实例)。

    那又怎么样? #visit(Assignment)方法确实收到一个Assignment节点。 直接在该对象上,可以获得左侧和右侧表达式( assignment.getLeftHandSide()assignment.getRightHandSide() )。 正如你所提到的,这两个都是Expression ,它可能会变得非常复杂,所以我们如何从这些子树中提取干净的线性“路径”? 访问者当然是最好的方式,但是这里有个问题,应该使用不同的访问者来完成,而不是让你的第一个访问者(一个捕获Assignment )继续下降任何一方的表达。 在技​​术上可以用一个访问者来完成,但这将涉及该访问者内部的重要状态管理。 无论如何,我相当相信这种管理的复杂性会非常高,以至于这样的实施实际上效率会比不同的访问者接近。

    所以我们可以像这样形象化:

    class MyAssignmentListVisitor extends ASTVisitor {
        @Override
        public boolean visit(Assignment assignment) {
            FieldAccessLineralizationVisitor leftHandSideVisitor = new FieldAccessLineralizationVisitor();
            assignment.getLeftHandSide().accept(leftHandSideVisitor);
            LinearFieldAccess leftHandSidePath = leftHandSideVisitor.asLinearFieldAccess();
    
            FieldAccessLineralizationVisitor rightHandSideVisitor = new FieldAccessLineralizationVisitor();
            assignment.getRightHandSide().accept(rightHandSideVisitor);
            LinearFieldAccess rightHandSidePath = rightHandSideVisitor.asLinearFieldAccess();
    
            processAssigment(leftHandSidePath, rightHandSidePath);
    
            return true;
        }
    }
    
    class FieldAccessLineralizationVisitor extends ASTVisitor {
    
        List<?> significantFieldAccessParts = [...];
    
        // ... various visit method expecting concrete subtypes of Expression ...
    
        @Override
        public boolean visit(Assignment assignment) {
            // Found an assignment inside an assignment; ignore its
            // left hand side, as it does not affect the "path" for 
            // the assignment currently being investigated
    
            assignment.getRightHandSide().accept(this);
    
            return false;
        }
    }
    

    请注意,在此代码中, MyAssignmentListVisitor.visit(Assignment)返回true ,指示应该递归检查分配的子项。 这听起来可能听起来没有必要,Java语言确实支持一些赋值可能包含其他赋值的构造; 考虑例如以下极端情况:

    (varA = someObject).someField = varB = (varC = new SomeClass(varD = "string").someField);
    

    出于同样的原因,只要赋值的右侧在表达式的线性化期间被访问,因为赋值的“结果值”是其右侧。 在这种情况下,左手边只是一种可以安全忽略的副作用。

    鉴于我不了解您的具体情况所需的信息的性质,我不会再进一步​​研究路径实际建模的原型。 对于左手边表情和右手边表情分别创建不同的访客类也更为合适,例如为了更好地处理右手边可能实际涉及多个变量/字段/方法调用的事实通过二元运算符。 这将是你的决定。

    在讨论AST树的访问者遍历时,仍然存在一些主要问题,即依靠默认的节点遍历顺序,你失去了获取每个节点之间关系信息的机会。 例如,给定表达式this.someMethod(this.fieldA).fieldB ,您将看到类似于以下序列的内容:

    FieldAccess      => corresponding to the whole expression
    MethodInvovation => corresponding to this.someMethod(this.fieldA)
    ThisExpression
    SimpleName ("someMethod")
    FieldAccess      => corresponding to this.fieldA
    ThisExpression
    SimpleName ("fieldA")
    SimpleName ("fieldB")
    

    根本没有办法从这个事件序列中真正推导出线性化的表达式。 您将改为显式拦截每个节点,并且只在适当的情况下按照适当的顺序显式递归节点的子节点。 例如,我们可以做到以下几点:

        @Override
        public boolean visit(FieldAccess fieldAccess) {
            // FieldAccess :: <expression>.<name>
    
            // First descend on the "subject" of the field access
            fieldAccess.getExpression().accept(this);
    
            // Then append the name of the accessed field itself
            this.path.append(fieldAccess.getName().getIdentifier());
    
            return false;
        }
    
        @Override
        public boolean visit(MethodInvocation methodInvocation) {
            // MethodInvocation :: <expression>.<methodName><<typeArguments>>(arguments)
    
            // First descend on the "subject" of the method invocation
            methodInvocation.getExpression().accept(this);
    
            // Then append the name of the accessed field itself
            this.path.append(methodAccess.getName().getIdentifier() + "()");
    
            return false;
        }
    
        @Override
        public boolean visit(ThisExpression thisExpression) {
            // ThisExpression :: [<qualifier>.] this
    
            // I will ignore the qualifier part for now, it will be up
            // to you to determine if it is pertinent
            this.path.append("this");
    
            return false;
        }
    

    根据前面的例子,这些方法会在path收集以下序列: thissomeMethod()fieldB 。 我相信,这与您正在寻找的东西非常接近。 如果你想收集所有的字段访问/方法调用序列(例如,你希望你的访问者返回this,someMethod(),fieldBthis,fieldA ),那么你可以重写visit(MethodInvocation)方法visit(MethodInvocation)这个:

        @Override
        public boolean visit(MethodInvocation methodInvocation) {
            // MethodInvocation :: <expression>.<methodName><<typeArguments>>(arguments)
    
            // First descend on the "subject" of the method invocation
            methodInvocation.getExpression().accept(this);
    
            // Then append the name of the accessed field itself
            this.path.append(methodAccess.getName().getIdentifier() + "()");
    
            // Now deal with method arguments, each within its own, distinct access chain
            for (Expression arg : methodInvocation.getArguments()) {
                LinearPath orginalPath = this.path;
                this.path = new LinearPath();
    
                arg.accept(this);
    
                this.collectedPaths.append(this.path);
                this.path = originalPath;
            }
    
            return false;
        }
    

    最后,如果您想知道路径中每个步骤的值的类型,则必须查看与每个节点关联的绑定对象,例如: methodInvocation.resolveMethodBinding().getDeclaringClass() 。 但请注意,在构建AST树时必须明确要求绑定解析。

    上面的代码不能正确处理更多的语言结构; 不过,我相信你应该能够自己解决这些剩余的问题。 如果您需要参考实现来查看,请查看org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteFlattener类,它基本上从现有的AST树重构Java源代码; 虽然这个特定的访问者比大多数其他ASTVisitor ,但它更容易理解。

    根据OP的编辑#2更新

    这是您最近一次编辑之后的更新起点。 仍然有很多案件需要处理,但这更符合您的具体问题。 还要注意的是,尽管我使用了大量的instanceof检查(因为目前对我来说更容易,因为我正在用简单的文本编辑器编写代码,并且没有ASTNode常量的代码完成),您可以选择node.getNodeType()上的switch语句,通常会更高效。

    class ConstCheckVisitor extends ASTVisitor {
    
        @Override
        public boolean visit(MethodInvocation methodInvocation) {    
            if (isConst(methodInvocation.getExpression())) {
                if (isConst(methodInvocation.resolveMethodBinding().getMethodDeclaration()))
                    reportInvokingNonConstMethodOnConstSubject(methodInvocation);
            }
    
            return true;
        }
    
        @Override
        public boolean visit(Assignment assignment) {
            if (isConst(assignment.getLeftHandSide())) {
                if ( /* assignment to @Const value is not acceptable in the current situation */ )
                    reportAssignmentToConst(assignment.getLeftHandSide());
    
                // FIXME: I assume here that aliasing a @Const value to
                //        another @Const value is acceptable. Is that right?
    
            } else if (isImplicitelyConst(assigment.getLeftHandSide())) {
                reportAssignmentToImplicitConst(assignment.getLeftHandSide());        
    
            } else if (isConst(assignment.getRightHandSide())) {
                reportAliasing(assignment.getRightHandSide());
            }
    
            return true;
        }
    
        private boolean isConst(Expression expression) {
            if (expression instanceof FieldAccess)
                return (isConst(((FieldAccess) expression).resolveFieldBinding()));
    
            if (expression instanceof SuperFieldAccess)
                return isConst(((SuperFieldAccess) expression).resolveFieldBinding());
    
            if (expression instanceof Name)
                return isConst(((Name) expression).resolveBinding());
    
            if (expression instanceof ArrayAccess)
                return isConst(((ArrayAccess) expression).getArray());
    
            if (expression instanceof Assignment)
                return isConst(((Assignment) expression).getRightHandSide());
    
            return false;
        }
    
        private boolean isImplicitConst(Expression expression) {
            // Check if field is actually accessed through a @Const chain
            if (expression instanceof FieldAccess)
                return isConst((FieldAccess expression).getExpression()) ||
                       isimplicitConst((FieldAccess expression).getExpression());
    
            // FIXME: Not sure about the effect of MethodInvocation, assuming
            //        that its subject is const or implicitly const
    
            return false;
        }
    
        private boolean isConst(IBinding binding) {
            if ((binding instanceof IVariableBinding) || (binding instanceof IMethodBinding))
                return containsConstAnnotation(binding.getAnnotations());
    
            return false;
        }
    }
    

    希望有所帮助。


    首先从我发布的答案引用:

    你将不得不使用bindings 。 要使绑定可用,这意味着resolveBinding()不返回null ,可能我已发布的其他步骤是必需的。

    下面的访问者应该帮助你做你想做的事情:

    class AssignmentVisitor extends ASTVisitor {
    
        public boolean visit(Assignment node) {
            ensureConstAnnotationNotViolated(node);
            return super.visit(node);
        }
    
        private void ensureConstAnnotationNotViolated(Assignment node) {
            Expression leftHandSide = node.getLeftHandSide();
            if (leftHandSide.getNodeType() == ASTNode.FIELD_ACCESS) {
                FieldAccess fieldAccess = (FieldAccess) leftHandSide;
                // access field IVariableBinding
                fieldAccess.resolveFieldBinding();
                // access IAnnotationBindings e.g. your @const
                fieldAccess.resolveFieldBinding().getAnnotations();
                // access field ITypeBinding
                fieldAccess.getExpression().resolveTypeBinding();
            } else {
                // TODO: check possible other cases
            }
    
        }
    }
    

    您可以进一步访问node.getLeftHandSide() 。 我认为一个很好的例子可以在Sharpen(Java2C#翻译)代码中找到:

    https://github.com/mono/sharpen/blob/master/src/main/sharpen/core/CSharpBuilder.java#L2848

    这里有一个简单的示例项目:https://github.com/revaultch/jdt-sample

    Junit-Test在这里:https://github.com/revaultch/jdt-sample/blob/master/src/test/java/ch/revault/jdt/test/VisitorTest.java

    链接地址: http://www.djcxy.com/p/91533.html

    上一篇: How do I get to the bottom of an AST Expression

    下一篇: Python, write json / dictionary objects to a file iteratively (one at a time)