0

由于特定原因,我想使用 Checker Framework 及其子类型检查器。为了使这个检查器工作,我必须使用ElementType.TYPE_PARAMETERand ElementType.TYPE_USE。但是,我想在编译为类文件之前将它们从局部变量中删除。

例如,假设我有以下带有自定义@FirstName和的代码@LastName(两者都必须在类级别保留RetentionPolicy.CLASS):

@FirstName String firstName = ...;
@LastName String lastName = ...;
...
firstName = lastName; // illegal, the error is generated by Checker Framework because the first name cannot be assigned to the last name

但出于另一个原因,我想从“at”字节码级别的局部变量中删除注释,就好像源代码只是:

String firstName = ...;
String lastName = ...;
...
firstName = lastName; // totally fine and legal in Java

如果我了解它可以完成的方式,注释处理是一种方法。因此,如果这是正确的做法,那么我必须按以下顺序链接一些注释处理器:

  • org.checkerframework.common.subtyping.SubtypingChecker.
  • 我的自定义“删除局部变量注释”注释处理器;

好吧,深入研究javac工作原理对我来说是一个极大的挑战。到目前为止我已经实施的是:

@SupportedOptions(RemoveLocalVariableAnnotationsProcessor.ANNOTATIONS_OPTION)
@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public final class RemoveLocalVariableAnnotationsProcessor
        extends AbstractProcessor {

    private static final Pattern commaPattern = Pattern.compile(",");

    static final String ANNOTATIONS_OPTION = "RemoveLocalVariableAnnotationsProcessor.annotations";

    @Nonnull
    private Predicate<? super Class<? extends Annotation>> annotationClasses = clazz -> false;

    @Override
    public void init(@Nonnull final ProcessingEnvironment environment) {
        super.init(environment);
        final Messager messager = environment.getMessager();
        final Map<String, String> options = environment.getOptions();
        @Nullable
        final String annotationsOption = options.get(ANNOTATIONS_OPTION);
        if ( annotationsOption != null ) {
            annotationClasses = commaPattern.splitAsStream(annotationsOption)
                    .<Class<? extends Annotation>>flatMap(className -> {
                        try {
                            @SuppressWarnings("unchecked")
                            final Class<? extends Annotation> clazz = (Class<? extends Annotation>) Class.forName(className);
                            if ( !clazz.isAnnotation() ) {
                                messager.printMessage(Diagnostic.Kind.WARNING, "Not an annotation: " + className);
                                return Stream.empty();
                            }
                            return Stream.of(clazz);
                        } catch ( final ClassNotFoundException ex ) {
                            messager.printMessage(Diagnostic.Kind.WARNING, "Cannot find " + className);
                            return Stream.empty();
                        }
                    })
                    .collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet))
                    ::contains;
        }
        final Trees trees = Trees.instance(environment);
        final JavacTask javacTask = JavacTask.instance(environment);
        javacTask.addTaskListener(new RemoverTaskListener(trees, messager));
    }

    @Override
    public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment environment) {
        // do nothing: ElementType.TYPE_USE and ElementType.TYPE_PARAMETER seem to be unable to be analyzed here
        return false;
    }

    private static final class RemoverTaskListener
            implements TaskListener {

        private final Trees trees;
        private final Messager messager;

        private RemoverTaskListener(final Trees trees, final Messager messager) {
            this.trees = trees;
            this.messager = messager;
        }

        @Override
        public void started(final TaskEvent taskEvent) {
            if ( taskEvent.getKind() == TaskEvent.Kind.ANALYZE ) {
                final TreeScanner<?, ?> remover = new Remover(trees, messager);
                remover.scan(taskEvent.getCompilationUnit(), null);
            }
        }

        @Override
        public void finished(final TaskEvent taskEvent) {
            // do nothing
        }

        private static final class Remover
                extends TreePathScanner<Void, Void> {

            private final Trees trees;
            private final Messager messager;

            private Remover(final Trees trees, final Messager messager) {
                this.trees = trees;
                this.messager = messager;
            }

            @Override
            public Void visitVariable(final VariableTree variableTree, final Void nothing) {
                super.visitVariable(variableTree, nothing);
                final Symbol symbol = (Symbol) trees.getElement(trees.getPath(getCurrentPath().getCompilationUnit(), variableTree));
                if ( !symbol.hasTypeAnnotations() || symbol.getKind() != ElementKind.LOCAL_VARIABLE ) {
                    return nothing;
                }
                final List<? extends AnnotationTree> annotationTrees = variableTree.getModifiers().getAnnotations();
                if ( annotationTrees.isEmpty() ) {
                    return nothing;
                }
                messager.printMessage(Diagnostic.Kind.WARNING, "TODO: " + symbol);
                for ( final AnnotationTree annotationTree : annotationTrees ) {
                    // TODO how to align AnnotationTree and java.lang.annotation.Annotation?
                    // TODO how to remove the annotation from the local variable?
                }
                return nothing;
            }

        }

    }

}

如您所见,它没有按预期工作。

从局部变量中删除注释的正确方法是什么?我的意思是,我该如何实现它?javac如果可能的话,由于 Maven 构建集成的细节,我想坚持使用注释处理器。

4

1 回答 1

0

据我所知,你不能这样做:

  • javac 注释处理器 ( JSR-269 ) 不能修改代码。他们只能观察它并生成将与手写代码一起编译的新代码。因此,注释处理是在多轮中完成的,以允许编译器和其他注释处理器看到新生成的代码。当回合结束时没有生成新代码时,处理基本上停止。
  • 这种方式注释处理器调用的顺序没有定义,这没关系,因为多轮编译 - 它有助于解决循环依赖关系。

您需要的是一个字节码重写器(ASM 库会做得很好)。编译完成后,此类工具对生成的 .class 文件进行操作。再一次,AFAIK,注释处理嵌入到编译本身中,因此您将无法在Checker 注释处理器看到它之前重写字节码。

因此,遗憾的是,我没有看到任何解决方案,而是尝试分叉 Checker 框架并使其忽略您想要的注释,当然,如果它还没有关闭某些验证的选项。

于 2020-09-02T12:54:21.667 回答