summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java325
-rw-r--r--errorprone/tests/java/com/google/errorprone/bugpatterns/android/EfficientXmlCheckerTest.java320
-rw-r--r--errorprone/tests/res/android/util/Base64.java27
-rw-r--r--errorprone/tests/res/android/util/Xml.java30
-rw-r--r--errorprone/tests/res/com/android/internal/util/FastXmlSerializer.java20
-rw-r--r--errorprone/tests/res/com/android/internal/util/HexDump.java27
-rw-r--r--errorprone/tests/res/libcore/util/HexEncoding.java27
7 files changed, 776 insertions, 0 deletions
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java
new file mode 100644
index 000000000000..b5f26e7dc9dd
--- /dev/null
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2020 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.google.errorprone.bugpatterns.android;
+
+import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
+import static com.google.errorprone.matchers.Matchers.anyOf;
+import static com.google.errorprone.matchers.Matchers.instanceMethod;
+import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
+import static com.google.errorprone.matchers.Matchers.methodInvocation;
+import static com.google.errorprone.matchers.Matchers.staticMethod;
+import static com.google.errorprone.matchers.Matchers.stringLiteral;
+
+import com.google.auto.service.AutoService;
+import com.google.common.base.Objects;
+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.matchers.Matcher;
+import com.google.errorprone.util.ASTHelpers;
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePathScanner;
+import com.sun.tools.javac.code.Type;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import javax.lang.model.element.Name;
+
+/**
+ * Android offers {@code TypedXmlSerializer} and {@code TypedXmlPullParser} to
+ * more efficiently store primitive values.
+ * <p>
+ * This checker identifies callers that are manually converting strings instead
+ * of relying on efficient strongly-typed methods.
+ */
+@AutoService(BugChecker.class)
+@BugPattern(
+ name = "AndroidFrameworkEfficientXml",
+ summary = "Verifies efficient XML best-practices",
+ severity = WARNING)
+public final class EfficientXmlChecker extends BugChecker
+ implements MethodInvocationTreeMatcher, NewClassTreeMatcher {
+ private static final String STRING = "java.lang.String";
+ private static final String INTEGER = "java.lang.Integer";
+ private static final String LONG = "java.lang.Long";
+ private static final String FLOAT = "java.lang.Float";
+ private static final String DOUBLE = "java.lang.Double";
+ private static final String BOOLEAN = "java.lang.Boolean";
+
+ private static final Matcher<ExpressionTree> BOOLEAN_STRING_LITERAL = stringLiteral(
+ Pattern.compile("(true|false)"));
+
+ private static final Matcher<ExpressionTree> PRIMITIVE_TO_STRING = anyOf(
+ methodInvocation(staticMethod().onClass(INTEGER).named("toString")),
+ methodInvocation(staticMethod().onClass(INTEGER).named("toHexString")),
+ methodInvocation(staticMethod().onClass(LONG).named("toString")),
+ methodInvocation(staticMethod().onClass(LONG).named("toHexString")),
+ methodInvocation(staticMethod().onClass(FLOAT).named("toString")),
+ methodInvocation(staticMethod().onClass(DOUBLE).named("toString")),
+ methodInvocation(staticMethod().onClass(BOOLEAN).named("toString")),
+ methodInvocation(instanceMethod().onExactClass(INTEGER).named("toString")),
+ methodInvocation(instanceMethod().onExactClass(LONG).named("toString")),
+ methodInvocation(instanceMethod().onExactClass(FLOAT).named("toString")),
+ methodInvocation(instanceMethod().onExactClass(DOUBLE).named("toString")),
+ methodInvocation(instanceMethod().onExactClass(BOOLEAN).named("toString")));
+
+ private static final Matcher<ExpressionTree> VALUE_OF_PRIMITIVE = anyOf(
+ methodInvocation(staticMethod().onClass(STRING).withSignature("valueOf(int)")),
+ methodInvocation(staticMethod().onClass(STRING).withSignature("valueOf(long)")),
+ methodInvocation(staticMethod().onClass(STRING).withSignature("valueOf(float)")),
+ methodInvocation(staticMethod().onClass(STRING).withSignature("valueOf(double)")),
+ methodInvocation(staticMethod().onClass(STRING).withSignature("valueOf(boolean)")));
+
+ private static final Matcher<ExpressionTree> VALUE_OF_OBJECT = methodInvocation(
+ staticMethod().onClass(STRING).withSignature("valueOf(java.lang.Object)"));
+
+ private static final Matcher<ExpressionTree> PRIMITIVE_PARSE = anyOf(
+ methodInvocation(staticMethod().onClass(INTEGER).named("parseInt")),
+ methodInvocation(staticMethod().onClass(INTEGER)
+ .withSignature("valueOf(java.lang.String)")),
+ methodInvocation(staticMethod().onClass(INTEGER)
+ .withSignature("valueOf(java.lang.String,int)")),
+ methodInvocation(staticMethod().onClass(LONG).named("parseLong")),
+ methodInvocation(staticMethod().onClass(LONG)
+ .withSignature("valueOf(java.lang.String)")),
+ methodInvocation(staticMethod().onClass(LONG)
+ .withSignature("valueOf(java.lang.String,int)")),
+ methodInvocation(staticMethod().onClass(FLOAT).named("parseFloat")),
+ methodInvocation(staticMethod().onClass(FLOAT)
+ .withSignature("valueOf(java.lang.String)")),
+ methodInvocation(staticMethod().onClass(DOUBLE).named("parseDouble")),
+ methodInvocation(staticMethod().onClass(DOUBLE)
+ .withSignature("valueOf(java.lang.String)")),
+ methodInvocation(staticMethod().onClass(BOOLEAN).named("parseBoolean")),
+ methodInvocation(staticMethod().onClass(BOOLEAN)
+ .withSignature("valueOf(java.lang.String)")));
+
+ private static final Matcher<Tree> IS_FAST_XML_SERIALIZER =
+ isSubtypeOf("com.android.internal.util.FastXmlSerializer");
+
+ private static final Matcher<ExpressionTree> WRITE_ATTRIBUTE = methodInvocation(
+ instanceMethod().onDescendantOf("org.xmlpull.v1.XmlSerializer")
+ .named("attribute"));
+
+ private static final Matcher<ExpressionTree> READ_ATTRIBUTE = methodInvocation(
+ instanceMethod().onDescendantOf("org.xmlpull.v1.XmlPullParser")
+ .named("getAttributeValue"));
+
+ private static final Matcher<ExpressionTree> XML_FACTORY = methodInvocation(staticMethod()
+ .onClass("android.util.Xml").namedAnyOf("newSerializer", "newPullParser"));
+
+ private static final Matcher<ExpressionTree> BYTES_TO_STRING = anyOf(
+ methodInvocation(staticMethod().onClass("android.util.Base64")
+ .named("encodeToString")),
+ methodInvocation(instanceMethod().onDescendantOf("java.util.Base64.Encoder")
+ .named("encodeToString")),
+ methodInvocation(staticMethod().onClass("libcore.util.HexEncoding")
+ .named("encodeToString")),
+ methodInvocation(staticMethod().onClass("com.android.internal.util.HexDump")
+ .named("toHexString")));
+
+ private static final Matcher<ExpressionTree> BYTES_FROM_STRING = anyOf(
+ methodInvocation(staticMethod().onClass("android.util.Base64")
+ .named("decode")),
+ methodInvocation(instanceMethod().onDescendantOf("java.util.Base64.Decoder")
+ .named("decode")),
+ methodInvocation(staticMethod().onClass("libcore.util.HexEncoding")
+ .named("decode")),
+ methodInvocation(staticMethod().onClass("com.android.internal.util.HexDump")
+ .named("hexStringToByteArray")));
+
+ private static final String MESSAGE_WRITE = "Primitive values can be written more "
+ + "efficiently by using TypedXmlSerializer overloads";
+ private static final String MESSAGE_READ = "Primitive values can be parsed more "
+ + "efficiently by using TypedXmlPullParser overloads";
+ private static final String MESSAGE_CTOR = "Primitive values can be parsed more "
+ + "efficiently by using Xml.resolveSerializer() and/or Xml.resolvePullParser()";
+
+ /**
+ * Determine if the given expression is attempting to convert a primitive to
+ * a {@link String}.
+ */
+ private static final Matcher<ExpressionTree> CONVERT_PRIMITIVE_TO_STRING =
+ new Matcher<ExpressionTree>() {
+ @Override
+ public boolean matches(ExpressionTree tree, VisitorState state) {
+ if (PRIMITIVE_TO_STRING.matches(tree, state)) {
+ final List<? extends ExpressionTree> args = ((MethodInvocationTree) tree)
+ .getArguments();
+ // We're only interested in base-10 or base-16 numerical conversions
+ if (args.size() <= 1 || String.valueOf(args.get(1)).equals("10")
+ || String.valueOf(args.get(1)).equals("16")) {
+ return true;
+ }
+ } else if (VALUE_OF_PRIMITIVE.matches(tree, state)) {
+ return true;
+ } else if (VALUE_OF_OBJECT.matches(tree, state)) {
+ final Type type = ASTHelpers.getResultType(((MethodInvocationTree) tree)
+ .getArguments().get(0));
+ if (ASTHelpers.isSameType(type, state.getTypeFromString(INTEGER), state)
+ || ASTHelpers.isSameType(type, state.getTypeFromString(LONG), state)
+ || ASTHelpers.isSameType(type, state.getTypeFromString(FLOAT), state)
+ || ASTHelpers.isSameType(type, state.getTypeFromString(DOUBLE), state)
+ || ASTHelpers.isSameType(type, state.getTypeFromString(BOOLEAN), state)) {
+ return true;
+ }
+ } else if (BOOLEAN_STRING_LITERAL.matches(tree, state)) {
+ return true;
+ } else if (BYTES_TO_STRING.matches(tree, state)) {
+ return true;
+ }
+ return false;
+ }
+ };
+
+ /**
+ * Determine if the given expression is attempting to convert a
+ * {@link String} to a primitive.
+ */
+ private static final Matcher<ExpressionTree> CONVERT_STRING_TO_PRIMITIVE =
+ new Matcher<ExpressionTree>() {
+ @Override
+ public boolean matches(ExpressionTree tree, VisitorState state) {
+ if (PRIMITIVE_PARSE.matches(tree, state)) {
+ final List<? extends ExpressionTree> args = ((MethodInvocationTree) tree)
+ .getArguments();
+ // We're only interested in base-10 or base-16 numerical conversions
+ if (args.size() <= 1 || String.valueOf(args.get(1)).equals("10")
+ || String.valueOf(args.get(1)).equals("16")) {
+ return true;
+ }
+ } else if (BYTES_FROM_STRING.matches(tree, state)) {
+ return true;
+ }
+ return false;
+ }
+ };
+
+ @Override
+ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
+ if (WRITE_ATTRIBUTE.matches(tree, state)) {
+ final List<? extends ExpressionTree> args = tree.getArguments();
+ final ExpressionTree writeSource = args.get(2);
+ if (CONVERT_PRIMITIVE_TO_STRING.matches(writeSource, state)) {
+ return buildDescription(tree).setMessage(MESSAGE_WRITE).build();
+ }
+
+ // Hunt around for related conversions
+ if (writeSource instanceof IdentifierTree) {
+ final Name name = ((IdentifierTree) writeSource).getName();
+ final Description res = new TreePathScanner<Description, Void>() {
+ @Override
+ public Description reduce(Description r1, Description r2) {
+ return (r1 != null) ? r1 : r2;
+ }
+
+ @Override
+ public Description visitVariable(VariableTree node, Void param) {
+ return visitWriteSource(node.getName(), node.getInitializer());
+ }
+
+ @Override
+ public Description visitAssignment(AssignmentTree node, Void param) {
+ final ExpressionTree variable = node.getVariable();
+ if (variable instanceof IdentifierTree) {
+ return visitWriteSource(((IdentifierTree) variable).getName(),
+ node.getExpression());
+ } else {
+ return super.visitAssignment(node, param);
+ }
+ }
+
+ private Description visitWriteSource(Name target, ExpressionTree source) {
+ if (CONVERT_PRIMITIVE_TO_STRING.matches(source, state)
+ && Objects.equal(name, target)) {
+ return buildDescription(source).setMessage(MESSAGE_WRITE).build();
+ } else {
+ return null;
+ }
+ }
+ }.scan(state.findPathToEnclosing(MethodTree.class), null);
+ if (res != null) {
+ return res;
+ }
+ }
+ } else if (READ_ATTRIBUTE.matches(tree, state)) {
+ final Tree readDest = state.getPath().getParentPath().getLeaf();
+ if (readDest instanceof ExpressionTree
+ && CONVERT_STRING_TO_PRIMITIVE.matches((ExpressionTree) readDest, state)) {
+ return buildDescription(tree).setMessage(MESSAGE_READ).build();
+ }
+
+ // Hunt around for related conversions
+ Name name = null;
+ if (readDest instanceof VariableTree) {
+ name = ((VariableTree) readDest).getName();
+ } else if (readDest instanceof AssignmentTree) {
+ final ExpressionTree variable = ((AssignmentTree) readDest).getVariable();
+ if (variable instanceof IdentifierTree) {
+ name = ((IdentifierTree) variable).getName();
+ }
+ }
+ if (name != null) {
+ final Name fName = name;
+ final Description res = new TreePathScanner<Description, Void>() {
+ @Override
+ public Description reduce(Description r1, Description r2) {
+ return (r1 != null) ? r1 : r2;
+ }
+
+ @Override
+ public Description visitMethodInvocation(MethodInvocationTree node,
+ Void param) {
+ if (CONVERT_STRING_TO_PRIMITIVE.matches(node, state)) {
+ final ExpressionTree arg = node.getArguments().get(0);
+ if (arg instanceof IdentifierTree
+ && Objects.equal(((IdentifierTree) arg).getName(), fName)) {
+ return buildDescription(node).setMessage(MESSAGE_READ).build();
+ }
+ }
+ return super.visitMethodInvocation(node, param);
+ }
+ }.scan(state.findPathToEnclosing(MethodTree.class), null);
+ if (res != null) {
+ return res;
+ }
+ }
+ } else if (XML_FACTORY.matches(tree, state)) {
+ return buildDescription(tree).setMessage(MESSAGE_CTOR).build();
+ }
+ return Description.NO_MATCH;
+ }
+
+ @Override
+ public Description matchNewClass(NewClassTree tree, VisitorState state) {
+ if (IS_FAST_XML_SERIALIZER.matches(tree, state)) {
+ return buildDescription(tree).setMessage(MESSAGE_CTOR).build();
+ }
+ return Description.NO_MATCH;
+ }
+}
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/EfficientXmlCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/EfficientXmlCheckerTest.java
new file mode 100644
index 000000000000..084fb255ed7b
--- /dev/null
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/EfficientXmlCheckerTest.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2020 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.google.errorprone.bugpatterns.android;
+
+import com.google.errorprone.CompilationTestHelper;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class EfficientXmlCheckerTest {
+ private CompilationTestHelper compilationHelper;
+
+ @Before
+ public void setUp() {
+ compilationHelper = CompilationTestHelper.newInstance(
+ EfficientXmlChecker.class, getClass());
+ }
+
+ @Test
+ public void testCtor() {
+ compilationHelper
+ .addSourceFile("/android/util/Xml.java")
+ .addSourceFile("/com/android/internal/util/FastXmlSerializer.java")
+ .addSourceLines("Example.java",
+ "import android.util.Xml;",
+ "import com.android.internal.util.FastXmlSerializer;",
+ "public class Example {",
+ " public void writer() throws Exception {",
+ " // BUG: Diagnostic contains:",
+ " Xml.newSerializer();",
+ " // BUG: Diagnostic contains:",
+ " new FastXmlSerializer();",
+ " }",
+ " public void reader() throws Exception {",
+ " // BUG: Diagnostic contains:",
+ " Xml.newPullParser();",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testWrite() {
+ compilationHelper
+ .addSourceLines("Example.java",
+ "import org.xmlpull.v1.XmlSerializer;",
+ "public class Example {",
+ " public void typical(XmlSerializer out) throws Exception {",
+ " out.attribute(null, null, null);",
+ " out.attribute(null, null, \"foo\");",
+ " out.attribute(null, null, String.valueOf(null));",
+ " out.attribute(null, null, String.valueOf(\"foo\"));",
+ " }",
+ " public void rawBoolean(XmlSerializer out) throws Exception {",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, \"true\");",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, \"false\");",
+ " }",
+ " public void toString(XmlSerializer out) throws Exception {",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, Integer.toString(42));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, Integer.toString(42, 10));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, Integer.toString(42, 16));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, Integer.toHexString(42));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, Long.toString(42L));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, Long.toString(42L, 10));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, Long.toString(42L, 16));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, Long.toHexString(42L));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, Float.toString(42f));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, Double.toString(42d));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, Boolean.toString(true));",
+ " }",
+ " public void toStringBoxed(XmlSerializer out) throws Exception {",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, Integer.valueOf(42).toString());",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, Long.valueOf(42L).toString());",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, Float.valueOf(42f).toString());",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, Double.valueOf(42d).toString());",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, Boolean.valueOf(true).toString());",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, Boolean.TRUE.toString());",
+ " }",
+ " public void valueOf(XmlSerializer out) throws Exception {",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, String.valueOf(42));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, String.valueOf(42L));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, String.valueOf(42f));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, String.valueOf(42d));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, String.valueOf(true));",
+ " }",
+ " public void valueOfBoxed(XmlSerializer out) throws Exception {",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, String.valueOf(Integer.valueOf(42)));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, String.valueOf(Long.valueOf(42L)));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, String.valueOf(Float.valueOf(42f)));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, String.valueOf(Double.valueOf(42d)));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, String.valueOf(Boolean.valueOf(true)));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null, String.valueOf(Boolean.TRUE));",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testWrite_Indirect() {
+ compilationHelper
+ .addSourceLines("Example.java",
+ "import org.xmlpull.v1.XmlSerializer;",
+ "public class Example {",
+ " XmlSerializer out;",
+ " public void argUnknown(String arg) throws Exception {",
+ " out.attribute(null, null, arg);",
+ " }",
+ " public void argNull(String arg) throws Exception {",
+ " arg = null;",
+ " out.attribute(null, null, arg);",
+ " }",
+ " public void argValueOfNull(String arg) throws Exception {",
+ " arg = String.valueOf(null);",
+ " out.attribute(null, null, arg);",
+ " }",
+ " public void argToString(String arg) throws Exception {",
+ " // BUG: Diagnostic contains:",
+ " arg = Integer.toString(42);",
+ " out.attribute(null, null, arg);",
+ " }",
+ " public void argValueOf(String arg) throws Exception {",
+ " // BUG: Diagnostic contains:",
+ " arg = String.valueOf(42);",
+ " out.attribute(null, null, arg);",
+ " }",
+ " public void directToString() throws Exception {",
+ " // BUG: Diagnostic contains:",
+ " String arg = Integer.toString(42);",
+ " out.attribute(null, null, arg);",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testWrite_Bytes() {
+ compilationHelper
+ .addSourceFile("/android/util/Base64.java")
+ .addSourceFile("/libcore/util/HexEncoding.java")
+ .addSourceFile("/com/android/internal/util/HexDump.java")
+ .addSourceLines("Example.java",
+ "import org.xmlpull.v1.XmlSerializer;",
+ "public class Example {",
+ " XmlSerializer out;",
+ " public void bytes(byte[] arg) throws Exception {",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null,",
+ " android.util.Base64.encodeToString(arg, 0));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null,",
+ " java.util.Base64.getEncoder().encodeToString(arg));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null,",
+ " libcore.util.HexEncoding.encodeToString(arg));",
+ " // BUG: Diagnostic contains:",
+ " out.attribute(null, null,",
+ " com.android.internal.util.HexDump.toHexString(arg));",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testRead() {
+ compilationHelper
+ .addSourceLines("Example.java",
+ "import org.xmlpull.v1.XmlPullParser;",
+ "public class Example {",
+ " public void typical(XmlPullParser in) throws Exception {",
+ " in.getAttributeValue(null, null);",
+ " }",
+ " public void parse(XmlPullParser in) throws Exception {",
+ " // BUG: Diagnostic contains:",
+ " Integer.parseInt(in.getAttributeValue(null, null));",
+ " // BUG: Diagnostic contains:",
+ " Integer.parseInt(in.getAttributeValue(null, null), 10);",
+ " // BUG: Diagnostic contains:",
+ " Integer.parseInt(in.getAttributeValue(null, null), 16);",
+ " // BUG: Diagnostic contains:",
+ " Long.parseLong(in.getAttributeValue(null, null));",
+ " // BUG: Diagnostic contains:",
+ " Long.parseLong(in.getAttributeValue(null, null), 10);",
+ " // BUG: Diagnostic contains:",
+ " Long.parseLong(in.getAttributeValue(null, null), 16);",
+ " // BUG: Diagnostic contains:",
+ " Float.parseFloat(in.getAttributeValue(null, null));",
+ " // BUG: Diagnostic contains:",
+ " Double.parseDouble(in.getAttributeValue(null, null));",
+ " // BUG: Diagnostic contains:",
+ " Boolean.parseBoolean(in.getAttributeValue(null, null));",
+ " }",
+ " public void valueOf(XmlPullParser in) throws Exception {",
+ " // BUG: Diagnostic contains:",
+ " Integer.valueOf(in.getAttributeValue(null, null));",
+ " // BUG: Diagnostic contains:",
+ " Long.valueOf(in.getAttributeValue(null, null));",
+ " // BUG: Diagnostic contains:",
+ " Float.valueOf(in.getAttributeValue(null, null));",
+ " // BUG: Diagnostic contains:",
+ " Double.valueOf(in.getAttributeValue(null, null));",
+ " // BUG: Diagnostic contains:",
+ " Boolean.valueOf(in.getAttributeValue(null, null));",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testRead_Indirect() {
+ compilationHelper
+ .addSourceLines("Example.java",
+ "import org.xmlpull.v1.XmlPullParser;",
+ "public class Example {",
+ " public int direct(XmlPullParser in) throws Exception {",
+ " String arg = in.getAttributeValue(null, null);",
+ " if (arg != null) {",
+ " // BUG: Diagnostic contains:",
+ " return Integer.parseInt(arg);",
+ " } else {",
+ " return -1;",
+ " }",
+ " }",
+ " public int indirect(XmlPullParser in, String arg) throws Exception {",
+ " arg = in.getAttributeValue(null, null);",
+ " if (arg != null) {",
+ " // BUG: Diagnostic contains:",
+ " return Integer.parseInt(arg);",
+ " } else {",
+ " return -1;",
+ " }",
+ " }",
+ " public int tryCatch(XmlPullParser in) throws Exception {",
+ " String arg = in.getAttributeValue(null, null);",
+ " try {",
+ " // BUG: Diagnostic contains:",
+ " return Integer.parseInt(arg);",
+ " } catch (NumberFormatException e) {",
+ " return -1;",
+ " }",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testRead_Bytes() {
+ compilationHelper
+ .addSourceFile("/android/util/Base64.java")
+ .addSourceFile("/libcore/util/HexEncoding.java")
+ .addSourceFile("/com/android/internal/util/HexDump.java")
+ .addSourceLines("Example.java",
+ "import org.xmlpull.v1.XmlPullParser;",
+ "public class Example {",
+ " XmlPullParser in;",
+ " public void bytes() throws Exception {",
+ " android.util.Base64.decode(",
+ " // BUG: Diagnostic contains:",
+ " in.getAttributeValue(null, null), 0);",
+ " java.util.Base64.getDecoder().decode(",
+ " // BUG: Diagnostic contains:",
+ " in.getAttributeValue(null, null));",
+ " libcore.util.HexEncoding.decode(",
+ " // BUG: Diagnostic contains:",
+ " in.getAttributeValue(null, null));",
+ " com.android.internal.util.HexDump.hexStringToByteArray(",
+ " // BUG: Diagnostic contains:",
+ " in.getAttributeValue(null, null));",
+ " }",
+ "}")
+ .doTest();
+ }
+}
diff --git a/errorprone/tests/res/android/util/Base64.java b/errorprone/tests/res/android/util/Base64.java
new file mode 100644
index 000000000000..63d80befd750
--- /dev/null
+++ b/errorprone/tests/res/android/util/Base64.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 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 android.util;
+
+public class Base64 {
+ public static byte[] decode(String str, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ public static String encodeToString(byte[] input, int flags) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/errorprone/tests/res/android/util/Xml.java b/errorprone/tests/res/android/util/Xml.java
new file mode 100644
index 000000000000..0e44d00f4812
--- /dev/null
+++ b/errorprone/tests/res/android/util/Xml.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 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 android.util;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+public class Xml {
+ public static XmlPullParser newPullParser() {
+ throw new UnsupportedOperationException();
+ }
+
+ public static XmlSerializer newSerializer() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/errorprone/tests/res/com/android/internal/util/FastXmlSerializer.java b/errorprone/tests/res/com/android/internal/util/FastXmlSerializer.java
new file mode 100644
index 000000000000..83d14a9a3c64
--- /dev/null
+++ b/errorprone/tests/res/com/android/internal/util/FastXmlSerializer.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2020 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.internal.util;
+
+public class FastXmlSerializer {
+}
diff --git a/errorprone/tests/res/com/android/internal/util/HexDump.java b/errorprone/tests/res/com/android/internal/util/HexDump.java
new file mode 100644
index 000000000000..55d3e50cc7bb
--- /dev/null
+++ b/errorprone/tests/res/com/android/internal/util/HexDump.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 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.internal.util;
+
+public class HexDump {
+ public static String toHexString(byte[] array) {
+ throw new UnsupportedOperationException();
+ }
+
+ public static byte[] hexStringToByteArray(String hexString) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/errorprone/tests/res/libcore/util/HexEncoding.java b/errorprone/tests/res/libcore/util/HexEncoding.java
new file mode 100644
index 000000000000..34bbbaca4c1d
--- /dev/null
+++ b/errorprone/tests/res/libcore/util/HexEncoding.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 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 libcore.util;
+
+public class HexEncoding {
+ public static String encodeToString(byte[] data) {
+ throw new UnsupportedOperationException();
+ }
+
+ public static byte[] decode(String encoded) {
+ throw new UnsupportedOperationException();
+ }
+}