diff --git a/.gitignore b/.gitignore index dd93b08b2b7407426853366af64616d0575347a3..2e527fdf7ecd7a9c4637cdbac1ca3fa37207b3bd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /.idea/modules.xml /.idea/workspace.xml /.idea/runConfigurations +/.idea/usage.statistics.xml /out build/ stub-annotations/out diff --git a/.idea/misc.xml b/.idea/misc.xml index 84da703c3b6737ae27e4c89f3e04cb1712cf3a17..f6d5d5fb9089ae76140ed563625a0ab2b7a37c10 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,37 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> + <component name="NullableNotNullManager"> + <option name="myNullables"> + <value> + <list size="9"> + <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" /> + <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" /> + <item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" /> + <item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" /> + <item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" /> + <item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" /> + <item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" /> + <item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" /> + <item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" /> + </list> + </value> + </option> + <option name="myNotNulls"> + <value> + <list size="9"> + <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" /> + <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" /> + <item index="2" class="java.lang.String" itemvalue="javax.validation.constraints.NotNull" /> + <item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" /> + <item index="4" class="java.lang.String" itemvalue="android.support.annotation.NonNull" /> + <item index="5" class="java.lang.String" itemvalue="androidx.annotation.NonNull" /> + <item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" /> + <item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" /> + <item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" /> + </list> + </value> + </option> + </component> <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <output url="file://$PROJECT_DIR$/classes" /> </component> diff --git a/manual/android/util/annotations.xml b/manual/android/util/annotations.xml new file mode 100644 index 0000000000000000000000000000000000000000..c4e8392e5e35a3929d527eb7acc8fd88c8355dde --- /dev/null +++ b/manual/android/util/annotations.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<root> + <item name="android.util.Log boolean isLoggable(java.lang.String, int) 0"> + <annotation name="android.support.annotation.Size"> + <val name="max" val="23" /> + <val name="apis" val=""..23"" /> + </annotation> + </item> +</root> + diff --git a/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt b/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt index 8ed75242c9c373d48ef12525e15b23d40d6c9e3b..6cf812705b384eb72fafe4f3610541a19b5aacc5 100644 --- a/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt +++ b/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt @@ -16,6 +16,7 @@ package com.android.tools.metalava +import com.android.tools.metalava.doclava1.ApiPredicate import com.android.tools.metalava.doclava1.Errors import com.android.tools.metalava.model.AnnotationAttributeValue import com.android.tools.metalava.model.ClassItem @@ -194,7 +195,7 @@ class ApiAnalyzer( if (!isLeaf || cls.hasPrivateConstructor || cls.constructors().isNotEmpty()) { val constructors = cls.constructors() for (constructor in constructors) { - if (constructor.parameters().isEmpty() && constructor.isPublic) { + if (constructor.parameters().isEmpty() && constructor.isPublic && !constructor.hidden) { cls.defaultConstructor = constructor return } @@ -273,7 +274,7 @@ class ApiAnalyzer( } val currentUsesAvailableTypes = !referencesExcludedType(current, filter) - val nextUsesAvailableTypes = !referencesExcludedType(current, filter) + val nextUsesAvailableTypes = !referencesExcludedType(next, filter) if (currentUsesAvailableTypes != nextUsesAvailableTypes) { return if (currentUsesAvailableTypes) { current @@ -549,7 +550,7 @@ class ApiAnalyzer( // Make containing package non-hidden if it contains a show-annotation // class. Doclava does this in PackageInfo.isHidden(). cls.containingPackage().hidden = false - } else if (cls.modifiers.hasShowAnnotation()) { + } else if (cls.modifiers.hasHideAnnotations()) { cls.hidden = true } else if (containingClass != null) { if (containingClass.hidden) { @@ -815,13 +816,15 @@ class ApiAnalyzer( private fun handleStripping(stubImportPackages: Set<String>) { val notStrippable = HashSet<ClassItem>(5000) + val filter = ApiPredicate(codebase, ignoreShown = true) + // If a class is public or protected, not hidden, not imported and marked as included, // then we can't strip it val allTopLevelClasses = codebase.getPackages().allTopLevelClasses().toList() allTopLevelClasses .filter { it.checkLevel() && it.emit && !it.hidden() } .forEach { - cantStripThis(it, notStrippable, stubImportPackages) + cantStripThis(it, filter, notStrippable, stubImportPackages) } // complain about anything that looks includeable but is not supposed to @@ -891,12 +894,17 @@ class ApiAnalyzer( } else if (cl.deprecated) { // not hidden, but deprecated reporter.report(Errors.DEPRECATED, cl, "Class ${cl.qualifiedName()} is deprecated") + } else { + // Bring this class back + cl.hidden = false + cl.removed = false } } } private fun cantStripThis( cl: ClassItem, + filter: Predicate<Item>, notStrippable: MutableSet<ClassItem>, stubImportPackages: Set<String>? ) { @@ -917,43 +925,36 @@ class ApiAnalyzer( // cant strip any public fields or their generics for (field in cl.fields()) { - if (!field.checkLevel()) { + if (!filter.test(field)) { continue } val fieldType = field.type() if (!fieldType.primitive) { val typeClass = fieldType.asClass() if (typeClass != null) { - cantStripThis( - typeClass, notStrippable, stubImportPackages - ) + cantStripThis(typeClass, filter, notStrippable, stubImportPackages) } for (cls in fieldType.typeArgumentClasses()) { - cantStripThis( - cls, notStrippable, stubImportPackages - ) + cantStripThis(cls, filter, notStrippable, stubImportPackages) } } } // cant strip any of the type's generics for (cls in cl.typeArgumentClasses()) { - cantStripThis( - cls, notStrippable, stubImportPackages - ) + cantStripThis(cls, filter, notStrippable, stubImportPackages) } // cant strip any of the annotation elements // cantStripThis(cl.annotationElements(), notStrippable); // take care of methods - cantStripThis(cl.methods(), notStrippable, stubImportPackages) - cantStripThis(cl.constructors(), notStrippable, stubImportPackages) + cantStripThis(cl.methods(), filter, notStrippable, stubImportPackages) + cantStripThis(cl.constructors(), filter, notStrippable, stubImportPackages) // blow the outer class open if this is an inner class val containingClass = cl.containingClass() if (containingClass != null) { - cantStripThis( - containingClass, notStrippable, stubImportPackages - ) + cantStripThis(containingClass, filter, notStrippable, stubImportPackages) } // blow open super class and interfaces + // TODO: Consider using val superClass = cl.filteredSuperclass(filter) val superClass = cl.superClass() if (superClass != null) { if (superClass.isHiddenOrRemoved()) { @@ -971,9 +972,7 @@ class ApiAnalyzer( ) } } else { - cantStripThis( - superClass, notStrippable, stubImportPackages - ) + cantStripThis(superClass, filter, notStrippable, stubImportPackages) if (superClass.isPrivate && !superClass.isFromClassPath()) { reporter.report( Errors.PRIVATE_SUPERCLASS, cl, "Public class " + @@ -986,26 +985,22 @@ class ApiAnalyzer( private fun cantStripThis( methods: List<MethodItem>, + filter: Predicate<Item>, notStrippable: MutableSet<ClassItem>, stubImportPackages: Set<String>? ) { // for each method, blow open the parameters, throws and return types. also blow open their // generics for (method in methods) { - if (!method.checkLevel()) { + if (!filter.test(method)) { continue } for (typeParameterClass in method.typeArgumentClasses()) { - cantStripThis( - typeParameterClass, notStrippable, - stubImportPackages - ) + cantStripThis(typeParameterClass, filter, notStrippable, stubImportPackages) } for (parameter in method.parameters()) { for (parameterTypeClass in parameter.type().typeArgumentClasses()) { - cantStripThis( - parameterTypeClass, notStrippable, stubImportPackages - ) + cantStripThis(parameterTypeClass, filter, notStrippable, stubImportPackages) for (tcl in parameter.type().typeArgumentClasses()) { if (tcl.isHiddenOrRemoved()) { reporter.report( @@ -1014,32 +1009,21 @@ class ApiAnalyzer( "in ${method.containingClass().qualifiedName()}.${method.name()}()" ) } else { - cantStripThis( - tcl, notStrippable, - stubImportPackages - ) + cantStripThis(tcl, filter, notStrippable, stubImportPackages) } } } } for (thrown in method.throwsTypes()) { - cantStripThis( - thrown, notStrippable, stubImportPackages - ) + cantStripThis(thrown, filter, notStrippable, stubImportPackages) } val returnType = method.returnType() if (returnType != null && !returnType.primitive) { val returnTypeClass = returnType.asClass() if (returnTypeClass != null) { - cantStripThis( - returnTypeClass, notStrippable, - stubImportPackages - ) + cantStripThis(returnTypeClass, filter, notStrippable, stubImportPackages) for (tyItem in returnType.typeArgumentClasses()) { - cantStripThis( - tyItem, notStrippable, - stubImportPackages - ) + cantStripThis(tyItem, filter, notStrippable, stubImportPackages) } } } diff --git a/src/main/java/com/android/tools/metalava/Driver.kt b/src/main/java/com/android/tools/metalava/Driver.kt index 700ed5440085c35e6e038ef17df05b84df038b84..c5381263f72d1702ec2df910ec1c2f60d800799d 100644 --- a/src/main/java/com/android/tools/metalava/Driver.kt +++ b/src/main/java/com/android/tools/metalava/Driver.kt @@ -503,10 +503,8 @@ private fun loadFromSources(): Codebase { // General API checks for Android APIs AndroidApiChecks().check(codebase) - val ignoreShown = options.showUnannotated - - val filterEmit = ApiPredicate(codebase, ignoreShown = ignoreShown, ignoreRemoved = false) - val apiEmit = ApiPredicate(codebase, ignoreShown = ignoreShown) + val filterEmit = ApiPredicate(codebase, ignoreShown = true, ignoreRemoved = false) + val apiEmit = ApiPredicate(codebase, ignoreShown = true) val apiReference = ApiPredicate(codebase, ignoreShown = true) // Copy methods from soon-to-be-hidden parents into descendant classes, when necessary diff --git a/src/main/java/com/android/tools/metalava/Options.kt b/src/main/java/com/android/tools/metalava/Options.kt index 12d8b593642f06b2a08513803f2bd964c14578f8..b2c1c680fb8a43190567362d6f43865e857f0eda 100644 --- a/src/main/java/com/android/tools/metalava/Options.kt +++ b/src/main/java/com/android/tools/metalava/Options.kt @@ -551,8 +551,18 @@ class Options( ARG_LINT, "-lint" -> Errors.setErrorLevel(getValue(args, ++index), Severity.LINT) ARG_HIDE, "-hide" -> Errors.setErrorLevel(getValue(args, ++index), Severity.HIDDEN) - ARG_WARNINGS_AS_ERRORS, "-werror" -> warningsAreErrors = true - ARG_LINTS_AS_ERRORS, "-lerror" -> lintsAreErrors = true + ARG_WARNINGS_AS_ERRORS -> warningsAreErrors = true + ARG_LINTS_AS_ERRORS -> lintsAreErrors = true + "-werror" -> { + // Temporarily disabled; this is used in various builds but is pretty much + // never what we want. + //warningsAreErrors = true + } + "-lerror" -> { + // Temporarily disabled; this is used in various builds but is pretty much + // never what we want. + //lintsAreErrors = true + } ARG_CHECK_KOTLIN_INTEROP -> checkKotlinInterop = true diff --git a/src/main/java/com/android/tools/metalava/model/ClassItem.kt b/src/main/java/com/android/tools/metalava/model/ClassItem.kt index 0e09382ce7f90fc7a4c11aca7a4bcca979ae7812..de95759c2edb3ac256576d2fef62744e316742c2 100644 --- a/src/main/java/com/android/tools/metalava/model/ClassItem.kt +++ b/src/main/java/com/android/tools/metalava/model/ClassItem.kt @@ -652,6 +652,10 @@ interface ClassItem : Item { } fun allInnerClasses(includeSelf: Boolean = false): Sequence<ClassItem> { + if (!includeSelf && innerClasses().isEmpty()) { + return emptySequence() + } + val list = ArrayList<ClassItem>() if (includeSelf) { list.add(this) diff --git a/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt b/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt index 6545d5c058137276b7c89740e204e76140fea981..56d8375bb0c44037c17f5f5ebc85d7b48715ed2c 100644 --- a/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt +++ b/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt @@ -22,7 +22,6 @@ import com.android.tools.metalava.model.Codebase import com.android.tools.metalava.model.FieldItem import com.android.tools.metalava.model.Item import com.android.tools.metalava.model.MethodItem -import com.android.tools.metalava.options import java.util.function.Predicate open class ApiVisitor( @@ -79,7 +78,8 @@ open class ApiVisitor( nestInnerClasses: Boolean = false, /** Whether to ignore APIs with annotations in the --show-annotations list */ - ignoreShown: Boolean = options.showUnannotated, +// ignoreShown: Boolean = options.showUnannotated, + ignoreShown: Boolean = true, /** Whether to match APIs marked for removal instead of the normal API */ remove: Boolean = false, diff --git a/src/test/java/com/android/tools/metalava/StubsTest.kt b/src/test/java/com/android/tools/metalava/StubsTest.kt index 93ca225b7d12e3eefa97cc60f64aeabf7f64a487..5d7f7df3379b4b84435b1ffac31ad8bc81a8c797 100644 --- a/src/test/java/com/android/tools/metalava/StubsTest.kt +++ b/src/test/java/com/android/tools/metalava/StubsTest.kt @@ -35,10 +35,12 @@ class StubsTest : DriverTest() { api: String? = null, extraArguments: Array<String> = emptyArray(), docStubs: Boolean = false, + showAnnotations: Array<String> = emptyArray(), vararg sourceFiles: TestFile ) { check( *sourceFiles, + showAnnotations = showAnnotations, stubs = arrayOf(source), compatibilityMode = compatibilityMode, warnings = warnings, @@ -1444,6 +1446,88 @@ class StubsTest : DriverTest() { ) } + @Test + fun `Arguments to super constructors with showAnnotations`() { + // When overriding constructors we have to supply arguments + checkStubs( + showAnnotations = arrayOf("android.annotation.SystemApi"), + sourceFiles = + *arrayOf( + java( + """ + package test.pkg; + + @SuppressWarnings("WeakerAccess") + public class Constructors { + public class Parent { + public Parent(String s, int i, long l, boolean b, short sh) { + } + } + + public class Child extends Parent { + public Child(String s, int i, long l, boolean b, short sh) { + super(s, i, l, b, sh); + } + + private Child(String s) { + super(s, 0, 0, false, 0); + } + } + + public class Child2 extends Parent { + Child2(String s) { + super(s, 0, 0, false, 0); + } + } + + public class Child3 extends Child2 { + private Child3(String s) { + super("something"); + } + } + + public class Child4 extends Parent { + Child4(String s, HiddenClass hidden) { + super(s, 0, 0, true, 0); + } + } + /** @hide */ + public class HiddenClass { + } + } + """ + ) + ), + source = """ + package test.pkg; + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class Constructors { + public Constructors() { throw new RuntimeException("Stub!"); } + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class Child extends test.pkg.Constructors.Parent { + public Child(java.lang.String s, int i, long l, boolean b, short sh) { super(null, 0, 0, false, (short)0); throw new RuntimeException("Stub!"); } + } + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class Child2 extends test.pkg.Constructors.Parent { + Child2(java.lang.String s) { super(null, 0, 0, false, (short)0); throw new RuntimeException("Stub!"); } + } + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class Child3 extends test.pkg.Constructors.Child2 { + Child3(java.lang.String s) { super(null); throw new RuntimeException("Stub!"); } + } + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class Child4 extends test.pkg.Constructors.Parent { + Child4() { super(null, 0, 0, false, (short)0); throw new RuntimeException("Stub!"); } + } + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class Parent { + public Parent(java.lang.String s, int i, long l, boolean b, short sh) { throw new RuntimeException("Stub!"); } + } + } + """ + ) + } + // TODO: Add test to see what happens if I have Child4 in a different package which can't access the package private constructor of child3? @Test diff --git a/stub-annotations/src/main/java/androidx/annotation/Size.java b/stub-annotations/src/main/java/androidx/annotation/Size.java index 51e7bf152e840ba702ac6f19647cf369b0901b18..412a9ca788d79e6cd0cad9e0ec3f15482b0d6404 100644 --- a/stub-annotations/src/main/java/androidx/annotation/Size.java +++ b/stub-annotations/src/main/java/androidx/annotation/Size.java @@ -37,4 +37,8 @@ public @interface Size { long max() default Long.MAX_VALUE; /** The size must be a multiple of this factor */ long multiple() default 1; + + // STUBS ONLY: historical API range for when this permission applies. + // Used for merged annotations. + String apis() default ""; }