Merge "Add checker for inefficient Parcel usage."
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceChecker.java
new file mode 100644
index 0000000..d524316
--- /dev/null
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceChecker.java
@@ -0,0 +1,123 @@
+/*
+ * 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.allOf;
+import static com.google.errorprone.matchers.Matchers.enclosingClass;
+import static com.google.errorprone.matchers.Matchers.enclosingMethod;
+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.methodIsNamed;
+
+import com.google.auto.service.AutoService;
+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.matchers.Description;
+import com.google.errorprone.matchers.Matcher;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.Tree;
+
+/**
+ * Parcelable data can be transported in many ways (some of which can be very
+ * inefficient) so this checker guides developers towards using high-performance
+ * best-practices.
+ */
+@AutoService(BugChecker.class)
+@BugPattern(
+    name = "AndroidFrameworkParcelablePerformance",
+    summary = "Verifies Parcelable performance best-practices",
+    severity = WARNING)
+public final class ParcelablePerformanceChecker extends BugChecker
+        implements MethodInvocationTreeMatcher {
+    private static final Matcher<Tree> INSIDE_WRITE_TO_PARCEL = allOf(
+            enclosingClass(isSubtypeOf("android.os.Parcelable")),
+            enclosingMethod(methodIsNamed("writeToParcel")));
+
+    private static final Matcher<ExpressionTree> WRITE_STRING = methodInvocation(
+            instanceMethod().onExactClass("android.os.Parcel").named("writeString"));
+    private static final Matcher<ExpressionTree> WRITE_STRING_ARRAY = methodInvocation(
+            instanceMethod().onExactClass("android.os.Parcel").named("writeStringArray"));
+
+    private static final Matcher<ExpressionTree> WRITE_VALUE = methodInvocation(
+            instanceMethod().onExactClass("android.os.Parcel").named("writeValue"));
+    private static final Matcher<ExpressionTree> WRITE_PARCELABLE = methodInvocation(
+            instanceMethod().onExactClass("android.os.Parcel").named("writeParcelable"));
+
+    private static final Matcher<ExpressionTree> WRITE_LIST = methodInvocation(
+            instanceMethod().onExactClass("android.os.Parcel").named("writeList"));
+    private static final Matcher<ExpressionTree> WRITE_PARCELABLE_LIST = methodInvocation(
+            instanceMethod().onExactClass("android.os.Parcel").named("writeParcelableList"));
+    private static final Matcher<ExpressionTree> WRITE_PARCELABLE_ARRAY = methodInvocation(
+            instanceMethod().onExactClass("android.os.Parcel").named("writeParcelableArray"));
+
+    @Override
+    public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
+        if (INSIDE_WRITE_TO_PARCEL.matches(tree, state)) {
+            if (WRITE_STRING.matches(tree, state)) {
+                return buildDescription(tree)
+                        .setMessage("Recommended to use 'writeString8()' to improve "
+                                + "efficiency; sending as UTF-8 can double throughput")
+                        .build();
+            }
+            if (WRITE_STRING_ARRAY.matches(tree, state)) {
+                return buildDescription(tree)
+                        .setMessage("Recommended to use 'writeString8Array()' to improve "
+                                + "efficiency; sending as UTF-8 can double throughput")
+                        .build();
+            }
+
+            if (WRITE_VALUE.matches(tree, state)) {
+                return buildDescription(tree)
+                        .setMessage("Recommended to use strongly-typed methods to improve "
+                                + "efficiency; saves 4 bytes for type and overhead of "
+                                + "Parcelable class name")
+                        .build();
+            }
+            if (WRITE_PARCELABLE.matches(tree, state)) {
+                return buildDescription(tree)
+                        .setMessage("Recommended to use 'item.writeToParcel()' to improve "
+                                + "efficiency; saves overhead of Parcelable class name")
+                        .build();
+            }
+
+            if (WRITE_LIST.matches(tree, state)) {
+                return buildDescription(tree)
+                        .setMessage("Recommended to use 'writeTypedList()' to improve "
+                                + "efficiency; saves overhead of repeated Parcelable class name")
+                        .build();
+            }
+            if (WRITE_PARCELABLE_LIST.matches(tree, state)) {
+                return buildDescription(tree)
+                        .setMessage("Recommended to use 'writeTypedList()' to improve "
+                                + "efficiency; saves overhead of repeated Parcelable class name")
+                        .build();
+            }
+            if (WRITE_PARCELABLE_ARRAY.matches(tree, state)) {
+                return buildDescription(tree)
+                        .setMessage("Recommended to use 'writeTypedArray()' to improve "
+                                + "efficiency; saves overhead of repeated Parcelable class name")
+                        .build();
+            }
+        }
+        return Description.NO_MATCH;
+    }
+}
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceCheckerTest.java
new file mode 100644
index 0000000..75c76e3
--- /dev/null
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceCheckerTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ParcelablePerformanceCheckerTest {
+    private CompilationTestHelper compilationHelper;
+
+    @Before
+    public void setUp() {
+        compilationHelper = CompilationTestHelper.newInstance(
+                ParcelablePerformanceChecker.class, getClass());
+    }
+
+    @Test
+    public void testString() {
+        compilationHelper
+                .addSourceFile("/android/os/Parcel.java")
+                .addSourceFile("/android/os/Parcelable.java")
+                .addSourceLines("FooInfo.java",
+                        "import android.os.Parcel;",
+                        "import android.os.Parcelable;",
+                        "public class FooInfo implements Parcelable {",
+                        "  public void writeToParcel(Parcel dest, int flags) {",
+                        "    // BUG: Diagnostic contains:",
+                        "    dest.writeString(toString());",
+                        "    dest.writeString8(toString());",
+                        "    // BUG: Diagnostic contains:",
+                        "    dest.writeStringArray(new String[0]);",
+                        "    dest.writeString8Array(new String[0]);",
+                        "  }",
+                        "}")
+                .doTest();
+    }
+
+    @Test
+    public void testSingle() {
+        compilationHelper
+                .addSourceFile("/android/os/Parcel.java")
+                .addSourceFile("/android/os/Parcelable.java")
+                .addSourceLines("FooInfo.java",
+                        "import android.os.Parcel;",
+                        "import android.os.Parcelable;",
+                        "public class FooInfo implements Parcelable {",
+                        "  public void writeToParcel(Parcel dest, int flags) {",
+                        "    // BUG: Diagnostic contains:",
+                        "    dest.writeValue(this);",
+                        "    this.writeToParcel(dest, flags);",
+                        "    // BUG: Diagnostic contains:",
+                        "    dest.writeParcelable(this, flags);",
+                        "    this.writeToParcel(dest, flags);",
+                        "  }",
+                        "}")
+                .doTest();
+    }
+
+    @Test
+    public void testList() {
+        compilationHelper
+                .addSourceFile("/android/os/Parcel.java")
+                .addSourceFile("/android/os/Parcelable.java")
+                .addSourceLines("FooInfo.java",
+                        "import android.os.Parcel;",
+                        "import android.os.Parcelable;",
+                        "import java.util.List;",
+                        "import java.util.ArrayList;",
+                        "public class FooInfo implements Parcelable {",
+                        "  public void writeToParcel(Parcel dest, int flags) {",
+                        "    List<Parcelable> list = new ArrayList<Parcelable>();",
+                        "    Parcelable[] array = new Parcelable[0];",
+                        "    // BUG: Diagnostic contains:",
+                        "    dest.writeList(list);",
+                        "    dest.writeTypedList(list, flags);",
+                        "    // BUG: Diagnostic contains:",
+                        "    dest.writeParcelableList(list, flags);",
+                        "    dest.writeTypedList(list, flags);",
+                        "    // BUG: Diagnostic contains:",
+                        "    dest.writeParcelableArray(array, flags);",
+                        "    dest.writeTypedArray(array, flags);",
+                        "  }",
+                        "}")
+                .doTest();
+    }
+}
diff --git a/errorprone/tests/res/android/os/Parcel.java b/errorprone/tests/res/android/os/Parcel.java
new file mode 100644
index 0000000..bafa236
--- /dev/null
+++ b/errorprone/tests/res/android/os/Parcel.java
@@ -0,0 +1,57 @@
+/*
+ * 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.os;
+
+import java.util.List;
+
+public class Parcel {
+    public void writeString(String val) {
+        throw new UnsupportedOperationException();
+    }
+    public void writeString8(String val) {
+        throw new UnsupportedOperationException();
+    }
+    public final void writeStringArray(String[] val) {
+        throw new UnsupportedOperationException();
+    }
+    public final void writeString8Array(String[] val) {
+        throw new UnsupportedOperationException();
+    }
+
+    public final void writeValue(Object v) {
+        throw new UnsupportedOperationException();
+    }
+    public final void writeParcelable(Parcelable p, int flags) {
+        throw new UnsupportedOperationException();
+    }
+
+    public final void writeList(List val) {
+        throw new UnsupportedOperationException();
+    }
+    public final <T extends Parcelable> void writeParcelableList(List<T> val, int flags) {
+        throw new UnsupportedOperationException();
+    }
+    public <T extends Parcelable> void writeTypedList(List<T> val, int flags) {
+        throw new UnsupportedOperationException();
+    }
+    public final <T extends Parcelable> void writeParcelableArray(T[] value, int flags) {
+        throw new UnsupportedOperationException();
+    }
+    public final <T extends Parcelable> void writeTypedArray(T[] val, int flags) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/errorprone/tests/res/android/os/Parcelable.java b/errorprone/tests/res/android/os/Parcelable.java
new file mode 100644
index 0000000..217690d
--- /dev/null
+++ b/errorprone/tests/res/android/os/Parcelable.java
@@ -0,0 +1,21 @@
+/*
+ * 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.os;
+
+public interface Parcelable {
+    public void writeToParcel(Parcel dest, int flags);
+}