From 0b6f2e4a8d045e137ce2b440e7fdd79941c57ec5 Mon Sep 17 00:00:00 2001 From: Andy Cheung Date: Fri, 24 Mar 2023 09:48:08 +0800 Subject: [PATCH] Add Dubbo Annotation Processor (Add deprecated method warn when called by user, #10850) (#11513) --- .licenserc.yaml | 1 + NOTICE | 2 +- dubbo-annotation-processor/pom.xml | 165 +++++++ .../AnnotationProcessingHandler.java | 44 ++ .../AnnotationProcessorContext.java | 139 ++++++ .../DispatchingAnnotationProcessor.java | 162 +++++++ .../annotation/handler/DeprecatedHandler.java | 110 +++++ .../dubbo/annotation/permit/Permit.java | 429 ++++++++++++++++++ .../dubbo/annotation/permit/dummy/Child.java | 33 ++ .../annotation/permit/dummy/GrandChild.java | 43 ++ .../dubbo/annotation/permit/dummy/Parent.java | 36 ++ .../annotation/permit/dummy/package-info.java | 32 ++ .../dubbo/annotation/permit/package-info.java | 32 ++ .../dubbo/annotation/util/ASTUtils.java | 111 +++++ .../dubbo/annotation/util/FileUtils.java | 155 +++++++ .../javax.annotation.processing.Processor | 1 + .../src/main/resources/handlers.cfg | 17 + .../dubbo/annotation/RealInvocationTest.java | 64 +++ .../dubbo/annotation/TestingCommons.java | 71 +++ .../DeprecatedMethodInvocationCounter.java | 36 ++ .../dubbo/eci/extractor/JavassistUtils.java | 151 ++++++ .../apache/dubbo/eci/util/ReflectUtils.java | 51 +++ .../org/testing/dm/TestConstructorMethod.java | 28 ++ .../dm/TestConstructorMethodParentClass.java | 27 ++ .../dm/TestConstructorMethodSubClass.java | 30 ++ .../org/testing/dm/TestDeprecatedMethod.java | 26 ++ .../dm/TestInterfaceDeprecatedMethod.java | 24 + dubbo-build-tools/pom.xml | 8 +- .../DeprecatedMethodInvocationCounter.java | 83 ++++ ...catedMethodInvocationCounterConstants.java | 35 ++ .../common/constants/LoggerCodeConstants.java | 4 + ...DeprecatedMethodInvocationCounterTest.java | 59 +++ dubbo-config/dubbo-config-api/pom.xml | 1 - .../dubbo-metadata-processor/pom.xml | 13 + .../org/apache/dubbo/dependency/FileTest.java | 2 + pom.xml | 10 +- 36 files changed, 2228 insertions(+), 7 deletions(-) create mode 100644 dubbo-annotation-processor/pom.xml create mode 100644 dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/AnnotationProcessingHandler.java create mode 100644 dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/AnnotationProcessorContext.java create mode 100644 dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/DispatchingAnnotationProcessor.java create mode 100644 dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/handler/DeprecatedHandler.java create mode 100644 dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/Permit.java create mode 100644 dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/dummy/Child.java create mode 100644 dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/dummy/GrandChild.java create mode 100644 dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/dummy/Parent.java create mode 100644 dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/dummy/package-info.java create mode 100644 dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/package-info.java create mode 100644 dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/util/ASTUtils.java create mode 100644 dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/util/FileUtils.java create mode 100644 dubbo-annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor create mode 100644 dubbo-annotation-processor/src/main/resources/handlers.cfg create mode 100644 dubbo-annotation-processor/src/test/java/org/apache/dubbo/annotation/RealInvocationTest.java create mode 100644 dubbo-annotation-processor/src/test/java/org/apache/dubbo/annotation/TestingCommons.java create mode 100644 dubbo-annotation-processor/src/test/java/org/apache/dubbo/common/DeprecatedMethodInvocationCounter.java create mode 100644 dubbo-annotation-processor/src/test/java/org/apache/dubbo/eci/extractor/JavassistUtils.java create mode 100644 dubbo-annotation-processor/src/test/java/org/apache/dubbo/eci/util/ReflectUtils.java create mode 100644 dubbo-annotation-processor/src/test/resources/org/testing/dm/TestConstructorMethod.java create mode 100644 dubbo-annotation-processor/src/test/resources/org/testing/dm/TestConstructorMethodParentClass.java create mode 100644 dubbo-annotation-processor/src/test/resources/org/testing/dm/TestConstructorMethodSubClass.java create mode 100644 dubbo-annotation-processor/src/test/resources/org/testing/dm/TestDeprecatedMethod.java create mode 100644 dubbo-annotation-processor/src/test/resources/org/testing/dm/TestInterfaceDeprecatedMethod.java create mode 100644 dubbo-common/src/main/java/org/apache/dubbo/common/DeprecatedMethodInvocationCounter.java create mode 100644 dubbo-common/src/main/java/org/apache/dubbo/common/constants/DeprecatedMethodInvocationCounterConstants.java create mode 100644 dubbo-common/src/test/java/org/apache/dubbo/common/DeprecatedMethodInvocationCounterTest.java diff --git a/.licenserc.yaml b/.licenserc.yaml index 5b8980c95e..de74c43f6c 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -98,6 +98,7 @@ header: - 'dubbo-native/src/main/java/org/apache/dubbo/aot/generate/BasicJsonWriter.java' - 'dubbo-native/src/main/java/org/apache/dubbo/aot/generate/ExecutableMode.java' - 'dubbo-native/src/main/java/org/apache/dubbo/aot/generate/MemberCategory.java' + - 'dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/**' comment: on-failure diff --git a/NOTICE b/NOTICE index a060218b41..d409c01b52 100644 --- a/NOTICE +++ b/NOTICE @@ -4,7 +4,7 @@ Copyright 2018-2023 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). -This product contains code form the Netty Project: +This product contains code from the Netty Project: The Netty Project ================= diff --git a/dubbo-annotation-processor/pom.xml b/dubbo-annotation-processor/pom.xml new file mode 100644 index 0000000000..8a89f1bf74 --- /dev/null +++ b/dubbo-annotation-processor/pom.xml @@ -0,0 +1,165 @@ + + + + + + + 4.0.0 + + org.apache.dubbo + dubbo-annotation-processor + 1.0.0 + + + 8 + 8 + UTF-8 + + + true + true + true + true + + true + + 5.9.0 + 3.29.2-GA + + + jar + + + + + org.junit.jupiter + junit-jupiter-engine + ${junit_jupiter_version} + test + + + + org.junit.jupiter + junit-jupiter-api + ${junit_jupiter_version} + test + + + + org.junit.jupiter + junit-jupiter-params + ${junit_jupiter_version} + test + + + + org.javassist + javassist + ${javassist_version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + none + true + + + + + + + + default-profile-in-jdk-8 + + + ${java.home}/../lib/tools.jar + + (, 9) + + + ${java.home}/../lib/tools.jar + + + + + javac + javac + 1.0 + system + ${toolsjar} + + + + + + legacy-apple-jdk-profile + + false + + ${java.home}/../Classes/classes.jar + + (, 9) + + + + ${java.home}/../Classes/classes.jar + + + + + javac + javac + 1.0 + system + ${toolsjar} + + + + + + add-export-greater-than-9 + + false + [9, ) + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + true + none + + + + + + + + diff --git a/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/AnnotationProcessingHandler.java b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/AnnotationProcessingHandler.java new file mode 100644 index 0000000000..4830e32704 --- /dev/null +++ b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/AnnotationProcessingHandler.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.annotation; + +import javax.lang.model.element.Element; +import java.lang.annotation.Annotation; +import java.util.Set; + +/** + * Represents an annotation processing handler, which is invoked by DispatchingAnnotationProcessor. + */ +public interface AnnotationProcessingHandler { + + /** + * Set the annotations that this handler will handle. + * + * @return annotations to handle + */ + Set> getAnnotationsToHandle(); + + /** + * Formal processing method. + * + * @param elements the elements that annotated by the annotations of getAnnotationsToHandle() + * @param annotationProcessorContext the annotation processor context object (Javac object encapsulation) + */ + void process(Set elements, + AnnotationProcessorContext annotationProcessorContext); +} diff --git a/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/AnnotationProcessorContext.java b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/AnnotationProcessorContext.java new file mode 100644 index 0000000000..958385aba6 --- /dev/null +++ b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/AnnotationProcessorContext.java @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.annotation; + +import com.sun.source.util.Trees; +import com.sun.tools.javac.api.JavacTrees; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Names; + +import javax.annotation.processing.ProcessingEnvironment; +import java.lang.reflect.Method; +import java.util.Objects; + +/** + * The Context Object of Annotation Processor, which stores objects related to javac. + */ +public class AnnotationProcessorContext { + private JavacProcessingEnvironment javacProcessingEnvironment; + private JavacTrees javacTrees; + private TreeMaker treeMaker; + private Names names; + private Context javacContext; + private Trees trees; + + private AnnotationProcessorContext() { } + + private static T jbUnwrap(Class iface, T wrapper) { + T unwrapped = null; + try { + final Class apiWrappers = wrapper.getClass().getClassLoader().loadClass("org.jetbrains.jps.javac.APIWrappers"); + final Method unwrapMethod = apiWrappers.getDeclaredMethod("unwrap", Class.class, Object.class); + unwrapped = iface.cast(unwrapMethod.invoke(null, iface, wrapper)); + } catch (Throwable ignored) { + } + + return unwrapped != null ? unwrapped : wrapper; + } + + public static AnnotationProcessorContext fromProcessingEnvironment(ProcessingEnvironment processingEnv) { + AnnotationProcessorContext apContext = new AnnotationProcessorContext(); + + Object procEnvToUnwrap = processingEnv.getClass() == JavacProcessingEnvironment.class ? + processingEnv : jbUnwrap(JavacProcessingEnvironment.class, processingEnv); + + JavacProcessingEnvironment jcProcessingEnvironment = (JavacProcessingEnvironment) procEnvToUnwrap; + + Context context = jcProcessingEnvironment.getContext(); + + apContext.javacProcessingEnvironment = jcProcessingEnvironment; + + apContext.javacContext = context; + apContext.javacTrees = JavacTrees.instance(jcProcessingEnvironment); + apContext.treeMaker = TreeMaker.instance(context); + apContext.names = Names.instance(context); + + apContext.trees = Trees.instance(jcProcessingEnvironment); + + return apContext; + } + + // Auto-generated methods. + + public JavacTrees getJavacTrees() { + return javacTrees; + } + + public TreeMaker getTreeMaker() { + return treeMaker; + } + + public Names getNames() { + return names; + } + + public Context getJavacContext() { + return javacContext; + } + + public Trees getTrees() { + return trees; + } + + public JavacProcessingEnvironment getJavacProcessingEnvironment() { + return javacProcessingEnvironment; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AnnotationProcessorContext context = (AnnotationProcessorContext) o; + + if (!Objects.equals(javacTrees, context.javacTrees)) return false; + if (!Objects.equals(treeMaker, context.treeMaker)) return false; + if (!Objects.equals(names, context.names)) return false; + if (!Objects.equals(javacContext, context.javacContext)) + return false; + return Objects.equals(trees, context.trees); + } + + @Override + public int hashCode() { + int result = javacTrees != null ? javacTrees.hashCode() : 0; + result = 31 * result + (treeMaker != null ? treeMaker.hashCode() : 0); + result = 31 * result + (names != null ? names.hashCode() : 0); + result = 31 * result + (javacContext != null ? javacContext.hashCode() : 0); + result = 31 * result + (trees != null ? trees.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "AnnotationProcessorContext{" + + "javacTrees=" + javacTrees + + ", treeMaker=" + treeMaker + + ", names=" + names + + ", javacContext=" + javacContext + + ", trees=" + trees + + '}'; + } +} diff --git a/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/DispatchingAnnotationProcessor.java b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/DispatchingAnnotationProcessor.java new file mode 100644 index 0000000000..8c11d72eff --- /dev/null +++ b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/DispatchingAnnotationProcessor.java @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.annotation; + +import org.apache.dubbo.annotation.permit.Permit; +import org.apache.dubbo.annotation.util.FileUtils; + +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.processing.JavacRoundEnvironment; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Dispatching annotation processor, which uses a file to locate handlers and invoke them. + */ +@SupportedAnnotationTypes("*") +public class DispatchingAnnotationProcessor extends AbstractProcessor { + + private static Set handlers; + + private static Set loadHandlers() { + List classNames = FileUtils.loadConfigurationFileInResources("handlers.cfg"); + Set tempHandlers = new HashSet<>(); + + for (String clsName : classNames) { + try { + Class cls = (Class) Class.forName(clsName); + AnnotationProcessingHandler r = cls.getConstructor().newInstance(); + + tempHandlers.add(r); + + } catch (InstantiationException | NoSuchMethodException | InvocationTargetException | + IllegalAccessException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + return Collections.unmodifiableSet(tempHandlers); + } + + private AnnotationProcessorContext apContext; + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + Permit.addOpens(); + super.init(processingEnv); + + if (processingEnv.getClass().getName().startsWith("org.eclipse.jdt.")) { + // Don't run on ECJ, since this processor is javac based. + processingEnv + .getMessager() + .printMessage(Diagnostic.Kind.WARNING, "The Annotation processor doesn't support ECJ."); + + return; + } + + if (handlers == null) { + handlers = loadHandlers(); + } + + apContext = AnnotationProcessorContext.fromProcessingEnvironment(processingEnv); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + + if (processingEnv.getClass().getName().startsWith("org.eclipse.jdt.")) { + // Don't run on ECJ, since this processor is javac based. + return false; + } + + for (AnnotationProcessingHandler i : handlers) { + Set elements = new HashSet<>(16); + + for (Class annotationClass : i.getAnnotationsToHandle()) { + elements.addAll(getElementsAnnotatedWith(annotationClass, roundEnv)); + } + + i.process(elements, apContext); + } + + return false; + } + + /** + * A hack to make comment only .java file pass the compilation by skipping package symbol scan. + * + * @param annotationClass the annotation class to find + * @param roundEnvironment the javac's round environment + * @return the elements annotated with specified annotation + */ + private Set getElementsAnnotatedWith(Class annotationClass, RoundEnvironment roundEnvironment) { + + TypeElement a = processingEnv.getElementUtils().getTypeElement(annotationClass.getCanonicalName()); + + // Detect the Javac's version. + if (Integer.parseInt(SourceVersion.latest().name().split("_")[1]) > 8) { + return roundEnvironment.getElementsAnnotatedWith(a); + } + + Set result = Collections.emptySet(); + + try { + Class annotationSetScannerClass = Class.forName("com.sun.tools.javac.processing.JavacRoundEnvironment$AnnotationSetScanner"); + Constructor scannerConstructor = Permit.getConstructor(annotationSetScannerClass, JavacRoundEnvironment.class, Set.class); + Object scanner = scannerConstructor.newInstance(roundEnvironment, result); + + Set rootElements1 = roundEnvironment + .getRootElements() + .stream() + .filter(x -> x.getClass() != Symbol.PackageSymbol.class) + .collect(Collectors.toSet()); + + for (Element element : rootElements1) { + Method scanningMethod = Permit.getMethod(annotationSetScannerClass, "scan", Element.class, TypeElement.class); + result = (Set) scanningMethod.invoke(scanner, element, a); + } + + } catch (InstantiationException | NoSuchMethodException | ClassNotFoundException | InvocationTargetException | + IllegalAccessException e) { + throw new RuntimeException(e); + } + + return result; + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } +} diff --git a/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/handler/DeprecatedHandler.java b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/handler/DeprecatedHandler.java new file mode 100644 index 0000000000..c87cb8f6c9 --- /dev/null +++ b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/handler/DeprecatedHandler.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.annotation.handler; + +import org.apache.dubbo.annotation.AnnotationProcessingHandler; +import org.apache.dubbo.annotation.AnnotationProcessorContext; +import org.apache.dubbo.annotation.util.ASTUtils; + +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.TreeTranslator; + +import javax.lang.model.element.Element; +import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Handles @Deprecated annotation and adds logger warn call to the methods that are annotated with it. + */ +public class DeprecatedHandler implements AnnotationProcessingHandler { + + @Override + public Set> getAnnotationsToHandle() { + return new HashSet<>( + Collections.singletonList(Deprecated.class) + ); + } + + @Override + public void process(Set elements, AnnotationProcessorContext apContext) { + for (Element element : elements) { + // Only interested in methods. + if (!(element instanceof Symbol.MethodSymbol)) { + continue; + } + + Symbol.ClassSymbol classSymbol = (Symbol.ClassSymbol) element.getEnclosingElement(); + + ASTUtils.addImportStatement(apContext, classSymbol, "org.apache.dubbo.common", "DeprecatedMethodInvocationCounter"); + + JCTree methodTree = apContext.getJavacTrees().getTree(element); + + methodTree.accept(new TreeTranslator() { + @Override + public void visitMethodDef(JCTree.JCMethodDecl jcMethodDecl) { + + JCTree.JCBlock block = jcMethodDecl.body; + + if (block == null) { + // No method body. (i.e. interface method declaration.) + return; + } + + ASTUtils.insertStatementToHeadOfMethod(block, jcMethodDecl, generateCounterStatement(apContext, classSymbol, jcMethodDecl)); + } + }); + } + } + + /** + * Generate an expression statement like this: + * DeprecatedMethodInvocationCounter.onDeprecatedMethodCalled("...."); + * + * @param originalMethodDecl the method declaration that will add logger statement + * @param apContext annotation processor context + * @param classSymbol the enclosing class that will be the logger's name + * @return generated expression statement + */ + private JCTree.JCExpressionStatement generateCounterStatement(AnnotationProcessorContext apContext, + Symbol.ClassSymbol classSymbol, + JCTree.JCMethodDecl originalMethodDecl) { + + JCTree.JCExpression fullStatement = apContext.getTreeMaker().Apply( + com.sun.tools.javac.util.List.nil(), + + apContext.getTreeMaker().Select( + apContext.getTreeMaker().Ident(apContext.getNames().fromString("DeprecatedMethodInvocationCounter")), + apContext.getNames().fromString("onDeprecatedMethodCalled") + ), + + com.sun.tools.javac.util.List.of( + apContext.getTreeMaker().Literal(getMethodDefinition(classSymbol, originalMethodDecl)) + ) + ); + + return apContext.getTreeMaker().Exec(fullStatement); + } + + private String getMethodDefinition(Symbol.ClassSymbol classSymbol, JCTree.JCMethodDecl originalMethodDecl) { + return classSymbol.getQualifiedName() + "." + + originalMethodDecl.name.toString() + "(" + originalMethodDecl.params.toString() + ")"; + } +} diff --git a/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/Permit.java b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/Permit.java new file mode 100644 index 0000000000..3d5c941764 --- /dev/null +++ b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/Permit.java @@ -0,0 +1,429 @@ +/* + * Authored by Project Lombok and licensed by MIT License, which is attached below: + * + * Copyright (C) 2018-2021 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.apache.dubbo.annotation.permit; + +import org.apache.dubbo.annotation.permit.dummy.Parent; + +import sun.misc.Unsafe; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +// sunapi suppresses javac's warning about using Unsafe; 'all' suppresses eclipse's warning about the unspecified 'sunapi' key. Leave them both. +// Yes, javac's definition of the word 'all' is quite contrary to what the dictionary says it means. 'all' does NOT include 'sunapi' according to javac. +@SuppressWarnings({"sunapi", "all"}) +public class Permit { + private Permit() { + } + + + private static final long ACCESSIBLE_OVERRIDE_FIELD_OFFSET; + private static final IllegalAccessException INIT_ERROR; + private static final sun.misc.Unsafe UNSAFE = (sun.misc.Unsafe) reflectiveStaticFieldAccess(sun.misc.Unsafe.class, "theUnsafe"); + + static { + Field f; + long g; + Throwable ex; + + try { + g = getOverrideFieldOffset(); + ex = null; + } catch (Throwable t) { + f = null; + g = -1L; + ex = t; + } + + ACCESSIBLE_OVERRIDE_FIELD_OFFSET = g; + if (ex == null) INIT_ERROR = null; + else if (ex instanceof IllegalAccessException) INIT_ERROR = (IllegalAccessException) ex; + else { + INIT_ERROR = new IllegalAccessException("Cannot initialize Unsafe-based permit"); + INIT_ERROR.initCause(ex); + } + } + + public static T setAccessible(T accessor) { + if (INIT_ERROR == null) { + UNSAFE.putBoolean(accessor, ACCESSIBLE_OVERRIDE_FIELD_OFFSET, true); + } else { + accessor.setAccessible(true); + } + + return accessor; + } + + private static long getOverrideFieldOffset() throws Throwable { + Field f = null; + Throwable saved = null; + try { + f = AccessibleObject.class.getDeclaredField("override"); + } catch (Throwable t) { + saved = t; + } + + if (f != null) { + return UNSAFE.objectFieldOffset(f); + } + // The below seems very risky, but for all AccessibleObjects in java today it does work, and starting with JDK12, making the field accessible is no longer possible. + try { + return UNSAFE.objectFieldOffset(Fake.class.getDeclaredField("override")); + } catch (Throwable t) { + throw saved; + } + } + + static class Fake { + boolean override; + Object accessCheckCache; + } + + public static Method getMethod(Class c, String mName, Class... parameterTypes) throws NoSuchMethodException { + Method m = null; + Class oc = c; + while (c != null) { + try { + m = c.getDeclaredMethod(mName, parameterTypes); + break; + } catch (NoSuchMethodException e) { + } + c = c.getSuperclass(); + } + + if (m == null) throw new NoSuchMethodException(oc.getName() + " :: " + mName + "(args)"); + return setAccessible(m); + } + + public static Method permissiveGetMethod(Class c, String mName, Class... parameterTypes) { + try { + return getMethod(c, mName, parameterTypes); + } catch (Exception ignore) { + return null; + } + } + + public static Field getField(Class c, String fName) throws NoSuchFieldException { + Field f = null; + Class oc = c; + while (c != null) { + try { + f = c.getDeclaredField(fName); + break; + } catch (NoSuchFieldException e) { + } + c = c.getSuperclass(); + } + + if (f == null) throw new NoSuchFieldException(oc.getName() + " :: " + fName); + + return setAccessible(f); + } + + public static Field permissiveGetField(Class c, String fName) { + try { + return getField(c, fName); + } catch (Exception ignore) { + return null; + } + } + + public static T permissiveReadField(Class type, Field f, Object instance) { + try { + return type.cast(f.get(instance)); + } catch (Exception ignore) { + return null; + } + } + + public static Constructor getConstructor(Class c, Class... parameterTypes) throws NoSuchMethodException { + return setAccessible(c.getDeclaredConstructor(parameterTypes)); + } + + private static Object reflectiveStaticFieldAccess(Class c, String fName) { + try { + Field f = c.getDeclaredField(fName); + f.setAccessible(true); + return f.get(null); + } catch (Exception e) { + return null; + } + } + + public static boolean isDebugReflection() { + return !"false".equals(System.getProperty("lombok.debug.reflection", "false")); + } + + public static void handleReflectionDebug(Throwable t, Throwable initError) { + if (!isDebugReflection()) return; + + System.err.println("** LOMBOK REFLECTION exception: " + t.getClass() + ": " + (t.getMessage() == null ? "(no message)" : t.getMessage())); + t.printStackTrace(System.err); + if (initError != null) { + System.err.println("*** ADDITIONALLY, exception occurred setting up reflection: "); + initError.printStackTrace(System.err); + } + } + + public static Object invoke(Method m, Object receiver, Object... args) throws IllegalAccessException, InvocationTargetException { + return invoke(null, m, receiver, args); + } + + public static Object invoke(Throwable initError, Method m, Object receiver, Object... args) throws IllegalAccessException, InvocationTargetException { + try { + return m.invoke(receiver, args); + } catch (IllegalAccessException e) { + handleReflectionDebug(e, initError); + throw e; + } catch (RuntimeException e) { + handleReflectionDebug(e, initError); + throw e; + } catch (Error e) { + handleReflectionDebug(e, initError); + throw e; + } + } + + public static Object invokeSneaky(Method m, Object receiver, Object... args) { + return invokeSneaky(null, m, receiver, args); + } + + public static Object invokeSneaky(Throwable initError, Method m, Object receiver, Object... args) { + try { + return m.invoke(receiver, args); + } catch (NoClassDefFoundError e) { + handleReflectionDebug(e, initError); + //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly + //do anything useful here. + return null; + } catch (NullPointerException e) { + handleReflectionDebug(e, initError); + //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly + //do anything useful here. + return null; + } catch (IllegalAccessException e) { + handleReflectionDebug(e, initError); + throw sneakyThrow(e); + } catch (InvocationTargetException e) { + throw sneakyThrow(e.getCause()); + } catch (RuntimeException e) { + handleReflectionDebug(e, initError); + throw e; + } catch (Error e) { + handleReflectionDebug(e, initError); + throw e; + } + } + + public static T newInstance(Constructor c, Object... args) throws IllegalAccessException, InvocationTargetException, InstantiationException { + return newInstance(null, c, args); + } + + public static T newInstance(Throwable initError, Constructor c, Object... args) throws IllegalAccessException, InvocationTargetException, InstantiationException { + try { + return c.newInstance(args); + } catch (IllegalAccessException e) { + handleReflectionDebug(e, initError); + throw e; + } catch (InstantiationException e) { + handleReflectionDebug(e, initError); + throw e; + } catch (RuntimeException e) { + handleReflectionDebug(e, initError); + throw e; + } catch (Error e) { + handleReflectionDebug(e, initError); + throw e; + } + } + + public static T newInstanceSneaky(Constructor c, Object... args) { + return newInstanceSneaky(null, c, args); + } + + public static T newInstanceSneaky(Throwable initError, Constructor c, Object... args) { + try { + return c.newInstance(args); + } catch (NoClassDefFoundError e) { + handleReflectionDebug(e, initError); + //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly + //do anything useful here. + return null; + } catch (NullPointerException e) { + handleReflectionDebug(e, initError); + //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly + //do anything useful here. + return null; + } catch (IllegalAccessException e) { + handleReflectionDebug(e, initError); + throw sneakyThrow(e); + } catch (InstantiationException e) { + handleReflectionDebug(e, initError); + throw sneakyThrow(e); + } catch (InvocationTargetException e) { + throw sneakyThrow(e.getCause()); + } catch (RuntimeException e) { + handleReflectionDebug(e, initError); + throw e; + } catch (Error e) { + handleReflectionDebug(e, initError); + throw e; + } + } + + public static Object get(Field f, Object receiver) throws IllegalAccessException { + try { + return f.get(receiver); + } catch (IllegalAccessException e) { + handleReflectionDebug(e, null); + throw e; + } catch (RuntimeException e) { + handleReflectionDebug(e, null); + throw e; + } catch (Error e) { + handleReflectionDebug(e, null); + throw e; + } + } + + public static void set(Field f, Object receiver, Object newValue) throws IllegalAccessException { + try { + f.set(receiver, newValue); + } catch (IllegalAccessException e) { + handleReflectionDebug(e, null); + throw e; + } catch (RuntimeException e) { + handleReflectionDebug(e, null); + throw e; + } catch (Error e) { + handleReflectionDebug(e, null); + throw e; + } + } + + public static void reportReflectionProblem(Throwable initError, String msg) { + if (!isDebugReflection()) return; + System.err.println("** LOMBOK REFLECTION issue: " + msg); + if (initError != null) { + System.err.println("*** ADDITIONALLY, exception occurred setting up reflection: "); + initError.printStackTrace(System.err); + } + } + + public static RuntimeException sneakyThrow(Throwable t) { + if (t == null) throw new NullPointerException("t"); + return Permit.sneakyThrow0(t); + } + + @SuppressWarnings("unchecked") + private static T sneakyThrow0(Throwable t) throws T { + throw (T) t; + } + + private static Object getJdkCompilerModule() { + /* call public api: ModuleLayer.boot().findModule("jdk.compiler").get(); + but use reflection because we don't want this code to crash on jdk1.7 and below. + In that case, none of this stuff was needed in the first place, so we just exit via + the catch block and do nothing. + */ + + try { + Class cModuleLayer = Class.forName("java.lang.ModuleLayer"); + Method mBoot = cModuleLayer.getDeclaredMethod("boot"); + Object bootLayer = mBoot.invoke(null); + Class cOptional = Class.forName("java.util.Optional"); + Method mFindModule = cModuleLayer.getDeclaredMethod("findModule", String.class); + Object oCompilerO = mFindModule.invoke(bootLayer, "jdk.compiler"); + return cOptional.getDeclaredMethod("get").invoke(oCompilerO); + } catch (Exception e) { + return null; + } + } + + private static Object getOwnModule() { + try { + Method m = Permit.getMethod(Class.class, "getModule"); + return m.invoke(Permit.class); + } catch (Exception e) { + return null; + } + } + + private static long getFirstFieldOffset(Unsafe unsafe) { + try { + return unsafe.objectFieldOffset(Parent.class.getDeclaredField("first")); + } catch (NoSuchFieldException e) { + // can't happen. + throw new RuntimeException(e); + } catch (SecurityException e) { + // can't happen + throw new RuntimeException(e); + } + } + + public static void addOpens() { + Class cModule; + try { + cModule = Class.forName("java.lang.Module"); + } catch (ClassNotFoundException e) { + return; //jdk8-; this is not needed. + } + + Unsafe unsafe = UNSAFE; + + Object jdkCompilerModule = getJdkCompilerModule(); + Object ownModule = getOwnModule(); + String[] allPkgs = { + "com.sun.tools.javac.code", + "com.sun.tools.javac.comp", + "com.sun.tools.javac.file", + "com.sun.tools.javac.main", + "com.sun.tools.javac.model", + "com.sun.tools.javac.parser", + "com.sun.tools.javac.processing", + "com.sun.tools.javac.tree", + "com.sun.tools.javac.util", + "com.sun.tools.javac.jvm", + "com.sun.tools.javac.api" + }; + + try { + Method m = cModule.getDeclaredMethod("implAddOpens", String.class, cModule); + long firstFieldOffset = getFirstFieldOffset(unsafe); + unsafe.putBooleanVolatile(m, firstFieldOffset, true); + for (String p : allPkgs) m.invoke(jdkCompilerModule, p, ownModule); + } catch (Exception ignore) {} + + try { + Method m = cModule.getDeclaredMethod("implAddExports", String.class, cModule); + long firstFieldOffset = getFirstFieldOffset(unsafe); + unsafe.putBooleanVolatile(m, firstFieldOffset, true); + for (String p : allPkgs) m.invoke(jdkCompilerModule, p, ownModule); + } catch (Exception ignore) {} + } +} diff --git a/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/dummy/Child.java b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/dummy/Child.java new file mode 100644 index 0000000000..46c647bedc --- /dev/null +++ b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/dummy/Child.java @@ -0,0 +1,33 @@ +/* + * Authored by Project Lombok and licensed by MIT License, which is attached below: + * + * Copyright (C) 2018-2021 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.apache.dubbo.annotation.permit.dummy; + +@SuppressWarnings("all") +public abstract class Child extends Parent { + private transient volatile boolean foo; + private transient volatile Object[] bar; + private transient volatile Object baz; + +} diff --git a/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/dummy/GrandChild.java b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/dummy/GrandChild.java new file mode 100644 index 0000000000..c2bf737629 --- /dev/null +++ b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/dummy/GrandChild.java @@ -0,0 +1,43 @@ +/* + * Authored by Project Lombok and licensed by MIT License, which is attached below: + * + * Copyright (C) 2018-2021 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.apache.dubbo.annotation.permit.dummy; + +@SuppressWarnings("all") +public final class GrandChild extends Child { + private Class a; + private int b; + private String c; + private Class d; + private Class[] e; + private Class[] f; + private int g; + private transient String h; + private transient Object i; + private byte[] j; + private byte[] k; + private byte[] l; + private volatile Object m; + private Object n; +} diff --git a/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/dummy/Parent.java b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/dummy/Parent.java new file mode 100644 index 0000000000..53cb8c8ce5 --- /dev/null +++ b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/dummy/Parent.java @@ -0,0 +1,36 @@ +/* + * Authored by Project Lombok and licensed by MIT License, which is attached below: + * + * Copyright (C) 2018-2021 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.apache.dubbo.annotation.permit.dummy; + +import java.io.OutputStream; + +@SuppressWarnings("all") +public class Parent { + boolean first; + static final Object staticObj = OutputStream.class; + volatile Object second; + private static volatile boolean staticSecond; + private static volatile boolean staticThird; +} diff --git a/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/dummy/package-info.java b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/dummy/package-info.java new file mode 100644 index 0000000000..dcbf5a58a0 --- /dev/null +++ b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/dummy/package-info.java @@ -0,0 +1,32 @@ +/* + * Authored by Project Lombok and licensed by MIT License, which is attached below: + * + * Copyright (C) 2018-2021 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * This package recreates the type hierarchy of {@code java.lang.reflect.AccessibleObject} and friends (such as {@code java.lang.reflect.Method}); + * its purpose is to allow us to ask {@code sun.misc.internal.Unsafe} about the exact offset of the {@code override} field of {@code AccessibleObject}; + * asking about that field directly doesn't work after jdk14, presumably because the fields of AO are expressly hidden somehow. + *

+ * NB: It's usually 12, on the vast majority of OS, VM, and architecture combos. + */ +package org.apache.dubbo.annotation.permit.dummy; diff --git a/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/package-info.java b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/package-info.java new file mode 100644 index 0000000000..95b72451c6 --- /dev/null +++ b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/permit/package-info.java @@ -0,0 +1,32 @@ +/* + * Authored by Project Lombok and licensed by MIT License, which is attached below: + * + * Copyright (C) 2018-2021 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * This is a reduced copy of the nqzero Permit-reflect project. + * https://github.com/nqzero/permit-reflect + *

+ * Many thanks to nqzero. The permit-reflect project is, like lombok itself, licensed under the MIT license. + * See https://github.com/nqzero/permit-reflect/blob/master/License for license info. + */ +package org.apache.dubbo.annotation.permit; diff --git a/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/util/ASTUtils.java b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/util/ASTUtils.java new file mode 100644 index 0000000000..bfd463785f --- /dev/null +++ b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/util/ASTUtils.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.annotation.util; + +import org.apache.dubbo.annotation.AnnotationProcessorContext; + +import com.sun.source.util.TreePath; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.ListBuffer; + +import java.util.List; + +/** + * Some utils about AST manipulating. + */ +public final class ASTUtils { + + private ASTUtils() { + throw new UnsupportedOperationException("No instance of 'ASTUtils' for you! "); + } + + public static void addImportStatement(AnnotationProcessorContext apContext, + Symbol.ClassSymbol classSymbol, + String packageName, + String className) { + + JCTree.JCImport jcImport = apContext.getTreeMaker().Import( + apContext.getTreeMaker().Select( + apContext.getTreeMaker().Ident(apContext.getNames().fromString(packageName)), + apContext.getNames().fromString(className) + ), false); + + TreePath treePath = apContext.getTrees().getPath(classSymbol); + TreePath parentPath = treePath.getParentPath(); + JCTree.JCCompilationUnit compilationUnit = (JCTree.JCCompilationUnit) parentPath.getCompilationUnit(); + + List imports = compilationUnit.getImports(); + if (imports.stream().noneMatch(x -> x.qualid.toString().contains(packageName + "." + className))) { + + compilationUnit.accept(new JCTree.Visitor() { + @Override + public void visitTopLevel(JCTree.JCCompilationUnit that) { + + List defs = compilationUnit.defs; + + ListBuffer newDefs = new ListBuffer<>(); + + newDefs.add(defs.get(0)); + newDefs.add(jcImport); + newDefs.addAll(defs.subList(1, defs.size())); + + compilationUnit.defs = newDefs.toList(); + } + }); + } + } + + /** + * Insert statement to head of the method. + * + * @param block the method body + * @param originalMethodDecl the method declaration that will add logger statement + * @param fullExpressionStatement the statement to insert. + */ + public static void insertStatementToHeadOfMethod(JCTree.JCBlock block, + JCTree.JCMethodDecl originalMethodDecl, + JCTree.JCStatement fullExpressionStatement) { + + boolean isConstructor = originalMethodDecl.name.toString().equals(""); + ListBuffer statements = new ListBuffer<>(); + + // In constructor, super(...) or this(...) should be the first statement. + + if (isConstructor && !block.stats.isEmpty()) { + + boolean startsWithSuper = block.stats.get(0).toString().startsWith("super("); + boolean startsWithThis = block.stats.get(0).toString().startsWith("this("); + + if (startsWithSuper || startsWithThis) { + statements.add(block.stats.get(0)); + statements.add(fullExpressionStatement); + statements.addAll(block.stats.subList(1, block.stats.size())); + } else { + statements.add(fullExpressionStatement); + statements.addAll(block.stats); + } + + } else { + statements.add(fullExpressionStatement); + statements.addAll(block.stats); + } + + block.stats = statements.toList(); + } +} diff --git a/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/util/FileUtils.java b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/util/FileUtils.java new file mode 100644 index 0000000000..66a897913a --- /dev/null +++ b/dubbo-annotation-processor/src/main/java/org/apache/dubbo/annotation/util/FileUtils.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.annotation.util; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Scanner; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Utilities of iterating file. + */ +public final class FileUtils { + + private static final Pattern WINDOWS_PATH_PATTERN = Pattern.compile("file:/\\w:/.*"); + + + private FileUtils() { + throw new UnsupportedOperationException("No instance of FileUtils for you! "); + } + + public static List getAllClassFilePaths(String rootPath) { + List targetFolders; + + try (Stream filesStream = Files.walk(Paths.get(rootPath))) { + targetFolders = filesStream.filter(x -> !x.toFile().isFile()) + .filter(x -> x.toString().contains("classes") && !x.toString().contains("test-classes")) + .filter(x -> x.toString().contains("\\org\\apache\\dubbo".replace('\\', File.separatorChar))) + .collect(Collectors.toList()); + + return targetFolders; + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static byte[] openFileAsByteArray(String filePath) { + try (FileChannel fileChannel = FileChannel.open(Paths.get(filePath))) { + + ByteBuffer byteBuffer = ByteBuffer.allocate((int) fileChannel.size()); + fileChannel.read(byteBuffer); + + return byteBuffer.array(); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static String openFileAsString(String filePath) { + return new String(openFileAsByteArray(filePath)); + } + + public static String getSourceFilePathFromClassFilePath(String classFilePath) { + + String classesPathString = "\\target\\classes\\".replace("\\", File.separator); + String sourcesPathString = "\\src\\main\\java\\".replace("\\", File.separator); + + String sourceFilePathByReplace = classFilePath.replace(classesPathString, sourcesPathString) + .replace(".class", ".java"); + + // Inner classes. + if (sourceFilePathByReplace.lastIndexOf('$') != -1) { + int dollarCharIndex = sourceFilePathByReplace.lastIndexOf('$'); + String outerClassPath = sourceFilePathByReplace.substring(0, dollarCharIndex); + + return outerClassPath + ".java"; + } + + return sourceFilePathByReplace; + } + + public static List loadConfigurationFileInResources(String path) { + + ReadableByteChannel resourceReadableByteChannel = Channels.newChannel( + Objects.requireNonNull(FileUtils.class.getClassLoader().getResourceAsStream(path))); + + List lines = new ArrayList<>(); + + try (Scanner scanner = new Scanner(resourceReadableByteChannel)) { + + while (scanner.hasNextLine()) { + String line = scanner.nextLine().trim(); + + if (!line.startsWith("#") && !line.isEmpty()) { + lines.add(line); + } + } + + return lines; + } + } + + /** + *

Get absolute path of resource. + * + *

Retained for testing. It won't work in JAR. + * + * @param path relative path of resources folder. + * @return absolute path of resource + */ + public static String getResourceFilePath(String path) { + String resourceFilePath = FileUtils.class.getClassLoader().getResource(path).toString(); + + if (WINDOWS_PATH_PATTERN.matcher(resourceFilePath).matches()) { + resourceFilePath = resourceFilePath.replace("file:/", ""); + } else { + resourceFilePath = resourceFilePath.replace("file:", ""); + } + + return resourceFilePath; + } + + public static List getAllFilesInDirectory(Path targetFolder) { + + try (Stream classFilesStream = Files.walk(targetFolder)) { + + return classFilesStream + .filter(x -> x.toFile().isFile()) + .collect(Collectors.toList()); + + } catch (IOException e) { + return Collections.emptyList(); + } + } +} diff --git a/dubbo-annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/dubbo-annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 0000000000..fe51e15be0 --- /dev/null +++ b/dubbo-annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +org.apache.dubbo.annotation.DispatchingAnnotationProcessor diff --git a/dubbo-annotation-processor/src/main/resources/handlers.cfg b/dubbo-annotation-processor/src/main/resources/handlers.cfg new file mode 100644 index 0000000000..bcdca71445 --- /dev/null +++ b/dubbo-annotation-processor/src/main/resources/handlers.cfg @@ -0,0 +1,17 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +org.apache.dubbo.annotation.handler.DeprecatedHandler diff --git a/dubbo-annotation-processor/src/test/java/org/apache/dubbo/annotation/RealInvocationTest.java b/dubbo-annotation-processor/src/test/java/org/apache/dubbo/annotation/RealInvocationTest.java new file mode 100644 index 0000000000..9544990ffe --- /dev/null +++ b/dubbo-annotation-processor/src/test/java/org/apache/dubbo/annotation/RealInvocationTest.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.annotation; + +import org.apache.dubbo.annotation.util.FileUtils; +import org.apache.dubbo.eci.extractor.JavassistUtils; + +import javassist.bytecode.ClassFile; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Real invocation test of DispatchingAnnotationProcessor (and DeprecatedHandler). + */ +class RealInvocationTest { + + /** + * [File name, Should the counter exist in the class file? ] + */ + private static final Map FILES = new HashMap<>(5, 1); + + static { + FILES.put("TestConstructorMethod.java", true); + FILES.put("TestDeprecatedMethod.java", true); + FILES.put("TestInterfaceDeprecatedMethod.java", false); + FILES.put("TestConstructorMethodParentClass.java", false); + FILES.put("TestConstructorMethodSubClass.java", true); + } + + @Test + void test() { + + for (Map.Entry i : FILES.entrySet()) { + String filePath = FileUtils.getResourceFilePath("org/testing/dm/" + i.getKey()); + + Assertions.assertTrue(TestingCommons.compileTheSource(filePath), "Compile failed! "); + + String classFilePath = filePath.replace(".java", ".class"); + ClassFile classFile = JavassistUtils.openClassFile(classFilePath); + List stringItems = JavassistUtils.getConstPoolStringItems(classFile.getConstPool()); + + Assertions.assertEquals(i.getValue(), stringItems.contains("org/apache/dubbo/common/DeprecatedMethodInvocationCounter")); + } + } +} diff --git a/dubbo-annotation-processor/src/test/java/org/apache/dubbo/annotation/TestingCommons.java b/dubbo-annotation-processor/src/test/java/org/apache/dubbo/annotation/TestingCommons.java new file mode 100644 index 0000000000..cca11f56e6 --- /dev/null +++ b/dubbo-annotation-processor/src/test/java/org/apache/dubbo/annotation/TestingCommons.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.annotation; + +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Locale; + +import static java.util.Arrays.asList; + +/** + * Common code of annotation processor testing. + */ +public final class TestingCommons { + private TestingCommons() { + throw new UnsupportedOperationException("No instance of TestingCommons for you! "); + } + + private static class ObjectHolders { + static final JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); + + static final StandardJavaFileManager javaFileManager = javaCompiler.getStandardFileManager( + null, + Locale.ROOT, + StandardCharsets.UTF_8 + ); + } + + public static boolean compileTheSource(String filePath) { + + + JavaCompiler.CompilationTask compilationTask = ObjectHolders.javaCompiler.getTask( + null, + ObjectHolders.javaFileManager, + null, + asList("-parameters", "-Xlint:unchecked", "-nowarn", "-Xlint:deprecation"), + null, + getSourceFileJavaFileObject(filePath) + ); + + compilationTask.setProcessors( + Collections.singletonList(new DispatchingAnnotationProcessor()) + ); + + return compilationTask.call(); + } + + private static Iterable getSourceFileJavaFileObject(String filePath) { + + return ObjectHolders.javaFileManager.getJavaFileObjects(filePath); + } +} diff --git a/dubbo-annotation-processor/src/test/java/org/apache/dubbo/common/DeprecatedMethodInvocationCounter.java b/dubbo-annotation-processor/src/test/java/org/apache/dubbo/common/DeprecatedMethodInvocationCounter.java new file mode 100644 index 0000000000..cc34299c81 --- /dev/null +++ b/dubbo-annotation-processor/src/test/java/org/apache/dubbo/common/DeprecatedMethodInvocationCounter.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.common; + +/** + * Reduced mock of Deprecated method invocation counter. + */ +public final class DeprecatedMethodInvocationCounter { + private DeprecatedMethodInvocationCounter() { + throw new UnsupportedOperationException("No instance of DeprecatedMethodInvocationCounter for you! "); + } + + /** + * Invoked by (modified) deprecated method. + * + * @param methodDefinition filled by annotation processor. (like 'org.apache.dubbo.common.URL.getServiceName()') + */ + public static void onDeprecatedMethodCalled(String methodDefinition) { + // Intended to be empty. + } +} diff --git a/dubbo-annotation-processor/src/test/java/org/apache/dubbo/eci/extractor/JavassistUtils.java b/dubbo-annotation-processor/src/test/java/org/apache/dubbo/eci/extractor/JavassistUtils.java new file mode 100644 index 0000000000..3a2475591a --- /dev/null +++ b/dubbo-annotation-processor/src/test/java/org/apache/dubbo/eci/extractor/JavassistUtils.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.eci.extractor; + +import org.apache.dubbo.annotation.util.FileUtils; + +import javassist.bytecode.ClassFile; +import javassist.bytecode.ConstPool; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Utilities of Javassist. + */ +public class JavassistUtils { + private static final Map stringFieldCache = new HashMap<>(2, 1); + + private static Method getItemMethodCache = null; + + private JavassistUtils() { + throw new UnsupportedOperationException("No instance of JavassistUtils for you! "); + } + + public static ClassFile openClassFile(String classFilePath) { + try { + byte[] clsB = FileUtils.openFileAsByteArray(classFilePath); + return new ClassFile(new DataInputStream(new ByteArrayInputStream(clsB))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + static List getConstPoolItems(ConstPool cp) { + List objects = new ArrayList<>(cp.getSize()); + + for (int i = 0; i < cp.getSize(); i++) { + objects.add(getConstPoolItem(cp, i)); + } + + return objects; + } + + /** + * Calls ConstPool.getItem() method reflectively. + * + * @param cp The ConstPool object. + * @param index The index of items. + * @return The XXXInfo Object. Since it's invisible, return Object instead. + */ + static Object getConstPoolItem(ConstPool cp, int index) { + + if (getItemMethodCache == null) { + Class cpc = ConstPool.class; + Method getItemMethod; + try { + getItemMethod = cpc.getDeclaredMethod("getItem", int.class); + getItemMethod.setAccessible(true); + + getItemMethodCache = getItemMethod; + + } catch (NoSuchMethodException e) { + throw new RuntimeException("Javassist internal method changed.", e); + } + } + + try { + return getItemMethodCache.invoke(cp, index); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Javassist internal method changed.", e); + } + } + + public static List getConstPoolStringItems(ConstPool cp) { + List objects = getConstPoolItems(cp); + List stringItems = new ArrayList<>(cp.getSize()); + + for (Object item : objects) { + + Field stringField; + + if (item != null) { + stringField = getStringFieldInConstPoolItems(item); + + if (stringField == null) { + continue; + } + + Object fieldData; + + try { + fieldData = stringField.get(item); + } catch (IllegalAccessException e) { + throw new RuntimeException("Javassist internal field changed.", e); + } + + if (fieldData.getClass() == String.class) { + stringItems.add((String) fieldData); + } + } + } + + return stringItems; + } + + /** + * Obtain the 'string' field in Utf8Info and StringInfo. + * + * @param item The instance of Utf8Info and StringInfo. + * @return 'string' field's value + */ + static Field getStringFieldInConstPoolItems(Object item) { + if (stringFieldCache.containsKey(item.getClass())) { + return stringFieldCache.get(item.getClass()); + } else { + try { + Field stringField = item.getClass().getDeclaredField("string"); + stringField.setAccessible(true); + stringFieldCache.put(item.getClass(), stringField); + + return stringField; + } catch (NoSuchFieldException ignored) { + } + } + + return null; + } +} diff --git a/dubbo-annotation-processor/src/test/java/org/apache/dubbo/eci/util/ReflectUtils.java b/dubbo-annotation-processor/src/test/java/org/apache/dubbo/eci/util/ReflectUtils.java new file mode 100644 index 0000000000..681bdc0441 --- /dev/null +++ b/dubbo-annotation-processor/src/test/java/org/apache/dubbo/eci/util/ReflectUtils.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.eci.util; + +import java.lang.reflect.Field; + +/** + * Tools of reflective operations. + */ +public final class ReflectUtils { + private ReflectUtils() { + throw new UnsupportedOperationException("No instance of ReflectUtils for you! "); + } + + /** + * Searches (a private) field in super classes recursively. + * + * @param cls the actual type + * @param name the field name + * @return the corresponding Field object, or null if the field does not exist. + */ + public static Field getDeclaredFieldRecursively(Class cls, String name) { + try { + Field indexField = cls.getDeclaredField(name); + indexField.setAccessible(true); + + return indexField; + } catch (NoSuchFieldException e) { + if (cls == Object.class) { + return null; + } + + return getDeclaredFieldRecursively(cls.getSuperclass(), name); + } + } +} diff --git a/dubbo-annotation-processor/src/test/resources/org/testing/dm/TestConstructorMethod.java b/dubbo-annotation-processor/src/test/resources/org/testing/dm/TestConstructorMethod.java new file mode 100644 index 0000000000..c705ca8d13 --- /dev/null +++ b/dubbo-annotation-processor/src/test/resources/org/testing/dm/TestConstructorMethod.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.testing.dm; + +public class TestConstructorMethod { + @Deprecated + TestConstructorMethod() { + this("1"); + } + + TestConstructorMethod(String s) { + } +} diff --git a/dubbo-annotation-processor/src/test/resources/org/testing/dm/TestConstructorMethodParentClass.java b/dubbo-annotation-processor/src/test/resources/org/testing/dm/TestConstructorMethodParentClass.java new file mode 100644 index 0000000000..8e14e67c51 --- /dev/null +++ b/dubbo-annotation-processor/src/test/resources/org/testing/dm/TestConstructorMethodParentClass.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.testing.dm; + +public class TestConstructorMethodParentClass { + + public TestConstructorMethodParentClass() { + } + + public TestConstructorMethodParentClass(String s) { + } +} diff --git a/dubbo-annotation-processor/src/test/resources/org/testing/dm/TestConstructorMethodSubClass.java b/dubbo-annotation-processor/src/test/resources/org/testing/dm/TestConstructorMethodSubClass.java new file mode 100644 index 0000000000..3b71626c9b --- /dev/null +++ b/dubbo-annotation-processor/src/test/resources/org/testing/dm/TestConstructorMethodSubClass.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.testing.dm; + +public class TestConstructorMethodSubClass extends TestConstructorMethodParentClass { + + @Deprecated + public TestConstructorMethodSubClass(int i) { + super(); + } + + public TestConstructorMethodSubClass(String s) { + super(s); + } +} diff --git a/dubbo-annotation-processor/src/test/resources/org/testing/dm/TestDeprecatedMethod.java b/dubbo-annotation-processor/src/test/resources/org/testing/dm/TestDeprecatedMethod.java new file mode 100644 index 0000000000..6360c3e137 --- /dev/null +++ b/dubbo-annotation-processor/src/test/resources/org/testing/dm/TestDeprecatedMethod.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.testing.dm; + +public class TestDeprecatedMethod { + + @Deprecated + public void m1() { + System.out.println("M1! "); + } +} diff --git a/dubbo-annotation-processor/src/test/resources/org/testing/dm/TestInterfaceDeprecatedMethod.java b/dubbo-annotation-processor/src/test/resources/org/testing/dm/TestInterfaceDeprecatedMethod.java new file mode 100644 index 0000000000..a2ce7af598 --- /dev/null +++ b/dubbo-annotation-processor/src/test/resources/org/testing/dm/TestInterfaceDeprecatedMethod.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.testing.dm; + +public interface TestInterfaceDeprecatedMethod { + + @Deprecated + public void m1(); +} diff --git a/dubbo-build-tools/pom.xml b/dubbo-build-tools/pom.xml index 28bae806b1..5c3b83e2dd 100644 --- a/dubbo-build-tools/pom.xml +++ b/dubbo-build-tools/pom.xml @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. --> - 4.0.0 @@ -24,7 +24,7 @@ jar - true + true - \ No newline at end of file + diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/DeprecatedMethodInvocationCounter.java b/dubbo-common/src/main/java/org/apache/dubbo/common/DeprecatedMethodInvocationCounter.java new file mode 100644 index 0000000000..b4245c5bfa --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/DeprecatedMethodInvocationCounter.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.common; + +import org.apache.dubbo.common.constants.DeprecatedMethodInvocationCounterConstants; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.LongAdder; + +/** + * Deprecated method invocation counter, which is used by annotation processor. + *

+ * If an IDE says it is unused, just ignore it. + */ +public final class DeprecatedMethodInvocationCounter { + private DeprecatedMethodInvocationCounter() { + throw new UnsupportedOperationException("No instance of DeprecatedMethodInvocationCounter for you! "); + } + + private static final ConcurrentHashMap COUNTERS = new ConcurrentHashMap<>(); + + private static final ErrorTypeAwareLogger LOGGER = LoggerFactory.getErrorTypeAwareLogger(DeprecatedMethodInvocationCounter.class); + + /** + * Invoked by (modified) deprecated method. + * + * @param methodDefinition filled by annotation processor. (like 'org.apache.dubbo.common.URL.getServiceName()') + */ + public static void onDeprecatedMethodCalled(String methodDefinition) { + if (!hasThisMethodInvoked(methodDefinition)) { + LOGGER.warn( + DeprecatedMethodInvocationCounterConstants.ERROR_CODE, + DeprecatedMethodInvocationCounterConstants.POSSIBLE_CAUSE, + DeprecatedMethodInvocationCounterConstants.EXTENDED_MESSAGE, + DeprecatedMethodInvocationCounterConstants.LOGGER_MESSAGE_PREFIX + methodDefinition + ); + } + + increaseInvocationCount(methodDefinition); + } + + private static void increaseInvocationCount(String methodDefinition) { + COUNTERS.computeIfAbsent(methodDefinition, k -> new LongAdder()); + LongAdder adder = COUNTERS.get(methodDefinition); + + adder.increment(); + } + + public static boolean hasThisMethodInvoked(String methodDefinition) { + return COUNTERS.containsKey(methodDefinition); + } + + public static Map getInvocationRecord() { + // Perform a deep-copy to avoid concurrent issues. + HashMap copyOfCounters = new HashMap<>(); + + for (Map.Entry entry : COUNTERS.entrySet()) { + copyOfCounters.put(entry.getKey(), entry.getValue().intValue()); + } + + return Collections.unmodifiableMap(copyOfCounters); + } +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/DeprecatedMethodInvocationCounterConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/DeprecatedMethodInvocationCounterConstants.java new file mode 100644 index 0000000000..f747568491 --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/DeprecatedMethodInvocationCounterConstants.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.common.constants; + +/** + * Constants of Deprecated Method Invocation Counter. + */ +public final class DeprecatedMethodInvocationCounterConstants { + private DeprecatedMethodInvocationCounterConstants() { + throw new UnsupportedOperationException("No instance of DeprecatedMethodInvocationCounterConstants for you! "); + } + + public static final String ERROR_CODE = LoggerCodeConstants.COMMON_DEPRECATED_METHOD_INVOKED; + + public static final String POSSIBLE_CAUSE = "invocation of deprecated method"; + + public static final String EXTENDED_MESSAGE = ""; + + public static final String LOGGER_MESSAGE_PREFIX = "Deprecated method invoked. The method is "; +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/LoggerCodeConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/LoggerCodeConstants.java index 0682c5233d..c39fa1c698 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/LoggerCodeConstants.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/LoggerCodeConstants.java @@ -92,6 +92,10 @@ public interface LoggerCodeConstants { String VULNERABILITY_WARNING = "0-28"; + /** + * Used in annotation processor to indicate a deprecated method is invoked. + */ + String COMMON_DEPRECATED_METHOD_INVOKED = "0-99"; // Registry module diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/DeprecatedMethodInvocationCounterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/DeprecatedMethodInvocationCounterTest.java new file mode 100644 index 0000000000..b1ad0943a7 --- /dev/null +++ b/dubbo-common/src/test/java/org/apache/dubbo/common/DeprecatedMethodInvocationCounterTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.common; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; + +/** + * Tests of DeprecatedMethodInvocationCounter. + */ +class DeprecatedMethodInvocationCounterTest { + + private static final String METHOD_DEFINITION = "org.apache.dubbo.common.URL.getServiceName()"; + + @Test + void testRealInvocation() { + Assertions.assertFalse(DeprecatedMethodInvocationCounter.hasThisMethodInvoked(METHOD_DEFINITION)); + + // Invoke a deprecated method. + URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path#index?version=1.0.0&id=org.apache.dubbo.config.RegistryConfig#0"); + + // Not a typo, intentionally invoke twice. + invokeDeprecatedMethod(url); + invokeDeprecatedMethod(url); + + Assertions.assertTrue(DeprecatedMethodInvocationCounter.hasThisMethodInvoked(METHOD_DEFINITION)); + + Map record = DeprecatedMethodInvocationCounter.getInvocationRecord(); + Assertions.assertEquals(2, record.get(METHOD_DEFINITION)); + } + + private void invokeDeprecatedMethod(URL url) { + try { + Method m = URL.class.getMethod("getServiceName"); + m.invoke(url); + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dubbo-config/dubbo-config-api/pom.xml b/dubbo-config/dubbo-config-api/pom.xml index 42ee10a4e0..4687ce2285 100644 --- a/dubbo-config/dubbo-config-api/pom.xml +++ b/dubbo-config/dubbo-config-api/pom.xml @@ -226,6 +226,5 @@ 1.17.6 test - diff --git a/dubbo-metadata/dubbo-metadata-processor/pom.xml b/dubbo-metadata/dubbo-metadata-processor/pom.xml index 0cb2c9139d..009ce910e4 100644 --- a/dubbo-metadata/dubbo-metadata-processor/pom.xml +++ b/dubbo-metadata/dubbo-metadata-processor/pom.xml @@ -161,4 +161,17 @@ test + + + + + org.apache.maven.plugins + maven-compiler-plugin + + true + none + + + + diff --git a/dubbo-test/dubbo-test-modules/src/test/java/org/apache/dubbo/dependency/FileTest.java b/dubbo-test/dubbo-test-modules/src/test/java/org/apache/dubbo/dependency/FileTest.java index 4f912e3001..7f4b3250b4 100644 --- a/dubbo-test/dubbo-test-modules/src/test/java/org/apache/dubbo/dependency/FileTest.java +++ b/dubbo-test/dubbo-test-modules/src/test/java/org/apache/dubbo/dependency/FileTest.java @@ -48,6 +48,7 @@ class FileTest { ignoredModules.add(Pattern.compile("dubbo-parent")); ignoredModules.add(Pattern.compile("dubbo-core-spi")); ignoredModules.add(Pattern.compile("dubbo-demo.*")); + ignoredModules.add(Pattern.compile("dubbo-annotation-processor")); ignoredModulesInDubboAll.add(Pattern.compile("dubbo")); ignoredModulesInDubboAll.add(Pattern.compile("dubbo-bom")); @@ -57,6 +58,7 @@ class FileTest { ignoredModulesInDubboAll.add(Pattern.compile("dubbo-metadata-processor")); ignoredModulesInDubboAll.add(Pattern.compile("dubbo-native.*")); ignoredModulesInDubboAll.add(Pattern.compile("dubbo-spring-boot.*")); + ignoredModulesInDubboAll.add(Pattern.compile("dubbo-annotation-processor.*")); ignoredModulesInDubboAll.add(Pattern.compile("dubbo-maven-plugin")); } diff --git a/pom.xml b/pom.xml index 0af455fd55..a792bd4a95 100644 --- a/pom.xml +++ b/pom.xml @@ -125,6 +125,7 @@ 1.3.0 3.2.1 3.1.0 + 1.0.0 true true @@ -134,6 +135,7 @@ + dubbo-annotation-processor dubbo-common dubbo-container dubbo-compiler @@ -177,6 +179,13 @@ + + + org.apache.dubbo + dubbo-annotation-processor + ${dubbo_annotation_processor_version} + + org.junit.jupiter @@ -710,7 +719,6 @@ ${maven_compiler_version} - -proc:none -parameters true