Merge "Account for minification of names in <clinit> default values."
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 494a62b..0bad20c 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -407,7 +407,7 @@
       Set<DexCallSite> desugaredCallSites;
       CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
       try {
-        IRConverter converter = new IRConverter(appView, options, timing, printer);
+        IRConverter converter = new IRConverter(appView, options, timing, printer, rootSet);
         application = converter.optimize(application, executorService);
         desugaredCallSites = converter.getDesugaredCallSites();
       } finally {
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 18e67bb..1541f80 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
@@ -68,6 +68,7 @@
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.IdentifierNameStringMarker;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -106,6 +107,7 @@
 
   public final AppInfo appInfo;
   public final AppView<? extends AppInfoWithSubtyping> appView;
+  public final RootSet rootSet;
 
   private final Timing timing;
   private final Outliner outliner;
@@ -150,13 +152,15 @@
       InternalOptions options,
       Timing timing,
       CfgPrinter printer,
-      AppView<? extends AppInfoWithSubtyping> appView) {
+      AppView<? extends AppInfoWithSubtyping> appView,
+      RootSet rootSet) {
     assert appInfo != null;
     assert options != null;
     assert options.programConsumer != null;
     this.timing = timing != null ? timing : new Timing("internal");
     this.appInfo = appInfo;
     this.appView = appView;
+    this.rootSet = rootSet;
     this.options = options;
     this.printer = printer;
     this.codeRewriter = new CodeRewriter(this, libraryMethodsReturningReceiver(), options);
@@ -180,6 +184,7 @@
       assert appInfo.hasLiveness();
       AppInfoWithLiveness appInfoWithLiveness = appInfo.withLiveness();
       AppView<? extends AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+      assert rootSet != null;
       this.nonNullTracker =
           new NonNullTracker(appInfo, libraryMethodsReturningNonNull(appInfo.dexItemFactory));
       this.inliner = new Inliner(appViewWithLiveness, this, options);
@@ -241,14 +246,14 @@
    * Create an IR converter for processing methods with full program optimization disabled.
    */
   public IRConverter(AppInfo appInfo, InternalOptions options) {
-    this(appInfo, options, null, null, null);
+    this(appInfo, options, null, null, null, null);
   }
 
   /**
    * Create an IR converter for processing methods with full program optimization disabled.
    */
   public IRConverter(AppInfo appInfo, InternalOptions options, Timing timing, CfgPrinter printer) {
-    this(appInfo, options, timing, printer, null);
+    this(appInfo, options, timing, printer, null, null);
   }
 
   /**
@@ -258,8 +263,9 @@
       AppView<AppInfoWithSubtyping> appView,
       InternalOptions options,
       Timing timing,
-      CfgPrinter printer) {
-    this(appView.appInfo(), options, timing, printer, appView);
+      CfgPrinter printer,
+      RootSet rootSet) {
+    this(appView.appInfo(), options, timing, printer, appView, rootSet);
   }
 
   private boolean enableInterfaceMethodDesugaring() {
@@ -927,7 +933,7 @@
       stringOptimizer.computeTrivialOperationsOnConstString(code, appInfo.dexItemFactory);
       // Reflection optimization 2. get*Name() with const-class -> const-string
       if (options.enableNameReflectionOptimization) {
-        stringOptimizer.rewriteClassGetName(code, appInfo);
+        stringOptimizer.rewriteClassGetName(code, appInfo, rootSet);
       }
       // Reflection optimization 3. String#valueOf(const-string) -> no op.
       stringOptimizer.removeTrivialConversions(code, appInfo);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 0b54a4d..a875679 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -90,6 +90,7 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
 import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -1815,25 +1816,48 @@
               assert false;
             }
           } else {
-            // TODO(b/120280603): Consider minification!
             InvokeVirtual invoke = inValue.definition.asInvokeVirtual();
             DexMethod invokedMethod = invoke.getInvokedMethod();
             DexType holderType = method.method.getHolder();
             DexClass holder = appInfo.definitionFor(holderType);
             assert holder != null;
             String descriptor = holderType.toDescriptorString();
+            DexItemBasedValueString deferred = null;
             String name = null;
             if (invokedMethod == appInfo.dexItemFactory.classMethods.getName) {
-              name = computeClassName(descriptor, holder, NAME, 0);
+              if (code.options.enableMinification
+                  && !converter.rootSet.noObfuscation.contains(holder)) {
+                deferred = new DexItemBasedValueString(
+                    holderType, new ClassNameComputationInfo(NAME));
+              } else {
+                name = computeClassName(descriptor, holder, NAME);
+              }
             } else if (invokedMethod == appInfo.dexItemFactory.classMethods.getTypeName) {
               // TODO(b/119426668): desugar Type#getTypeName
             } else if (invokedMethod == appInfo.dexItemFactory.classMethods.getCanonicalName) {
-              name = computeClassName(descriptor, holder, CANONICAL_NAME, 0);
+              if (code.options.enableMinification
+                  && !converter.rootSet.noObfuscation.contains(holder)) {
+                deferred = new DexItemBasedValueString(
+                    holderType, new ClassNameComputationInfo(CANONICAL_NAME));
+              } else {
+                name = computeClassName(descriptor, holder, CANONICAL_NAME);
+              }
             } else if (invokedMethod == appInfo.dexItemFactory.classMethods.getSimpleName) {
-              name = computeClassName(descriptor, holder, SIMPLE_NAME, 0);
+              if (code.options.enableMinification
+                  && !converter.rootSet.noObfuscation.contains(holder)) {
+                deferred = new DexItemBasedValueString(
+                    holderType, new ClassNameComputationInfo(SIMPLE_NAME));
+              } else {
+                name = computeClassName(descriptor, holder, SIMPLE_NAME);
+              }
             }
-            assert name != null;
-            encodedField.setStaticValue(new DexValueString(dexItemFactory.createString(name)));
+            assert name != null || deferred != null;
+            if (name != null) {
+              encodedField.setStaticValue(new DexValueString(dexItemFactory.createString(name)));
+            } else {
+              assert deferred != null;
+              encodedField.setStaticValue(deferred);
+            }
           }
         } else if (field.type.isClassType() || field.type.isArrayType()) {
           if (inValue.isZero()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index 496e113..511b077 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -44,11 +44,15 @@
     }
 
     private static final ClassNameComputationInfo DEFAULT_INSTANCE =
-        new ClassNameComputationInfo(ClassNameComputationOption.NONE, 0);
+        new ClassNameComputationInfo(ClassNameComputationOption.NONE);
 
     final ClassNameComputationOption classNameComputationOption;
     final int arrayDepth;
 
+    public ClassNameComputationInfo(ClassNameComputationOption classNameComputationOption) {
+      this(classNameComputationOption, 0);
+    }
+
     public ClassNameComputationInfo(
         ClassNameComputationOption classNameComputationOption, int arrayDepth) {
       this.classNameComputationOption = classNameComputationOption;
@@ -141,6 +145,13 @@
   public static String computeClassName(
       String descriptor,
       DexClass holder,
+      ClassNameComputationOption classNameComputationOption) {
+    return computeClassName(descriptor, holder, classNameComputationOption, 0);
+  }
+
+  public static String computeClassName(
+      String descriptor,
+      DexClass holder,
       ClassNameComputationOption classNameComputationOption,
       int arrayDepth) {
     String name;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index 2e19475..fe8b3f8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
+import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.function.BiFunction;
 import java.util.function.Function;
@@ -115,7 +116,7 @@
   }
 
   // Find Class#get*Name() with a constant-class and replace it with a const-string if possible.
-  public void rewriteClassGetName(IRCode code, AppInfo appInfo) {
+  public void rewriteClassGetName(IRCode code, AppInfo appInfo, RootSet rootSet) {
     // Conflict with {@link CodeRewriter#collectClassInitializerDefaults}.
     if (code.method.isClassInitializer()) {
       return;
@@ -169,7 +170,7 @@
       DexItemBasedConstString deferred = null;
       String name = null;
       if (invokedMethod == appInfo.dexItemFactory.classMethods.getName) {
-        if (code.options.enableMinification) {
+        if (code.options.enableMinification && !rootSet.noObfuscation.contains(holder)) {
           deferred = new DexItemBasedConstString(
               invoke.outValue(), baseType, new ClassNameComputationInfo(NAME, arrayDepth));
         } else {
@@ -189,7 +190,7 @@
           if (!assumeTopLevel) {
             continue;
           }
-          if (code.options.enableMinification) {
+          if (code.options.enableMinification && !rootSet.noObfuscation.contains(holder)) {
             deferred =
                 new DexItemBasedConstString(
                     invoke.outValue(),
@@ -209,7 +210,7 @@
           if (!assumeTopLevel) {
             continue;
           }
-          if (code.options.enableMinification) {
+          if (code.options.enableMinification && !rootSet.noObfuscation.contains(holder)) {
             deferred = new DexItemBasedConstString(
                 invoke.outValue(), baseType, new ClassNameComputationInfo(SIMPLE_NAME, arrayDepth));
           } else {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameInClassInitializerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameInClassInitializerTest.java
new file mode 100644
index 0000000..8f172bd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameInClassInitializerTest.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2018, 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.ir.optimize.reflection;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Collection;
+import org.junit.Test;
+
+class GetNameClinitClass {
+  static String name;
+  static {
+    name = GetNameClinitClass.class.getName();
+  }
+
+  @NeverInline
+  static String getName() {
+    return name;
+  }
+}
+
+class GetNameClinitRunner {
+  public static void main(String[] args) {
+    System.out.print(GetNameClinitClass.getName());
+  }
+}
+
+public class GetNameInClassInitializerTest extends GetNameTestBase {
+  private Collection<Path> classPaths;
+  private static final String JAVA_OUTPUT = GetNameClinitClass.class.getName();
+  private static final Class<?> MAIN = GetNameClinitRunner.class;
+
+  public GetNameInClassInitializerTest(Backend backend, boolean enableMinification)
+      throws Exception {
+    super(backend, enableMinification);
+
+    ImmutableList.Builder<Path> builder = ImmutableList.builder();
+    builder.addAll(ToolHelper.getClassFilesForTestDirectory(
+        ToolHelper.getPackageDirectoryForTestPackage(MAIN.getPackage()),
+        path -> path.getFileName().toString().startsWith("GetNameClinit")));
+    builder.add(ToolHelper.getClassFileForTestClass(NeverInline.class));
+    classPaths = builder.build();
+  }
+
+  @Test
+  public void testJVMoutput() throws Exception {
+    assumeTrue("Only run JVM reference once (for CF backend)",
+        backend == Backend.CF && !enableMinification);
+    testForJvm().addTestClasspath().run(MAIN).assertSuccessWithOutput(JAVA_OUTPUT);
+  }
+
+  @Test
+  public void testR8_pinning() throws Exception {
+    // Pinning the test class.
+    R8TestBuilder builder = testForR8(backend)
+        .addProgramFiles(classPaths)
+        .enableInliningAnnotations()
+        .addKeepMainRule(MAIN)
+        .addKeepRules("-keep class **.GetNameClinit*")
+        .addKeepRules("-printmapping");
+    if (!enableMinification) {
+      builder.addKeepRules("-dontobfuscate");
+    }
+    builder
+        .addOptionsModification(this::configure)
+        .run(MAIN)
+        .assertSuccessWithOutput(JAVA_OUTPUT);
+  }
+
+  @Test
+  public void testR8_shallow_pinning() throws Exception {
+    // Pinning the test class.
+    R8TestBuilder builder = testForR8(backend)
+        .addProgramFiles(classPaths)
+        .enableInliningAnnotations()
+        .addKeepMainRule(MAIN)
+        .addKeepRules("-keep,allowobfuscation class **.GetNameClinit*")
+        .addKeepRules("-printmapping");
+    if (!enableMinification) {
+      builder.addKeepRules("-dontobfuscate");
+    }
+
+    TestRunResult result =
+        builder
+            .addOptionsModification(this::configure)
+            .run(MAIN);
+    result.assertSuccessWithOutput(
+        result.inspector().clazz(GetNameClinitClass.class).getFinalName());
+  }
+}