summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2017-06-05 09:55:40 -0600
committerJeff Sharkey <jsharkey@android.com>2017-06-05 14:00:57 -0600
commit8e8f96738e2a4e673db94a489ba2daf63807bb16 (patch)
treec8e2b14dbc00dbf6e4a04021276c23387382ed1b
parent1a41496282ebef74d8ace27a41c553e32b95f92d (diff)
downloadplatform_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.cs4
-rw-r--r--res/assets/templates-sdk/macros_override.cs11
-rw-r--r--src/com/google/doclava/AndroidAuxSource.java51
-rw-r--r--src/com/google/doclava/AndroidLinter.java10
-rw-r--r--src/com/google/doclava/AuxSource.java6
-rw-r--r--src/com/google/doclava/ClassInfo.java1
-rw-r--r--src/com/google/doclava/ClearPage.java7
-rw-r--r--src/com/google/doclava/Errors.java20
-rw-r--r--src/com/google/doclava/FederationTagger.java5
-rw-r--r--src/com/google/doclava/SinceTagger.java5
-rw-r--r--src/com/google/doclava/Stubs.java34
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 ");