Version 2.1.35

Cherry-pick: Add warnings for -assumenosideeffects
CL: https://r8-review.googlesource.com/c/r8/+/51863

Bug: 152492625
Bug: 158018192
Change-Id: I3c0a838b5384cc742a57987a4496f715711d8e1f
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 84acd31..bc82245 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "2.1.34";
+  public static final String LABEL = "2.1.35";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 1a318c0..60101db 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -147,6 +147,10 @@
   public final DexString boxedNumberDescriptor = createString("Ljava/lang/Number;");
   public final DexString boxedVoidDescriptor = createString("Ljava/lang/Void;");
 
+  public final DexString waitMethodName = createString("wait");
+  public final DexString notifyMethodName = createString("notify");
+  public final DexString notifyAllMethodName = createString("notifyAll");
+
   public final DexString unboxBooleanMethodName = createString("booleanValue");
   public final DexString unboxByteMethodName = createString("byteValue");
   public final DexString unboxCharMethodName = createString("charValue");
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index ffb940a..73171b4 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -40,6 +40,7 @@
 import com.android.tools.r8.utils.Consumer3;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.OriginWithPosition;
 import com.android.tools.r8.utils.PredicateSet;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -113,6 +114,9 @@
   private final DexStringCache dexStringCache = new DexStringCache();
   private final Set<ProguardIfRule> ifRules = Sets.newIdentityHashSet();
 
+  private final Map<OriginWithPosition, List<DexMethod>> assumeNoSideEffectsWarnings =
+      new HashMap<>();
+
   public RootSetBuilder(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       SubtypingInfo subtypingInfo,
@@ -304,6 +308,7 @@
     } finally {
       application.timing.end();
     }
+    generateAssumeNoSideEffectsWarnings();
     if (!noSideEffects.isEmpty() || !assumedValues.isEmpty()) {
       BottomUpClassHierarchyTraversal.forAllClasses(appView, subtypingInfo)
           .visit(appView.appInfo().classes(), this::propagateAssumeRules);
@@ -1117,6 +1122,7 @@
       mayHaveSideEffects.put(item.toReference(), rule);
       context.markAsUsed();
     } else if (context instanceof ProguardAssumeNoSideEffectRule) {
+      checkAssumeNoSideEffectsWarnings(item, (ProguardAssumeNoSideEffectRule) context, rule);
       noSideEffects.put(item.toReference(), rule);
       context.markAsUsed();
     } else if (context instanceof ProguardWhyAreYouKeepingRule) {
@@ -1376,6 +1382,66 @@
     }
   }
 
+  private void checkAssumeNoSideEffectsWarnings(
+      DexDefinition item, ProguardAssumeNoSideEffectRule context, ProguardMemberRule rule) {
+    if (rule.getRuleType() == ProguardMemberType.METHOD && rule.isSpecific()) {
+      return;
+    }
+    if (item.isDexEncodedMethod()) {
+      DexEncodedMethod method = item.asDexEncodedMethod();
+      if (method.holder() == options.itemFactory.objectType) {
+        OriginWithPosition key = new OriginWithPosition(context.getOrigin(), context.getPosition());
+        assumeNoSideEffectsWarnings.computeIfAbsent(key, k -> new ArrayList<>()).add(method.method);
+      }
+    }
+  }
+
+  private boolean isWaitOrNotifyMethod(DexMethod method) {
+    return method.name == options.itemFactory.waitMethodName
+        || method.name == options.itemFactory.notifyMethodName
+        || method.name == options.itemFactory.notifyAllMethodName;
+  }
+
+  private void generateAssumeNoSideEffectsWarnings() {
+    ProguardClassFilter dontWarnPatterns =
+        options.getProguardConfiguration() != null
+            ? options.getProguardConfiguration().getDontWarnPatterns()
+            : ProguardClassFilter.empty();
+    if (dontWarnPatterns.matches(options.itemFactory.objectType)) {
+      return;
+    }
+
+    assumeNoSideEffectsWarnings.forEach(
+        (originWithPosition, methods) -> {
+          boolean waitOrNotifyMethods = methods.stream().anyMatch(this::isWaitOrNotifyMethod);
+          StringBuilder message = new StringBuilder();
+          message.append(
+              "The -assumenosideeffects rule matches methods on `java.lang.Object` with wildcards");
+          if (waitOrNotifyMethods) {
+            message.append(" including the method(s) ");
+            for (int i = 0; i < methods.size(); i++) {
+              if (i > 0) {
+                message.append(i < methods.size() - 1 ? ", " : " and ");
+              }
+              message.append("`");
+              message.append(methods.get(i).toSourceStringWithoutHolder());
+              message.append("`");
+            }
+            message.append(". ");
+            message.append("This will most likely cause problems. ");
+          } else {
+            message.append(". ");
+            message.append("This is most likely not intended. ");
+          }
+          message.append("Consider specifying the methods more precisely.");
+          options.reporter.warning(
+              new StringDiagnostic(
+                  message.toString(),
+                  originWithPosition.getOrigin(),
+                  originWithPosition.getPosition()));
+        });
+  }
+
   public static class RootSet extends RootSetBase {
 
     public final ImmutableList<DexReference> reasonAsked;
diff --git a/src/main/java/com/android/tools/r8/utils/OriginWithPosition.java b/src/main/java/com/android/tools/r8/utils/OriginWithPosition.java
new file mode 100644
index 0000000..8459d19
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/OriginWithPosition.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.Objects;
+
+public class OriginWithPosition {
+  private final Origin origin;
+  private final Position position;
+
+  public OriginWithPosition(Origin origin, Position position) {
+    this.origin = origin;
+    this.position = position;
+  }
+
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  public Position getPosition() {
+    return position;
+  }
+
+  @Override
+  public int hashCode() {
+    return origin.hashCode() * 13 + position.hashCode();
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (other instanceof OriginWithPosition) {
+      return Objects.equals(((OriginWithPosition) other).origin, origin)
+          && Objects.equals(((OriginWithPosition) other).position, position);
+    }
+    return false;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
index 5f6b792..70518d7 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
@@ -64,7 +65,21 @@
 
       @Override
       protected void explain(Description description) {
-        description.appendText("orgin ").appendText(origin.toString());
+        description.appendText("origin ").appendText(origin.toString());
+      }
+    };
+  }
+
+  public static Matcher<Diagnostic> diagnosticPosition(Position position) {
+    return new DiagnosticsMatcher() {
+      @Override
+      protected boolean eval(Diagnostic diagnostic) {
+        return diagnostic.getPosition().equals(position);
+      }
+
+      @Override
+      protected void explain(Description description) {
+        description.appendText("position ").appendText(position.getDescription());
       }
     };
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java
index ebf633c..71614d2 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java
@@ -41,27 +41,32 @@
         case RULE_THAT_REFERS_LIB_BASE:
           return StringUtils.lines(
               "-assumenosideeffects class " + LibraryBase.class.getTypeName() + " {",
-              "  *;",
+              "  throwing(...);",
+              "  debug(...);",
               "}");
         case RULE_THAT_REFERS_PRG_BASE:
           return StringUtils.lines(
               "-assumenosideeffects class " + ProgramBase.class.getTypeName() + " {",
-              "  *;",
+              "  throwing(...);",
+              "  debug(...);",
               "}");
         case RULE_THAT_REFERS_PRG_SUB:
           return StringUtils.lines(
               "-assumenosideeffects class " + ProgramSub.class.getTypeName() + " {",
-              "  *;",
+              "  throwing(...);",
+              "  debug(...);",
               "}");
         case RULE_WITH_EXTENDS_LIB_BASE:
           return StringUtils.lines(
               "-assumenosideeffects class * extends " + LibraryBase.class.getTypeName() + " {",
-              "  *;",
+              "  throwing(...);",
+              "  debug(...);",
               "}");
         case RULE_WITH_EXTENDS_PRG_BASE:
           return StringUtils.lines(
               "-assumenosideeffects class * extends " + ProgramBase.class.getTypeName() + " {",
-              "  *;",
+              "  throwing(...);",
+              "  debug(...);",
               "}");
       }
       throw new Unreachable();
@@ -112,7 +117,9 @@
 
   @Parameterized.Parameters(name = "{0} {1}")
   public static Collection<Object[]> data() {
-    return buildParameters(getTestParameters().withAllRuntimes().build(), TestConfig.values());
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().withAllApiLevelsAlsoForCf().build(),
+        TestConfig.values());
   }
 
   private final TestParameters parameters;
@@ -145,7 +152,7 @@
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addLibraryFiles(libJarPath)
-        .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+        .addLibraryFiles(ToolHelper.getFirstSupportedAndroidJar(parameters.getApiLevel()))
         .addProgramClasses(ProgramBase.class, ProgramSub.class, MAIN)
         .addKeepMainRule(MAIN)
         .addKeepRules(config.getKeepRule())
@@ -153,7 +160,7 @@
         .enableMergeAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .addRunClasspathFiles(parameters.isDexRuntime() ? libDexPath : libJarPath)
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
index fe72a94..560f6ea 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
@@ -4,15 +4,28 @@
 
 package com.android.tools.r8.shaking.assumenosideeffects;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticPosition;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
 
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.TextPosition;
+import com.android.tools.r8.position.TextRange;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.google.common.collect.ImmutableList;
+import org.hamcrest.Matcher;
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Test;
@@ -47,13 +60,118 @@
                     }));
   }
 
+  private Matcher<Diagnostic> matchAssumeNoSideEffectsWarningMessage() {
+    return diagnosticMessage(
+        containsString(
+            "The -assumenosideeffects rule matches methods on `java.lang.Object` with"
+                + " wildcards"));
+  }
+
+  private Matcher<Diagnostic> matchWarningMessageForAllProblematicMethods() {
+    return diagnosticMessage(
+        allOf(
+            containsString("void notify()"),
+            containsString("void notifyAll()"),
+            containsString("void wait()"),
+            containsString("void wait(long)"),
+            containsString("void wait(long, int)")));
+  }
+
+  private Matcher<Diagnostic> matchWarningMessageForWaitMethods() {
+    return diagnosticMessage(
+        allOf(
+            containsString("void wait()"),
+            containsString("void wait(long)"),
+            containsString("void wait(long, int)")));
+  }
+
+  private TextRange textRangeForString(String s) {
+    return new TextRange(
+        new TextPosition(0, 1, 1), new TextPosition(s.length(), 1, s.length() + 1));
+  }
+
   @Test
-  public void testR8() throws Exception {
+  public void testR8AllMatch() throws Exception {
     testForR8(parameters.getBackend())
-        .addInnerClasses(B152492625.class)
+        .addProgramClasses(TestClass.class, B.class)
         .addKeepMainRule(TestClass.class)
         .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }")
         .setMinApi(parameters.getApiLevel())
+        .allowDiagnosticWarningMessages()
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertOnlyWarnings();
+              diagnostics.assertWarningsMatch(matchAssumeNoSideEffectsWarningMessage());
+              diagnostics.assertWarningsMatch(matchWarningMessageForAllProblematicMethods());
+            })
+        .inspect(this::noCallToWait)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world");
+  }
+
+  @Test
+  public void testR8AllMatchMultipleRules() throws Exception {
+    class MyOrigin extends Origin {
+      private final String part;
+
+      public MyOrigin(String part) {
+        super(Origin.root());
+        this.part = part;
+      }
+
+      @Override
+      public String part() {
+        return part;
+      }
+    }
+
+    Origin starRuleOrigin = new MyOrigin("star rule");
+    Origin methodsRuleOrigin = new MyOrigin("methods rule");
+
+    String starRule = "-assumenosideeffects class " + B.class.getTypeName() + " { *; }";
+    String methodsRule = "-assumenosideeffects class " + B.class.getTypeName() + " { <methods>; }";
+
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, B.class)
+        .addKeepMainRule(TestClass.class)
+        .apply(
+            b ->
+                b.getBuilder().addProguardConfiguration(ImmutableList.of(starRule), starRuleOrigin))
+        .apply(
+            b ->
+                b.getBuilder()
+                    .addProguardConfiguration(ImmutableList.of(methodsRule), methodsRuleOrigin))
+        .setMinApi(parameters.getApiLevel())
+        .allowDiagnosticWarningMessages()
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertOnlyWarnings();
+              diagnostics.assertWarningsMatch(
+                  ImmutableList.of(
+                      allOf(
+                          matchAssumeNoSideEffectsWarningMessage(),
+                          matchWarningMessageForAllProblematicMethods(),
+                          diagnosticOrigin(starRuleOrigin),
+                          diagnosticPosition(textRangeForString(starRule))),
+                      allOf(
+                          matchAssumeNoSideEffectsWarningMessage(),
+                          matchWarningMessageForAllProblematicMethods(),
+                          diagnosticOrigin(methodsRuleOrigin),
+                          diagnosticPosition(textRangeForString(methodsRule)))));
+            })
+        .inspect(this::noCallToWait)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world");
+  }
+
+  @Test
+  public void testR8AllMatchDontWarn() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, B.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }")
+        .addKeepRules("-dontwarn java.lang.Object")
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::noCallToWait)
         .run(parameters.getRuntime(), TestClass.class)
@@ -61,11 +179,85 @@
   }
 
   @Test
+  public void testR8AllMethodsMatch() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, B.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { <methods>; }")
+        .setMinApi(parameters.getApiLevel())
+        .allowDiagnosticWarningMessages()
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertOnlyWarnings();
+              diagnostics.assertWarningsMatch(matchAssumeNoSideEffectsWarningMessage());
+              diagnostics.assertWarningsMatch(matchWarningMessageForAllProblematicMethods());
+            })
+        .inspect(this::noCallToWait)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world");
+  }
+
+  @Test
+  public void testR8WaitMethodMatch() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, B.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *** w*(...); }")
+        .setMinApi(parameters.getApiLevel())
+        .allowDiagnosticWarningMessages()
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertOnlyWarnings();
+              diagnostics.assertWarningsMatch(matchAssumeNoSideEffectsWarningMessage());
+              diagnostics.assertWarningsMatch(matchWarningMessageForWaitMethods());
+            })
+        .inspect(this::noCallToWait)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world");
+  }
+
+  @Test
+  public void testR8WaitSpecificMethodMatch() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, B.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-assumenosideeffects class java.lang.Object { void wait(); }")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::noCallToWait)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world");
+  }
+
+  @Test
+  public void testR8AssumeNoSideEffectsNotConditional() throws Exception {
+    try {
+      testForR8(parameters.getBackend())
+          .addProgramClasses(TestClass.class, B.class)
+          .addKeepMainRule(TestClass.class)
+          .addKeepRules(
+              "-if class " + TestClass.class.getTypeName(),
+              " -assumenosideeffects class " + B.class.getTypeName() + " { *; }")
+          .setMinApi(parameters.getApiLevel())
+          .compileWithExpectedDiagnostics(
+              diagnostics -> {
+                diagnostics.assertOnlyErrors();
+                diagnostics.assertErrorsMatch(
+                    diagnosticMessage(
+                        containsString("Expecting '-keep' option after '-if' option")));
+              });
+      fail("Expected failed compilation");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+  }
+
+  @Test
   public void testProguardNotRemovingWait() throws Exception {
     Assume.assumeTrue(parameters.isCfRuntime());
 
     testForProguard()
-        .addInnerClasses(B152492625.class)
+        .addProgramClasses(TestClass.class, B.class)
         .addKeepMainRule(TestClass.class)
         .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }")
         .addKeepRules("-dontwarn " + B152492625.class.getTypeName())
@@ -80,7 +272,7 @@
     Assume.assumeTrue(parameters.isCfRuntime());
 
     testForProguard()
-        .addInnerClasses(B152492625.class)
+        .addProgramClasses(TestClass.class, B.class)
         .addKeepMainRule(TestClass.class)
         .addKeepRules("-assumenosideeffects class java.lang.Object { void wait(); }")
         .addKeepRules("-dontwarn " + B152492625.class.getTypeName())