diff options
author | Jeff Sharkey <jsharkey@android.com> | 2017-06-05 09:55:40 -0600 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2017-06-05 14:00:57 -0600 |
commit | 8e8f96738e2a4e673db94a489ba2daf63807bb16 (patch) | |
tree | c8e2b14dbc00dbf6e4a04021276c23387382ed1b | |
parent | 1a41496282ebef74d8ace27a41c553e32b95f92d (diff) | |
download | platform_external_doclava-8e8f96738e2a4e673db94a489ba2daf63807bb16.tar.gz platform_external_doclava-8e8f96738e2a4e673db94a489ba2daf63807bb16.tar.bz2 platform_external_doclava-8e8f96738e2a4e673db94a489ba2daf63807bb16.zip |
Lint to verify @SystemApi permissions; more docs.
Most @SystemApi methods should be protected with system (or higher)
permissions, so verify that system service methods are annotated with
any relevant @RequiresPermission.
Auto-document new @SystemService annotation to guide developers on
how to obtain manager instances.
Test: make -j32 update-api && make -j32 offline-sdk-docs
Bug: 62263906
Change-Id: I57ae661241557970af4328ff21abec7629492c7c
-rw-r--r-- | res/assets/templates-sdk/class.cs | 4 | ||||
-rw-r--r-- | res/assets/templates-sdk/macros_override.cs | 11 | ||||
-rw-r--r-- | src/com/google/doclava/AndroidAuxSource.java | 51 | ||||
-rw-r--r-- | src/com/google/doclava/AndroidLinter.java | 10 | ||||
-rw-r--r-- | src/com/google/doclava/AuxSource.java | 6 | ||||
-rw-r--r-- | src/com/google/doclava/ClassInfo.java | 1 | ||||
-rw-r--r-- | src/com/google/doclava/ClearPage.java | 7 | ||||
-rw-r--r-- | src/com/google/doclava/Errors.java | 20 | ||||
-rw-r--r-- | src/com/google/doclava/FederationTagger.java | 5 | ||||
-rw-r--r-- | src/com/google/doclava/SinceTagger.java | 5 | ||||
-rw-r--r-- | src/com/google/doclava/Stubs.java | 34 |
11 files changed, 140 insertions, 14 deletions
diff --git a/res/assets/templates-sdk/class.cs b/res/assets/templates-sdk/class.cs index 9ce6a6d..66134f8 100644 --- a/res/assets/templates-sdk/class.cs +++ b/res/assets/templates-sdk/class.cs @@ -319,6 +319,10 @@ if:subcount(class.subclasses.direct) && !class.subclasses.hidden ?> <p><?cs call:tag_list(class.descr) ?></p> <?cs /if ?> +<?cs if:subcount(class.descrAux) ?> + <?cs call:aux_tag_list(class.descrAux) ?> +<?cs /if ?> + <?cs call:see_also_tags(class.seeAlso) ?> <?cs ################# diff --git a/res/assets/templates-sdk/macros_override.cs b/res/assets/templates-sdk/macros_override.cs index 10fb9c1..92be480 100644 --- a/res/assets/templates-sdk/macros_override.cs +++ b/res/assets/templates-sdk/macros_override.cs @@ -59,6 +59,7 @@ def:aux_tag_list(tags) ?><?cs elif:tag.kind == "@range" ?><?cs call:dump_range(tag) ?><?cs elif:tag.kind == "@intDef" ?><?cs call:dump_int_def(tag) ?><?cs elif:tag.kind == "@permission" ?><?cs call:dump_permission(tag) ?><?cs + elif:tag.kind == "@service" ?><?cs call:dump_service(tag) ?><?cs /if ?><?cs /each ?></p><?cs /def ?><?cs @@ -106,3 +107,13 @@ def:dump_permission(tag) ?>Requires the <?cs else ?> permission.<?cs /if ?><?cs /def ?> + +# Print output for @service tags ?><?cs +def:dump_service(tag) ?>Instances of this class must be obtained using <?cs + loop:i = #0, subcount(tag.values) - 1, #2 ?><?cs + call:tag_list(tag.values[i].commentTags) ?> with the argument <?cs + call:tag_list(tag.values[i+1].commentTags) ?><?cs + if i < subcount(tag.values) - 2 ?> or <?cs + /if ?><?cs + /loop ?>.<?cs +/def ?> diff --git a/src/com/google/doclava/AndroidAuxSource.java b/src/com/google/doclava/AndroidAuxSource.java index 93341ed..d006af0 100644 --- a/src/com/google/doclava/AndroidAuxSource.java +++ b/src/com/google/doclava/AndroidAuxSource.java @@ -22,12 +22,59 @@ import java.util.List; import java.util.Map; public class AndroidAuxSource implements AuxSource { - private static final int TYPE_METHOD = 0; - private static final int TYPE_FIELD = 1; + private static final int TYPE_FIELD = 0; + private static final int TYPE_METHOD = 1; private static final int TYPE_PARAM = 2; private static final int TYPE_RETURN = 3; @Override + public TagInfo[] classAuxTags(ClassInfo clazz) { + if (hasSuppress(clazz.annotations())) return TagInfo.EMPTY_ARRAY; + ArrayList<TagInfo> tags = new ArrayList<>(); + for (AnnotationInstanceInfo annotation : clazz.annotations()) { + // Document system services + if (annotation.type().qualifiedNameMatches("android", "annotation.SystemService")) { + ArrayList<TagInfo> valueTags = new ArrayList<>(); + valueTags + .add(new ParsedTagInfo("", "", + "{@link android.content.Context#getSystemService(Class)" + + " Context.getSystemService(Class)}", + null, SourcePositionInfo.UNKNOWN)); + valueTags.add(new ParsedTagInfo("", "", + "{@code " + clazz.name() + ".class}", null, + SourcePositionInfo.UNKNOWN)); + + ClassInfo contextClass = annotation.type().findClass("android.content.Context"); + for (AnnotationValueInfo val : annotation.elementValues()) { + switch (val.element().name()) { + case "value": + final String expected = String.valueOf(val.value()); + for (FieldInfo field : contextClass.fields()) { + if (field.isHiddenOrRemoved()) continue; + if (String.valueOf(field.constantValue()).equals(expected)) { + valueTags.add(new ParsedTagInfo("", "", + "{@link android.content.Context#getSystemService(String)" + + " Context.getSystemService(String)}", + null, SourcePositionInfo.UNKNOWN)); + valueTags.add(new ParsedTagInfo("", "", + "{@link android.content.Context#" + field.name() + + " Context." + field.name() + "}", + null, SourcePositionInfo.UNKNOWN)); + } + } + break; + } + } + + Map<String, String> args = new HashMap<>(); + tags.add(new AuxTagInfo("@service", "@service", SourcePositionInfo.UNKNOWN, args, + valueTags.toArray(TagInfo.getArray(valueTags.size())))); + } + } + return tags.toArray(TagInfo.getArray(tags.size())); + } + + @Override public TagInfo[] fieldAuxTags(FieldInfo field) { if (hasSuppress(field)) return TagInfo.EMPTY_ARRAY; return auxTags(TYPE_FIELD, field.annotations()); diff --git a/src/com/google/doclava/AndroidLinter.java b/src/com/google/doclava/AndroidLinter.java index 168448e..399226d 100644 --- a/src/com/google/doclava/AndroidLinter.java +++ b/src/com/google/doclava/AndroidLinter.java @@ -45,18 +45,18 @@ public class AndroidLinter implements Linter { if (text.contains("Broadcast Action:") || (text.contains("protected intent") && text.contains("system"))) { if (!hasBehavior) { - Errors.error(Errors.BROADCAST_BEHAVIOR, field.position(), + Errors.error(Errors.BROADCAST_BEHAVIOR, field, "Field '" + field.name() + "' is missing @BroadcastBehavior"); } if (!hasSdkConstant) { - Errors.error(Errors.SDK_CONSTANT, field.position(), "Field '" + field.name() + Errors.error(Errors.SDK_CONSTANT, field, "Field '" + field.name() + "' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)"); } } if (text.contains("Activity Action:")) { if (!hasSdkConstant) { - Errors.error(Errors.SDK_CONSTANT, field.position(), "Field '" + field.name() + Errors.error(Errors.SDK_CONSTANT, field, "Field '" + field.name() + "' is missing @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)"); } } @@ -95,7 +95,7 @@ public class AndroidLinter implements Linter { String perm = String.valueOf(value.value()); if (perm.indexOf('.') >= 0) perm = perm.substring(perm.lastIndexOf('.') + 1); if (text.contains(perm)) { - Errors.error(Errors.REQUIRES_PERMISSION, method.position(), "Method '" + method.name() + Errors.error(Errors.REQUIRES_PERMISSION, method, "Method '" + method.name() + "' documentation mentions permissions already declared by @RequiresPermission"); } } @@ -103,7 +103,7 @@ public class AndroidLinter implements Linter { } if (text.contains("android.Manifest.permission") || text.contains("android.permission.")) { if (!hasAnnotation) { - Errors.error(Errors.REQUIRES_PERMISSION, method.position(), "Method '" + method.name() + Errors.error(Errors.REQUIRES_PERMISSION, method, "Method '" + method.name() + "' documentation mentions permissions without declaring @RequiresPermission"); } } diff --git a/src/com/google/doclava/AuxSource.java b/src/com/google/doclava/AuxSource.java index 09215fd..03594f5 100644 --- a/src/com/google/doclava/AuxSource.java +++ b/src/com/google/doclava/AuxSource.java @@ -17,6 +17,7 @@ package com.google.doclava; public interface AuxSource { + public TagInfo[] classAuxTags(ClassInfo clazz); public TagInfo[] fieldAuxTags(FieldInfo field); public TagInfo[] methodAuxTags(MethodInfo method); public TagInfo[] paramAuxTags(MethodInfo method, ParameterInfo param); @@ -25,6 +26,11 @@ public interface AuxSource { class EmptyAuxSource implements AuxSource { @Override + public TagInfo[] classAuxTags(ClassInfo clazz) { + return TagInfo.EMPTY_ARRAY; + } + + @Override public TagInfo[] fieldAuxTags(FieldInfo field) { return TagInfo.EMPTY_ARRAY; } diff --git a/src/com/google/doclava/ClassInfo.java b/src/com/google/doclava/ClassInfo.java index b2e3344..20addc6 100644 --- a/src/com/google/doclava/ClassInfo.java +++ b/src/com/google/doclava/ClassInfo.java @@ -1216,6 +1216,7 @@ public class ClassInfo extends DocInfo implements ContainerInfo, Comparable, Sco // class description TagInfo.makeHDF(data, "class.descr", inlineTags()); + TagInfo.makeHDF(data, "class.descrAux", Doclava.auxSource.classAuxTags(this)); TagInfo.makeHDF(data, "class.seeAlso", comment().seeTags()); TagInfo.makeHDF(data, "class.deprecated", deprecatedTags()); diff --git a/src/com/google/doclava/ClearPage.java b/src/com/google/doclava/ClearPage.java index 1b4b5ca..95392e0 100644 --- a/src/com/google/doclava/ClearPage.java +++ b/src/com/google/doclava/ClearPage.java @@ -177,9 +177,10 @@ public class ClearPage { return; } if (!isValidContentType(allowExcepted, toPath, DROIDDOC_VALID_CONTENT_TYPES)) { - Errors.error(Errors.INVALID_CONTENT_TYPE, null, "Failed to process " + from - + ": Invalid file type. Please move the file to frameworks/base/docs/image_sources/... or docs/downloads/..."); - return; + Errors.error(Errors.INVALID_CONTENT_TYPE, (SourcePositionInfo) null, "Failed to process " + + from + ": Invalid file type. Please move the file to " + + "frameworks/base/docs/image_sources/... or docs/downloads/..."); + return; } long sizel = from.length(); diff --git a/src/com/google/doclava/Errors.java b/src/com/google/doclava/Errors.java index de92fdf..48202a3 100644 --- a/src/com/google/doclava/Errors.java +++ b/src/com/google/doclava/Errors.java @@ -81,6 +81,26 @@ public class Errors { } } + public static void error(Error error, MemberInfo mi, String text) { + if (error.getLevel() == Errors.LINT) { + final String ident = "Doclava" + error.code; + for (AnnotationInstanceInfo a : mi.annotations()) { + if (a.type().qualifiedNameMatches("android", "annotation.SuppressLint")) { + for (AnnotationValueInfo val : a.elementValues()) { + if ("value".equals(val.element().name())) { + for (AnnotationValueInfo inner : (ArrayList<AnnotationValueInfo>) val.value()) { + if (ident.equals(String.valueOf(inner.value()))) { + return; + } + } + } + } + } + } + } + error(error, mi.position(), text); + } + public static void error(Error error, SourcePositionInfo where, String text) { if (error.getLevel() == HIDDEN) { return; diff --git a/src/com/google/doclava/FederationTagger.java b/src/com/google/doclava/FederationTagger.java index 5eda42c..f3603a5 100644 --- a/src/com/google/doclava/FederationTagger.java +++ b/src/com/google/doclava/FederationTagger.java @@ -66,7 +66,8 @@ public final class FederationTagger { for (String name : federatedXmls.keySet()) { if (!federatedUrls.containsKey(name)) { - Errors.error(Errors.NO_FEDERATION_DATA, null, "Unknown documentation site for " + name); + Errors.error(Errors.NO_FEDERATION_DATA, (SourcePositionInfo) null, + "Unknown documentation site for " + name); } } @@ -83,7 +84,7 @@ public final class FederationTagger { if (e.getMessage() != null) { error += ": " + e.getMessage(); } - Errors.error(Errors.NO_FEDERATION_DATA, null, error); + Errors.error(Errors.NO_FEDERATION_DATA, (SourcePositionInfo) null, error); } } diff --git a/src/com/google/doclava/SinceTagger.java b/src/com/google/doclava/SinceTagger.java index b8ad418..ce2cee3 100644 --- a/src/com/google/doclava/SinceTagger.java +++ b/src/com/google/doclava/SinceTagger.java @@ -66,8 +66,9 @@ public class SinceTagger { } catch (ApiParseException e) { StringWriter stackTraceWriter = new StringWriter(); e.printStackTrace(new PrintWriter(stackTraceWriter)); - Errors.error(Errors.BROKEN_SINCE_FILE, null, "Failed to parse " + xmlFile - + " for " + versionName + " since data.\n" + stackTraceWriter.toString()); + Errors.error(Errors.BROKEN_SINCE_FILE, (SourcePositionInfo) null, + "Failed to parse " + xmlFile + " for " + versionName + " since data.\n" + + stackTraceWriter.toString()); continue; } diff --git a/src/com/google/doclava/Stubs.java b/src/com/google/doclava/Stubs.java index 5c70820..added35 100644 --- a/src/com/google/doclava/Stubs.java +++ b/src/com/google/doclava/Stubs.java @@ -1292,6 +1292,40 @@ public class Stubs { return hasWrittenPackageHead; } + // Look for Android @SystemApi exposed outside the normal SDK; we require + // that they're protected with a system permission. + if (Doclava.android && Doclava.showAnnotations.contains("android.annotation.SystemApi") + && !(predicate instanceof RemovedPredicate)) { + boolean systemService = false; + for (AnnotationInstanceInfo a : cl.annotations()) { + if (a.type().qualifiedNameMatches("android", "annotation.SystemService")) { + systemService = true; + } + } + if (systemService) { + for (MethodInfo mi : methods) { + boolean hasPermission = false; + for (AnnotationInstanceInfo a : mi.annotations()) { + if (a.type().qualifiedNameMatches("android", "annotation.RequiresPermission")) { + hasPermission = true; + } + } + for (ParameterInfo pi : mi.parameters()) { + for (AnnotationInstanceInfo a : pi.annotations()) { + if (a.type().qualifiedNameMatches("android", "annotation.RequiresPermission")) { + hasPermission = true; + } + } + } + if (!hasPermission) { + Errors.error(Errors.REQUIRES_PERMISSION, mi, + "Method '" + mi.name() + "' exposed as @SystemApi must be" + + " protected with a system permission."); + } + } + } + } + if (!hasWrittenPackageHead) { hasWrittenPackageHead = true; apiWriter.print("package "); |