Merge "Run string optimizations after class inlining."
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 3d1f080..18e67bb 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
@@ -924,7 +924,6 @@
     }
 
     if (!isDebugMode) {
-      // TODO(jsjeon): Consider merging these into one single optimize().
       stringOptimizer.computeTrivialOperationsOnConstString(code, appInfo.dexItemFactory);
       // Reflection optimization 2. get*Name() with const-class -> const-string
       if (options.enableNameReflectionOptimization) {
@@ -1008,7 +1007,12 @@
       // lambda, it does not get collected by merger.
       assert options.enableInlining && inliner != null;
       classInliner.processMethodCode(
-          appInfo.withLiveness(), codeRewriter, method, code, isProcessedConcurrently,
+          appInfo.withLiveness(),
+          codeRewriter,
+          stringOptimizer,
+          method,
+          code,
+          isProcessedConcurrently,
           methodsToInline -> inliner.performForcedInlining(method, code, methodsToInline),
           Suppliers.memoize(() -> inliner.createDefaultOracle(
               method, code,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 30cd8b7..b495507 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
 import com.android.tools.r8.ir.optimize.InliningOracle;
+import com.android.tools.r8.ir.optimize.string.StringOptimizer;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.google.common.collect.Streams;
 import java.util.Iterator;
@@ -121,6 +122,7 @@
   public final void processMethodCode(
       AppInfoWithLiveness appInfo,
       CodeRewriter codeRewriter,
+      StringOptimizer stringOptimizer,
       DexEncodedMethod method,
       IRCode code,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
@@ -187,6 +189,13 @@
       codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code, true);
       // If a method was inlined we may be able to prune additional branches.
       codeRewriter.simplifyIf(code);
+      // If a method was inlined we may see more trivial computation/conversion of String.
+      boolean isDebugMode =
+          code.options.debug || method.getOptimizationInfo().isReachabilitySensitive();
+      if (!isDebugMode) {
+        stringOptimizer.computeTrivialOperationsOnConstString(code, appInfo.dexItemFactory);
+        stringOptimizer.removeTrivialConversions(code, appInfo);
+      }
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
index 09d4cc4..ed1fa75 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
@@ -30,6 +30,17 @@
 
 class StringValueOfTestMain {
 
+  static class Notification {
+    String id;
+    Notification(String id) {
+      this.id = id;
+    }
+
+    String getId() {
+      return id;
+    }
+  }
+
   interface Itf {
     String getter();
   }
@@ -53,6 +64,11 @@
     }
   }
 
+  @NeverInline
+  static String eventuallyReturnsNull(String s) {
+    return System.currentTimeMillis() > 0 ? null : s;
+  }
+
   public static void main(String[] args) {
     Foo foo = new Foo();
     System.out.println(foo.getter());
@@ -73,6 +89,15 @@
     } catch (NullPointerException npe) {
       fail("Not expected: " + npe);
     }
+
+    // No matter what we pass, that function will return null.
+    // But, we're not sure about it, hence not optimizing String#valueOf.
+    System.out.println(String.valueOf(eventuallyReturnsNull(null)));
+    System.out.println(String.valueOf(eventuallyReturnsNull("non-null")));
+
+    // Eligible for class inlining. Make sure we're optimizing valueOf after class inlining.
+    Notification n = new Notification(null);
+    System.out.println(String.valueOf(n.getId()));
   }
 }
 
@@ -83,6 +108,7 @@
       ForceInline.class,
       NeverInline.class,
       StringValueOfTestMain.class,
+      StringValueOfTestMain.Notification.class,
       StringValueOfTestMain.Itf.class,
       StringValueOfTestMain.Foo.class
   );
@@ -92,6 +118,9 @@
       "com.android.tools.r8.ir.optimize.string.StringValueOfTestMain$Foo",
       "com.android.tools.r8.ir.optimize.string.StringValueOfTestMain$Foo",
       "null",
+      "null",
+      "null",
+      "null",
       "null"
   );
   private static final Class<?> MAIN = StringValueOfTestMain.class;
@@ -175,14 +204,14 @@
         .addProgramClasses(CLASSES)
         .run(MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 4, 1, 0);
+    test(result, 7, 1, 0);
 
     result = testForD8()
         .release()
         .addProgramClasses(CLASSES)
         .run(MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 3, 1, 1);
+    test(result, 6, 1, 1);
   }
 
   @Test
@@ -196,6 +225,9 @@
         .addOptionsModification(this::configure)
         .run(MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 1, 1, 1);
+    // Due to the different behavior regarding constant canonicalization.
+    int expectedNullCount = backend == Backend.CF ? 2 : 1;
+    int expectedNullStringCount = backend == Backend.CF ? 2 : 1;
+    test(result, 3, expectedNullCount, expectedNullStringCount);
   }
 }