diff options
author | Jeff Sharkey <jsharkey@android.com> | 2020-10-27 22:58:30 -0600 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2020-11-13 14:48:28 -0700 |
commit | ca3ea93171a4a46d42298a75f4139e8e660d303f (patch) | |
tree | c9d6e45fc5d21d69196871731f815b54391434be | |
parent | 82fe62465b1b785d70eae16b028f711455caa62f (diff) |
Progress towards efficient XML serialization.
Related changes are introducing new TypedXmlSerializer and
TypedXmlPullParser interfaces which offer efficient access to
primitive attributes, and this Error Prone detector helps identify
code that should shift to using those new interfaces.
Bug: 171832118
Test: atest error_prone_android_framework_test
Change-Id: Ic3ca6b96d2b056e6178e407af886bb925a3471c8
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(); + } +} |