diff options
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(); + } +} |