diff options
| author | Kun Niu <kunniu@google.com> | 2019-06-27 11:07:31 -0700 |
|---|---|---|
| committer | Kun Niu <kunniu@google.com> | 2019-08-21 03:57:56 +0000 |
| commit | d45cad98eacb2e95efdd51a75fb8c15bf38c32c6 (patch) | |
| tree | 0c0ecdf5b587c2a1c80cfed8d40c5aab3c0d7f46 | |
| parent | 4dfa2bce947991ad077f9ce07195e226a8f68b5b (diff) | |
| download | platform_tools_apifinder-d45cad98eacb2e95efdd51a75fb8c15bf38c32c6.tar.gz platform_tools_apifinder-d45cad98eacb2e95efdd51a75fb8c15bf38c32c6.tar.bz2 platform_tools_apifinder-d45cad98eacb2e95efdd51a75fb8c15bf38c32c6.zip | |
using error prone library to detect SDK public API used by mainlineplatform-tools-29.0.5ndk-sysroot-r21
module.
Test: atest JavaApiUsedByMainlineModuleTest.
Change-Id: I10716609e1c2bbb89a0054934365c1d07601d12e
4 files changed, 263 insertions, 0 deletions
diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..afd3fa0 --- /dev/null +++ b/Android.bp @@ -0,0 +1,59 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed 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. + +java_plugin { + name: "java_api_finder", + + static_libs: [ + "java_api_used_by_mainline_module", + ], +} + +java_library_host { + name: "java_api_used_by_mainline_module", + + srcs: ["src/main/**/*.java"], + + static_libs: [ + "//external/error_prone:error_prone_core", + "//external/dagger2:dagger2-auto-service", + ], + + plugins: [ + "//external/dagger2:dagger2-auto-service", + ], + + javacflags: ["-verbose"], +} + +java_test_host { + name: "JavaApiUsedByMainlineModuleTest", + srcs: ["src/test/**/JavaApiUsedByMainlineModuleTest.java"], + java_resource_dirs: ["src/test/res"], + java_resources: [":java_api_used_by_mainline_module_testdata"], + static_libs: [ + "java_api_used_by_mainline_module", + "error_prone_test_helpers", + "hamcrest-library", + "hamcrest", + "platform-test-annotations", + "junit", + ], +} + +filegroup { + name: "java_api_used_by_mainline_module_testdata", + path: "src/test/res", + srcs: ["src/test/res/**/*.java"], +} diff --git a/src/main/com/android/apifinder/JavaApiUsedByMainlineModule.java b/src/main/com/android/apifinder/JavaApiUsedByMainlineModule.java new file mode 100644 index 0000000..8e04688 --- /dev/null +++ b/src/main/com/android/apifinder/JavaApiUsedByMainlineModule.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed 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 com.android.apifinder; + +import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; + +import com.google.auto.service.AutoService; +import com.google.errorprone.BugPattern; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; +import com.google.errorprone.bugpatterns.BugChecker.NewClassTreeMatcher; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.NewClassTree; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symbol.MethodSymbol; +import com.sun.tools.javac.code.Symbol.VarSymbol; +import java.util.ArrayList; +import java.util.List; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Modifier; + +/** Bug checker to detect method or field used by a mainline module */ +@AutoService(BugChecker.class) +@BugPattern( + name = "JavaApiUsedByMainlineModule", + summary = "Any public method used by a mainline module.", + severity = WARNING) +public final class JavaApiUsedByMainlineModule extends BugChecker + implements MethodInvocationTreeMatcher, NewClassTreeMatcher { + + /* + * Checks if a method or class is private. + * A method is considered as private method when any of the following condition met. + * 1. Method is defined as private. + * 2. Method's class is defined as private. + * 3. Method's ancestor classes is defined as private. + */ + private boolean isPrivate(Symbol sym) { + Symbol tmpSym = sym; + while (tmpSym != null) { + if (!tmpSym.getModifiers().contains(Modifier.PUBLIC)) { + return true; + } + tmpSym = ASTHelpers.enclosingClass(tmpSym); + } + return false; + } + + /* + * Constructs parameters. Only return parameter type. + * For example + * (int, boolean, java.lang.String) + */ + private String constructParameters(Symbol sym) { + List<VarSymbol> paramsList = ((MethodSymbol) sym).getParameters(); + List<StringBuilder> stringParamsList = new ArrayList(); + for (VarSymbol paramSymbol : paramsList) { + StringBuilder tmpParam = new StringBuilder(paramSymbol.asType().toString()); + + // Removes "<*>" in parameter type. + if (tmpParam.indexOf("<") != -1) { + tmpParam = tmpParam.replace( + tmpParam.indexOf("<"), tmpParam.lastIndexOf(">") + 1, ""); + } + + stringParamsList.add(tmpParam); + } + return "(" + String.join(", ", stringParamsList) + ")"; + } + + @Override + public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { + Symbol sym = ASTHelpers.getSymbol(tree); + + // Exclude private function. + if (isPrivate(sym)) { + return Description.NO_MATCH; + } + + // Constructs method name formatted as superClassName.className.methodName, + // using supperClassName.className.className for constructor + String methodName = sym.name.toString(); + List<String> nameList = new ArrayList(); + if (sym.getKind() == ElementKind.CONSTRUCTOR) { + Symbol classSymbol = ASTHelpers.enclosingClass(sym); + while (classSymbol != null) { + nameList.add(0, classSymbol.name.toString()); + classSymbol = ASTHelpers.enclosingClass(classSymbol); + } + methodName = String.join(".", nameList); + } + + String params = constructParameters(sym); + + return buildDescription(tree) + .setMessage( + String.format("%s.%s%s", ASTHelpers.enclosingClass(sym), methodName, params)) + .build(); + } + + @Override + public Description matchNewClass(NewClassTree tree, VisitorState state) { + Symbol sym = ASTHelpers.getSymbol(tree); + + // Excludes private class. + if (isPrivate(sym)) { + return Description.NO_MATCH; + } + + String params = constructParameters(sym); + + // Constructs constructor name. + Symbol tmpSymbol = ASTHelpers.enclosingClass(sym); + List<String> nameList = new ArrayList(); + while (tmpSymbol != null) { + nameList.add(0, tmpSymbol.name.toString()); + tmpSymbol = ASTHelpers.enclosingClass(tmpSymbol); + } + String constructorName = String.join(".", nameList); + + return buildDescription(tree) + .setMessage( + String.format( + "%s.%s%s", ASTHelpers.enclosingClass(sym), constructorName, params)) + .build(); + } +} + diff --git a/src/test/com/android/apifinder/JavaApiUsedByMainlineModuleTest.java b/src/test/com/android/apifinder/JavaApiUsedByMainlineModuleTest.java new file mode 100644 index 0000000..9d79706 --- /dev/null +++ b/src/test/com/android/apifinder/JavaApiUsedByMainlineModuleTest.java @@ -0,0 +1,32 @@ +package com.android.apifinder; + +import com.android.apifinder.JavaApiUsedByMainlineModule; +import com.google.errorprone.CompilationTestHelper; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link JavaApiUsedByMainlineModule}. */ +@RunWith(JUnit4.class) +public final class JavaApiUsedByMainlineModuleTest { + + private CompilationTestHelper compilationHelper; + + @Before + public void setUp() { + compilationHelper = CompilationTestHelper.newInstance( + JavaApiUsedByMainlineModule.class, getClass()); + } + + /* + * The error prone testing library will run the plugin on the resource file. + * The error prone testing library will compare the comment of each method in the + * resource file to determine if the return value is expected. + */ + @Test + public void positiveFindPublicMethod() { + compilationHelper + .addSourceFile("JavaApiUsedByMainlineModuleCases.java").doTest(); + } +} diff --git a/src/test/res/com/android/apifinder/JavaApiUsedByMainlineModuleCases.java b/src/test/res/com/android/apifinder/JavaApiUsedByMainlineModuleCases.java new file mode 100644 index 0000000..603871f --- /dev/null +++ b/src/test/res/com/android/apifinder/JavaApiUsedByMainlineModuleCases.java @@ -0,0 +1,27 @@ +package com.android.apifinder; + +public class JavaApiUsedByMainlineModuleCases { + + public class PublicSubClass { + public void publicMethod() {} + + private void privateMethod() {} + } + + private class PrivateSubClass { + public void publicMethod() {} + } + + public void testMethod() { + // BUG: Diagnostic contains: JavaApiUsedByMainlineModuleCases.PublicSubClass + // .JavaApiUsedByMainlineModuleCases.PublicSubClass() + PublicSubClass publicTestClass = new PublicSubClass(); + + // BUG: Diagnostic contains: JavaApiUsedByMainlineModuleCases.PublicSubClass.publicMethod() + publicTestClass.publicMethod(); + + /** Should not be reported since PrivateSubClass is a private class. */ + PrivateSubClass privateTestClass = new PrivateSubClass(); + privateTestClass.publicMethod(); + } +} |
