Remove String#valueOf for uninstantiated types.

Bug: 118912005
Change-Id: I58a0bb0e25426058af6c5c23dbcc4854e66a735f
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 23d3621..7c9fda8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.LongInterval;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -446,7 +445,7 @@
     return !users.isEmpty() || !phiUsers.isEmpty() || numberOfDebugUsers() > 0;
   }
 
-  public boolean isAlwaysNull(AppView<AppInfoWithLiveness> appView) {
+  public boolean isAlwaysNull(AppView<? extends AppInfo> appView) {
     if (hasLocalInfo()) {
       // Not always null as the value can be changed via the debugger.
       return false;
@@ -454,8 +453,11 @@
     if (typeLattice.isDefinitelyNull()) {
       return true;
     }
-    if (typeLattice.isClassType()) {
-      return typeLattice.asClassTypeLatticeElement().getClassType().isAlwaysNull(appView);
+    if (typeLattice.isClassType() && appView.appInfo().hasLiveness()) {
+      return typeLattice
+          .asClassTypeLatticeElement()
+          .getClassType()
+          .isAlwaysNull(appView.withLiveness());
     }
     return false;
   }
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 4be6602..aca25ac 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
@@ -400,7 +400,7 @@
           continue;
         }
         TypeLatticeElement inType = in.getTypeLattice();
-        if (inType.isNullType()) {
+        if (in.isAlwaysNull(appView)) {
           Value nullStringValue =
               code.createValue(
                   TypeLatticeElement.stringClassType(appView, definitelyNotNull()),
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 1e9b3f4..512c54d 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
@@ -11,6 +11,7 @@
 
 import com.android.tools.r8.ForceInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -23,7 +24,6 @@
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Streams;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -46,6 +46,15 @@
     String getter();
   }
 
+  static class Uninitialized {
+  }
+
+  @NeverInline
+  @NeverPropagateValue
+  static String consumeUninitialized(Uninitialized arg) {
+    return String.valueOf(arg);
+  }
+
   @NeverInline
   static String hideNPE(String s) {
     return String.valueOf(s);
@@ -90,6 +99,7 @@
     } catch (NullPointerException npe) {
       fail("Not expected: " + npe);
     }
+    System.out.println(consumeUninitialized(null));
 
     // No matter what we pass, that function will return null.
     // But, we're not sure about it, hence not optimizing String#valueOf.
@@ -113,6 +123,7 @@
       "null",
       "null",
       "null",
+      "null",
       "null"
   );
   private static final Class<?> MAIN = StringValueOfTestMain.class;
@@ -173,25 +184,29 @@
 
   private void test(
       TestRunResult result,
-      int expectedStringValueOfCount,
+      int expectedStringValueOfCountInMain,
       int expectedNullCount,
-      int expectedNullStringCount)
+      int expectedNullStringCountInMain,
+      int expectedStringValueOfCountInConsumer,
+      int expectedNullStringCountInConsumer)
       throws Exception {
     CodeInspector codeInspector = result.inspector();
     ClassSubject mainClass = codeInspector.clazz(MAIN);
     MethodSubject mainMethod = mainClass.mainMethod();
     assertThat(mainMethod, isPresent());
-    long count = countStringValueOf(mainMethod);
-    assertEquals(expectedStringValueOfCount, count);
-    count = countConstNullNumber(mainMethod);
-    assertEquals(expectedNullCount, count);
-    count = countNullStringNumber(mainMethod);
-    assertEquals(expectedNullStringCount, count);
+    assertEquals(expectedStringValueOfCountInMain, countStringValueOf(mainMethod));
+    assertEquals(expectedNullCount, countConstNullNumber(mainMethod));
+    assertEquals(expectedNullStringCountInMain, countNullStringNumber(mainMethod));
 
-    MethodSubject hideNPE = mainClass.method(STRING_TYPE, "hideNPE", ImmutableList.of(STRING_TYPE));
+    MethodSubject hideNPE = mainClass.uniqueMethodWithName("hideNPE");
     assertThat(hideNPE, isPresent());
     // Due to the nullable argument, valueOf should remain.
     assertEquals(1, countStringValueOf(hideNPE));
+
+    MethodSubject uninit = mainClass.uniqueMethodWithName("consumeUninitialized");
+    assertThat(uninit, isPresent());
+    assertEquals(expectedStringValueOfCountInConsumer, countStringValueOf(uninit));
+    assertEquals(expectedNullStringCountInConsumer, countNullStringNumber(uninit));
   }
 
   @Test
@@ -205,7 +220,7 @@
             .setMinApi(parameters.getRuntime())
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 7, 1, 0);
+    test(result, 7, 1, 0, 1, 0);
 
     result =
         testForD8()
@@ -214,7 +229,7 @@
             .setMinApi(parameters.getRuntime())
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 5, 1, 1);
+    test(result, 5, 1, 1, 1, 0);
   }
 
   @Test
@@ -223,6 +238,7 @@
         testForR8(parameters.getBackend())
             .addProgramClassesAndInnerClasses(MAIN)
             .enableInliningAnnotations()
+            .enableMemberValuePropagationAnnotations()
             .addKeepMainRule(MAIN)
             .setMinApi(parameters.getRuntime())
             .noMinification()
@@ -232,6 +248,6 @@
     // Due to the different behavior regarding constant canonicalization.
     int expectedNullCount = parameters.getBackend() == Backend.CF ? 2 : 1;
     int expectedNullStringCount = parameters.getBackend() == Backend.CF ? 2 : 1;
-    test(result, 3, expectedNullCount, expectedNullStringCount);
+    test(result, 3, expectedNullCount, expectedNullStringCount, 0, 1);
   }
 }