Implement -dontoptimize: disabling related flags.

Different optimizations have their own fine-grained flags with different
default values, and thus it is non-trivial to replace them with a single
flag. Rather, when converting proguard config to internal options,
-dontoptimize will be propagated by flipping optimization-related flags:
  * Simple class merging
  * Switch map collecting
  * Inling
  * Outlining

Bug: 36799686
Change-Id: Id634b5513a4d5297503ba3ba9e1b77a1c344bd68
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 7bae1a1..4076a5a 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -440,6 +440,14 @@
     assert !internal.debug;
     internal.debug = getMode() == CompilationMode.DEBUG;
     internal.minApiLevel = getMinApiLevel();
+    // -dontoptimize disables optimizations by flipping related flags.
+    if (!proguardConfiguration.isOptimizing()) {
+      internal.skipDebugLineNumberOpt = true;
+      internal.skipClassMerging = true;
+      internal.inlineAccessors = false;
+      internal.removeSwitchMaps = false;
+      internal.outline.enabled = false;
+    }
     assert !internal.skipMinification;
     internal.skipMinification = !useMinification() || !proguardConfiguration.isObfuscating();
     assert internal.useTreeShaking;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 7dca5bd..716db5e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -133,9 +133,9 @@
   }
 
   /**
-   * Create an IR converter for processing methods without full program optimization enabled.
+   * Create an IR converter for processing methods with full program optimization disabled.
    *
-   * The argument <code>enableDesugaring</code> if desugaing is enabled.
+   * The argument <code>enableDesugaring</code> if desugaring is enabled.
    */
   public IRConverter(
       DexApplication application,
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 87a36e6..87b425b 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -24,7 +24,7 @@
     private String packagePrefix = "";
     private boolean allowAccessModification = false;
     private boolean ignoreWarnings = false;
-    private boolean optimize = true;
+    private boolean optimizing = true;
     private boolean obfuscating = true;
     private boolean shrinking = true;
     private boolean printUsage = false;
@@ -79,8 +79,8 @@
       this.ignoreWarnings = ignoreWarnings;
     }
 
-    public void setOptimize(boolean optimize) {
-      this.optimize = optimize;
+    public void setOptimizing(boolean optimizing) {
+      this.optimizing = optimizing;
     }
 
     public void setObfuscating(boolean obfuscate) {
@@ -172,6 +172,7 @@
           packagePrefix,
           allowAccessModification,
           ignoreWarnings,
+          optimizing,
           obfuscating,
           shrinking,
           printUsage,
@@ -200,6 +201,7 @@
   private final String packagePrefix;
   private final boolean allowAccessModification;
   private final boolean ignoreWarnings;
+  private final boolean optimizing;
   private final boolean obfuscating;
   private final boolean shrinking;
   private final boolean printUsage;
@@ -227,6 +229,7 @@
       String packagePrefix,
       boolean allowAccessModification,
       boolean ignoreWarnings,
+      boolean optimizing,
       boolean obfuscating,
       boolean shrinking,
       boolean printUsage,
@@ -252,6 +255,7 @@
     this.packagePrefix = packagePrefix;
     this.allowAccessModification = allowAccessModification;
     this.ignoreWarnings = ignoreWarnings;
+    this.optimizing = optimizing;
     this.obfuscating = obfuscating;
     this.shrinking = shrinking;
     this.printUsage = printUsage;
@@ -327,6 +331,10 @@
     return ignoreWarnings;
   }
 
+  public boolean isOptimizing() {
+    return optimizing;
+  }
+
   public boolean isObfuscating() {
     return obfuscating;
   }
@@ -393,6 +401,7 @@
           ""                    /* package prefix */,
           false                 /* allowAccessModification */,
           false                 /* ignoreWarnings */,
+          true                  /* optimizing */,
           false                 /* obfuscating */,
           false                 /* shrinking */,
           false                 /* printUsage */,
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 9a90c68..bcbfc8b 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -168,8 +168,7 @@
         ProguardWhyAreYouKeepingRule rule = parseWhyAreYouKeepingRule();
         configurationBuilder.addRule(rule);
       } else if (acceptString("dontoptimize")) {
-        configurationBuilder.setOptimize(false);
-        warnIgnoringOptions("dontoptimize");
+        configurationBuilder.setOptimizing(false);
       } else if (acceptString("optimizationpasses")) {
         skipWhitespace();
         Integer expectedOptimizationPasses = acceptInteger();
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index a2a0446..36e19bf 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -45,9 +45,13 @@
   public final int NOT_SPECIFIED = -1;
 
   public boolean printTimes = false;
-  // Skipping optimizations.
+
+  // Optimization-related flags. These should conform to -dontoptimize.
   public boolean skipDebugLineNumberOpt = false;
   public boolean skipClassMerging = true;
+  public boolean inlineAccessors = true;
+  public boolean removeSwitchMaps = true;
+  public final OutlineOptions outline = new OutlineOptions();
 
   // Number of threads to use while processing the dex files.
   public int numberOfThreads = NOT_SPECIFIED;
@@ -98,10 +102,7 @@
   public Path printMainDexListFile;
   public boolean ignoreMissingClasses = false;
   public boolean skipMinification = false;
-  public boolean inlineAccessors = true;
-  public boolean removeSwitchMaps = true;
   public boolean disableAssertions = true;
-  public final OutlineOptions outline = new OutlineOptions();
   public boolean debugKeepRules = false;
   public final AttributeRemovalOptions attributeRemoval = new AttributeRemovalOptions();
   public boolean allowParameterName = false;
diff --git a/src/test/examples/classmerging/keep-rules-dontoptimize.txt b/src/test/examples/classmerging/keep-rules-dontoptimize.txt
new file mode 100644
index 0000000..b2c1ff4
--- /dev/null
+++ b/src/test/examples/classmerging/keep-rules-dontoptimize.txt
@@ -0,0 +1,11 @@
+# Copyright (c) 2017, 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class classmerging.Test {
+  public static void main(...);
+}
+
+-dontoptimize
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 58b262b..f83adbd 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -7,6 +7,3 @@
 -keep public class classmerging.Test {
   public static void main(...);
 }
-
-# allow access modification to enable minification
--allowaccessmodification
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index 7bd18e0..a1692c8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -1,16 +1,21 @@
 package com.android.tools.r8.classmerging;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.List;
 import java.util.concurrent.ExecutionException;
-import org.junit.Assert;
-import org.junit.Before;
+import java.util.function.Consumer;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -21,54 +26,76 @@
       .resolve("classmerging.jar");
   private static final Path EXAMPLE_KEEP = Paths.get(ToolHelper.EXAMPLES_DIR)
       .resolve("classmerging").resolve("keep-rules.txt");
+  private static final Path DONT_OPTIMIZE = Paths.get(ToolHelper.EXAMPLES_DIR)
+      .resolve("classmerging").resolve("keep-rules-dontoptimize.txt");
 
   @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
-  @Before
-  public void runR8()
+  private void configure(InternalOptions options) {
+    options.skipClassMerging = false;
+  }
+
+  private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
       throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
-    // Disable access modification, as it otherwise is difficult to test visibility bridge methods.
     ToolHelper.runR8(
         R8Command.builder()
             .setOutputPath(Paths.get(temp.getRoot().getCanonicalPath()))
             .addProgramFiles(EXAMPLE_JAR)
-            .addProguardConfigurationFiles(EXAMPLE_KEEP)
+            .addProguardConfigurationFiles(proguardConfig)
             .setMinification(false)
-            .build(), o -> {
-          o.skipClassMerging = false;
-        });
+            .build(),
+        optionsConsumer);
     inspector = new DexInspector(
         Paths.get(temp.getRoot().getCanonicalPath()).resolve("classes.dex"));
   }
 
   private DexInspector inspector;
 
+  private final List<String> CAN_BE_MERGED = ImmutableList.of(
+      "classmerging.GenericInterface",
+      "classmerging.GenericAbstractClass",
+      "classmerging.Outer$SuperClass",
+      "classmerging.SuperClass"
+  );
+
   @Test
-  public void testClassesHaveBeenMerged() throws IOException, ExecutionException {
+  public void testClassesHaveBeenMerged()
+      throws IOException, ExecutionException, CompilationException, ProguardRuleParserException {
+    runR8(EXAMPLE_KEEP, this::configure);
     // GenericInterface should be merged into GenericInterfaceImpl.
-    Assert.assertFalse(inspector.clazz("classmerging.GenericInterface").isPresent());
-    Assert.assertTrue(inspector.clazz("classmerging.GenericInterfaceImpl").isPresent());
-    Assert.assertFalse(inspector.clazz("classmerging.GenericAbstractClass").isPresent());
-    Assert.assertTrue(inspector.clazz("classmerging.GenericInterfaceImpl").isPresent());
-    Assert.assertFalse(inspector.clazz("classmerging.Outer$SuperClass").isPresent());
-    Assert.assertTrue(inspector.clazz("classmerging.Outer$SubClass").isPresent());
-    Assert.assertFalse(inspector.clazz("classmerging.SuperClass").isPresent());
-    Assert.assertTrue(inspector.clazz("classmerging.SubClass").isPresent());
-  }
-
-
-  @Test
-  public void testConflictWasDetected() throws IOException, ExecutionException {
-    Assert.assertTrue(inspector.clazz("classmerging.ConflictingInterface").isPresent());
-    Assert.assertTrue(inspector.clazz("classmerging.ConflictingInterfaceImpl").isPresent());
+    for (String candidate : CAN_BE_MERGED) {
+      assertFalse(inspector.clazz(candidate).isPresent());
+    }
+    assertTrue(inspector.clazz("classmerging.GenericInterfaceImpl").isPresent());
+    assertTrue(inspector.clazz("classmerging.GenericInterfaceImpl").isPresent());
+    assertTrue(inspector.clazz("classmerging.Outer$SubClass").isPresent());
+    assertTrue(inspector.clazz("classmerging.SubClass").isPresent());
   }
 
   @Test
-  public void testSuperCallWasDetected() throws IOException, ExecutionException {
-    Assert.assertTrue(inspector.clazz("classmerging.SuperClassWithReferencedMethod").isPresent());
-    Assert
-        .assertTrue(inspector.clazz("classmerging.SubClassThatReferencesSuperMethod").isPresent());
+  public void testClassesShouldNotMerged()
+      throws IOException, ExecutionException, CompilationException, ProguardRuleParserException {
+    runR8(DONT_OPTIMIZE, null);
+    for (String candidate : CAN_BE_MERGED) {
+      assertTrue(inspector.clazz(candidate).isPresent());
+    }
+  }
+
+  @Test
+  public void testConflictWasDetected()
+      throws IOException, ExecutionException, CompilationException, ProguardRuleParserException {
+    runR8(EXAMPLE_KEEP, this::configure);
+    assertTrue(inspector.clazz("classmerging.ConflictingInterface").isPresent());
+    assertTrue(inspector.clazz("classmerging.ConflictingInterfaceImpl").isPresent());
+  }
+
+  @Test
+  public void testSuperCallWasDetected()
+      throws IOException, ExecutionException, CompilationException, ProguardRuleParserException {
+    runR8(EXAMPLE_KEEP, this::configure);
+    assertTrue(inspector.clazz("classmerging.SuperClassWithReferencedMethod").isPresent());
+    assertTrue(inspector.clazz("classmerging.SubClassThatReferencesSuperMethod").isPresent());
   }
 
 }