Misc. updates to allow better logging removal

Bug: 174285670
Change-Id: Ib90d7ae7dba94d91d5fbba961daca37508de1acf
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 489d0b6..2d91d71 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -125,8 +125,7 @@
       this.callSiteOptimizationInfoPropagator = null;
     }
 
-    this.libraryMethodSideEffectModelCollection =
-        new LibraryMethodSideEffectModelCollection(dexItemFactory());
+    this.libraryMethodSideEffectModelCollection = new LibraryMethodSideEffectModelCollection(this);
     this.libraryMemberOptimizer = new LibraryMemberOptimizer(this);
 
     if (enableWholeProgramOptimizations() && options().protoShrinking().isProtoShrinkingEnabled()) {
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 fd33483..30ac84b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -440,6 +440,7 @@
       createStaticallyKnownType("Ljava/util/logging/Logger;");
   public final DexType javaUtilSetType = createStaticallyKnownType("Ljava/util/Set;");
 
+  public final DexType androidAppActivity = createStaticallyKnownType("Landroid/app/Activity;");
   public final DexType androidOsBuildType = createStaticallyKnownType("Landroid/os/Build;");
   public final DexType androidOsBuildVersionType =
       createStaticallyKnownType("Landroid/os/Build$VERSION;");
@@ -683,6 +684,17 @@
           createString("makeConcat")
       );
 
+  public Map<DexMethod, int[]> libraryMethodsNonNullParamOrThrow =
+      buildLibraryMethodsNonNullParamOrThrow();
+
+  private Map<DexMethod, int[]> buildLibraryMethodsNonNullParamOrThrow() {
+    ImmutableMap.Builder<DexMethod, int[]> builder = ImmutableMap.builder();
+    for (DexMethod requireNonNullMethod : objectsMethods.requireNonNullMethods()) {
+      builder.put(requireNonNullMethod, new int[] {0});
+    }
+    return builder.build();
+  }
+
   public Set<DexMethod> libraryMethodsReturningReceiver =
       ImmutableSet.<DexMethod>builder()
           .addAll(stringBufferMethods.appendMethods)
@@ -719,6 +731,7 @@
   public Set<DexType> libraryTypesAssumedToBePresent =
       ImmutableSet.<DexType>builder()
           .add(
+              androidAppActivity,
               callableType,
               enumType,
               npeType,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index 23342df..b176db1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -165,7 +165,7 @@
     finalFieldPuts.forEach(
         (field, put) -> {
           DexType fieldType = field.field.type;
-          Value value = put.value();
+          Value value = put.value().getAliasedValue();
           if (unnecessaryStaticPuts.contains(put)) {
             if (fieldType == dexItemFactory.stringType) {
               fieldsWithStaticValues.put(field, getDexStringValue(value, context.getHolderType()));
@@ -404,7 +404,7 @@
             }
             DexField fieldReference = put.getField();
             DexEncodedField field = context.getHolder().lookupField(fieldReference);
-            Value value = put.value();
+            Value value = put.value().getAliasedValue();
             TypeElement valueType = value.getType();
             if (field != null) {
               if (isReadBefore.contains(field)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index 7f5d173..93a330e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -52,6 +52,10 @@
 
   public abstract BitSet getNonNullParamOrThrow();
 
+  public final boolean hasNonNullParamOnNormalExits() {
+    return getNonNullParamOnNormalExits() != null;
+  }
+
   public abstract BitSet getNonNullParamOnNormalExits();
 
   public abstract boolean hasBeenInlinedIntoSingleCallSite();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 9868a64..3aaba06 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -156,8 +156,8 @@
         definition, code, feedback, instanceFieldInitializationInfos, timing);
     computeMayHaveSideEffects(feedback, definition, code, timing);
     computeReturnValueOnlyDependsOnArguments(feedback, definition, code, timing);
-    computeNonNullParamOrThrow(feedback, definition, code, timing);
-    computeNonNullParamOnNormalExits(feedback, code, timing);
+    BitSet nonNullParamOrThrow = computeNonNullParamOrThrow(feedback, definition, code, timing);
+    computeNonNullParamOnNormalExits(feedback, code, nonNullParamOrThrow, timing);
   }
 
   private void identifyBridgeInfo(
@@ -1165,18 +1165,19 @@
     }
   }
 
-  private void computeNonNullParamOrThrow(
+  private BitSet computeNonNullParamOrThrow(
       OptimizationFeedback feedback, DexEncodedMethod method, IRCode code, Timing timing) {
     timing.begin("Compute non-null-param-or-throw");
-    computeNonNullParamOrThrow(feedback, method, code);
+    BitSet nonNullParamOrThrow = computeNonNullParamOrThrow(feedback, method, code);
     timing.end();
+    return nonNullParamOrThrow;
   }
 
   // Track usage of parameters and compute their nullability and possibility of NPE.
-  private void computeNonNullParamOrThrow(
+  private BitSet computeNonNullParamOrThrow(
       OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
     if (method.getOptimizationInfo().getNonNullParamOrThrow() != null) {
-      return;
+      return null;
     }
     List<Value> arguments = code.collectArguments();
     BitSet paramsCheckedForNull = new BitSet();
@@ -1196,66 +1197,80 @@
         paramsCheckedForNull.set(index);
       }
     }
-    if (paramsCheckedForNull.length() > 0) {
+    if (!paramsCheckedForNull.isEmpty()) {
       feedback.setNonNullParamOrThrow(method, paramsCheckedForNull);
+      return paramsCheckedForNull;
     }
+    return null;
   }
 
   private void computeNonNullParamOnNormalExits(
-      OptimizationFeedback feedback, IRCode code, Timing timing) {
+      OptimizationFeedback feedback, IRCode code, BitSet nonNullParamOrThrow, Timing timing) {
     timing.begin("Compute non-null-param-on-normal-exits");
-    computeNonNullParamOnNormalExits(feedback, code);
+    computeNonNullParamOnNormalExits(feedback, code, nonNullParamOrThrow);
     timing.end();
   }
 
-  private void computeNonNullParamOnNormalExits(OptimizationFeedback feedback, IRCode code) {
+  private void computeNonNullParamOnNormalExits(
+      OptimizationFeedback feedback, IRCode code, BitSet nonNullParamOrThrow) {
     Set<BasicBlock> normalExits = Sets.newIdentityHashSet();
     normalExits.addAll(code.computeNormalExitBlocks());
     DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
     List<Value> arguments = code.collectArguments();
     BitSet facts = new BitSet();
-    Set<BasicBlock> nullCheckedBlocks = Sets.newIdentityHashSet();
+    if (nonNullParamOrThrow != null) {
+      facts.or(nonNullParamOrThrow);
+    }
     for (int index = 0; index < arguments.size(); index++) {
+      if (facts.get(index)) {
+        continue;
+      }
       Value argument = arguments.get(index);
-      // Consider reference-type parameter only.
-      if (!argument.getType().isReferenceType()) {
-        continue;
-      }
-      // The receiver is always non-null on normal exits.
-      if (argument.isThis()) {
+      if (argument.getType().isReferenceType()
+          && isNonNullOnNormalExit(code, argument, dominatorTree, normalExits)) {
         facts.set(index);
-        continue;
-      }
-      // Collect basic blocks that check nullability of the parameter.
-      nullCheckedBlocks.clear();
-      for (Instruction user : argument.uniqueUsers()) {
-        if (user.isAssumeWithNonNullAssumption()) {
-          nullCheckedBlocks.add(user.asAssume().getBlock());
-        }
-        if (user.isIf()
-            && user.asIf().isZeroTest()
-            && (user.asIf().getType() == If.Type.EQ || user.asIf().getType() == If.Type.NE)) {
-          nullCheckedBlocks.add(user.asIf().targetFromNonNullObject());
-        }
-      }
-      if (!nullCheckedBlocks.isEmpty()) {
-        boolean allExitsCovered = true;
-        for (BasicBlock normalExit : normalExits) {
-          if (!isNormalExitDominated(normalExit, code, dominatorTree, nullCheckedBlocks)) {
-            allExitsCovered = false;
-            break;
-          }
-        }
-        if (allExitsCovered) {
-          facts.set(index);
-        }
       }
     }
-    if (facts.length() > 0) {
+    if (!facts.isEmpty()) {
       feedback.setNonNullParamOnNormalExits(code.method(), facts);
     }
   }
 
+  private boolean isNonNullOnNormalExit(
+      IRCode code, Value value, DominatorTree dominatorTree, Set<BasicBlock> normalExits) {
+    assert value.getType().isReferenceType();
+
+    // The receiver is always non-null on normal exits.
+    if (value.isThis()) {
+      return true;
+    }
+
+    // Collect basic blocks that check nullability of the parameter.
+    Set<BasicBlock> nullCheckedBlocks = Sets.newIdentityHashSet();
+    for (Instruction user : value.aliasedUsers()) {
+      if (user.isAssumeWithNonNullAssumption()
+          || user.throwsNpeIfValueIsNull(value, appView, code.context())) {
+        nullCheckedBlocks.add(user.getBlock());
+      }
+      if (user.isIf()
+          && user.asIf().isZeroTest()
+          && (user.asIf().getType() == If.Type.EQ || user.asIf().getType() == If.Type.NE)) {
+        nullCheckedBlocks.add(user.asIf().targetFromNonNullObject());
+      }
+    }
+
+    if (nullCheckedBlocks.isEmpty()) {
+      return false;
+    }
+
+    for (BasicBlock normalExit : normalExits) {
+      if (!isNormalExitDominated(normalExit, code, dominatorTree, nullCheckedBlocks)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   private boolean isNormalExitDominated(
       BasicBlock normalExit,
       IRCode code,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
index ae91ac8..3703148 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
@@ -4,11 +4,13 @@
 
 package com.android.tools.r8.ir.optimize.library;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.LibraryMethod;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.library.sideeffects.JavaLangObjectsSideEffectCollection;
 import com.android.tools.r8.utils.BiPredicateUtils;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -25,23 +27,31 @@
 
   private final Set<DexMethod> nonFinalMethodsWithoutSideEffects;
 
-  public LibraryMethodSideEffectModelCollection(DexItemFactory dexItemFactory) {
-    finalMethodsWithoutSideEffects = buildFinalMethodsWithoutSideEffects(dexItemFactory);
+  public LibraryMethodSideEffectModelCollection(AppView<?> appView) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    finalMethodsWithoutSideEffects = buildFinalMethodsWithoutSideEffects(appView, dexItemFactory);
     unconditionalFinalMethodsWithoutSideEffects =
         buildUnconditionalFinalMethodsWithoutSideEffects(dexItemFactory);
     nonFinalMethodsWithoutSideEffects = buildNonFinalMethodsWithoutSideEffects(dexItemFactory);
   }
 
   private static Map<DexMethod, BiPredicate<DexMethod, List<Value>>>
-      buildFinalMethodsWithoutSideEffects(DexItemFactory dexItemFactory) {
+      buildFinalMethodsWithoutSideEffects(AppView<?> appView, DexItemFactory dexItemFactory) {
     ImmutableMap.Builder<DexMethod, BiPredicate<DexMethod, List<Value>>> builder =
         ImmutableMap.<DexMethod, BiPredicate<DexMethod, List<Value>>>builder()
             .put(
+                dexItemFactory.objectsMethods.toStringWithObject,
+                (method, arguments) ->
+                    !JavaLangObjectsSideEffectCollection.toStringMayHaveSideEffects(
+                        appView, arguments))
+            .put(
                 dexItemFactory.stringMembers.constructor,
                 (method, arguments) -> arguments.get(1).isNeverNull())
             .put(
                 dexItemFactory.stringMembers.valueOf,
-                (method, arguments) -> arguments.get(0).isNeverNull());
+                (method, arguments) ->
+                    !JavaLangObjectsSideEffectCollection.toStringMayHaveSideEffects(
+                        appView, arguments));
     putAll(
         builder,
         dexItemFactory.stringBufferMethods.constructorMethods,
@@ -71,8 +81,11 @@
         .add(dexItemFactory.shortMembers.toString)
         .add(dexItemFactory.stringBufferMethods.toString)
         .add(dexItemFactory.stringBuilderMethods.toString)
+        .add(dexItemFactory.stringMembers.length)
         .add(dexItemFactory.stringMembers.hashCode)
+        .add(dexItemFactory.stringMembers.isEmpty)
         .add(dexItemFactory.stringMembers.toString)
+        .add(dexItemFactory.stringMembers.trim)
         .addAll(dexItemFactory.classMethods.getNames)
         .addAll(dexItemFactory.boxedValueOfMethods())
         .build();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
index ecbb3e4..1141303 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -45,6 +45,7 @@
   void run(Set<DexEncodedField> finalLibraryFields) {
     modelInstanceInitializers();
     modelStaticFinalLibraryFields(finalLibraryFields);
+    modelLibraryMethodsNonNullParamOrThrow();
     modelLibraryMethodsReturningNonNull();
     modelLibraryMethodsReturningReceiver();
     modelLibraryMethodsWithoutSideEffects();
@@ -87,6 +88,30 @@
     }
   }
 
+  private void modelLibraryMethodsNonNullParamOrThrow() {
+    dexItemFactory.libraryMethodsNonNullParamOrThrow.forEach(
+        (method, nonNullParamOrThrow) -> {
+          DexEncodedMethod definition = lookupMethod(method);
+          if (definition != null) {
+            assert nonNullParamOrThrow.length > 0;
+            int size = nonNullParamOrThrow[nonNullParamOrThrow.length - 1] + 1;
+            BitSet bitSet = new BitSet(size);
+            for (int argumentIndex : nonNullParamOrThrow) {
+              assert argumentIndex < size;
+              bitSet.set(argumentIndex);
+            }
+            feedback.setNonNullParamOrThrow(definition, bitSet);
+
+            // Also set non-null-param-on-normal-exits info.
+            if (definition.getOptimizationInfo().hasNonNullParamOnNormalExits()) {
+              definition.getOptimizationInfo().getNonNullParamOnNormalExits().or(bitSet);
+            } else {
+              feedback.setNonNullParamOnNormalExits(definition, (BitSet) bitSet.clone());
+            }
+          }
+        });
+  }
+
   private void modelLibraryMethodsReturningNonNull() {
     for (DexMethod method : dexItemFactory.libraryMethodsReturningNonNull) {
       DexEncodedMethod definition = lookupMethod(method);
@@ -131,14 +156,6 @@
       DexEncodedMethod definition = lookupMethod(requireNonNullMethod);
       if (definition != null) {
         feedback.methodReturnsArgument(definition, 0);
-
-        BitSet nonNullParamOrThrow = new BitSet();
-        nonNullParamOrThrow.set(0);
-        feedback.setNonNullParamOrThrow(definition, nonNullParamOrThrow);
-
-        BitSet nonNullParamOnNormalExits = new BitSet();
-        nonNullParamOnNormalExits.set(0);
-        feedback.setNonNullParamOnNormalExits(definition, nonNullParamOnNormalExits);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
index a303082..831236b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
@@ -5,19 +5,15 @@
 package com.android.tools.r8.ir.optimize.library;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexItemFactory.ObjectsMethods;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Set;
 
 public class ObjectsMethodOptimizer extends StatelessLibraryMethodModelCollection {
@@ -90,44 +86,6 @@
         invoke.outValue().replaceUsers(object);
       }
       instructionIterator.removeOrReplaceByDebugLocalRead();
-      return;
-    }
-
-    // Remove Objects.toString() if it is unused and does not have side effects.
-    if (!invoke.hasOutValue() || !invoke.outValue().hasNonDebugUsers()) {
-      // Calling toString() on an array does not call toString() on the array elements.
-      if (type.isArrayType()) {
-        instructionIterator.removeOrReplaceByDebugLocalRead();
-        return;
-      }
-
-      assert type.isClassType();
-
-      // Check if this is a library class with a toString() method that does not have side effects.
-      DexType classType = type.asClassType().getClassType();
-      DexMethod toStringMethodReference =
-          dexItemFactory.objectMembers.toString.withHolder(classType, dexItemFactory);
-      if (appView
-          .getLibraryMethodSideEffectModelCollection()
-          .isSideEffectFreeFinalMethod(toStringMethodReference, invoke.arguments())) {
-        instructionIterator.removeOrReplaceByDebugLocalRead();
-        return;
-      }
-
-      // Check if this is a program class with a toString() method that does not have side effects.
-      AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
-      if (appInfo != null) {
-        DexClass clazz = appInfo.definitionFor(classType, code.context());
-        if (clazz != null && clazz.isEffectivelyFinal(appView)) {
-          SingleResolutionResult resolutionResult =
-              appInfo.resolveMethodOn(clazz, toStringMethodReference).asSingleResolution();
-          if (resolutionResult != null
-              && !resolutionResult.getResolvedMethod().getOptimizationInfo().mayHaveSideEffects()) {
-            instructionIterator.removeOrReplaceByDebugLocalRead();
-            return;
-          }
-        }
-      }
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
index a8fabe3..8c62340 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -19,7 +20,6 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.utils.ValueUtils;
 import java.util.Set;
 
 public class StringMethodOptimizer extends StatelessLibraryMethodModelCollection {
@@ -48,7 +48,7 @@
     if (singleTargetReference == dexItemFactory.stringMembers.equals) {
       optimizeEquals(code, instructionIterator, invoke.asInvokeVirtual());
     } else if (singleTargetReference == dexItemFactory.stringMembers.valueOf) {
-      optimizeValueOf(instructionIterator, invoke.asInvokeStatic());
+      optimizeValueOf(code, instructionIterator, invoke.asInvokeStatic(), affectedValues);
     }
   }
 
@@ -65,12 +65,30 @@
     }
   }
 
-  private void optimizeValueOf(InstructionListIterator instructionIterator, InvokeStatic invoke) {
-    // Optimize String.valueOf(stringBuilder) if unused.
-    if (ValueUtils.isNonNullStringBuilder(invoke.getFirstArgument(), dexItemFactory)) {
-      if (!invoke.hasOutValue() || !invoke.outValue().hasNonDebugUsers()) {
-        instructionIterator.removeOrReplaceByDebugLocalRead();
+  private void optimizeValueOf(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeStatic invoke,
+      Set<Value> affectedValues) {
+    Value object = invoke.getFirstArgument();
+    TypeElement type = object.getType();
+
+    // Optimize String.valueOf(null) into "null".
+    if (type.isDefinitelyNull()) {
+      instructionIterator.replaceCurrentInstructionWithConstString(appView, code, "null");
+      if (invoke.hasOutValue()) {
+        affectedValues.addAll(invoke.outValue().affectedValues());
       }
+      return;
+    }
+
+    // Optimize String.valueOf(nonNullString) into nonNullString.
+    if (type.isDefinitelyNotNull() && type.isStringType(dexItemFactory)) {
+      if (invoke.hasOutValue()) {
+        affectedValues.addAll(invoke.outValue().affectedValues());
+        invoke.outValue().replaceUsers(object);
+      }
+      instructionIterator.removeOrReplaceByDebugLocalRead();
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
new file mode 100644
index 0000000..5f74465
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2021, 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.library.sideeffects;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.List;
+
+public class JavaLangObjectsSideEffectCollection {
+
+  public static boolean toStringMayHaveSideEffects(AppView<?> appView, List<Value> arguments) {
+    // Calling toString() on an array does not call toString() on the array elements.
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    TypeElement type = arguments.get(0).getType();
+    if (type.isArrayType() || type.isNullType()) {
+      return false;
+    }
+
+    assert type.isClassType();
+
+    // Check if this is a library class with a toString() method that does not have side effects.
+    DexType classType = type.asClassType().getClassType();
+    DexMethod toStringMethodReference =
+        dexItemFactory.objectMembers.toString.withHolder(classType, dexItemFactory);
+    if (appView
+        .getLibraryMethodSideEffectModelCollection()
+        .isSideEffectFreeFinalMethod(toStringMethodReference, arguments)) {
+      return false;
+    }
+
+    if (appView.appInfo().hasLiveness()) {
+      AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
+
+      // Check if there is an -assumenosideeffects rule for the toString() method.
+      if (appInfo.isAssumeNoSideEffectsMethod(toStringMethodReference)) {
+        return false;
+      }
+
+      // Check if this is a program class with a toString() method that does not have side effects.
+      DexClass clazz = appInfo.definitionFor(classType);
+      if (clazz != null && clazz.isEffectivelyFinal(appView)) {
+        SingleResolutionResult resolutionResult =
+            appInfo.resolveMethodOn(clazz, toStringMethodReference).asSingleResolution();
+        if (resolutionResult != null
+            && !resolutionResult.getResolvedMethod().getOptimizationInfo().mayHaveSideEffects()) {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+}
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 af2dee2..3c6c003 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
@@ -299,10 +299,6 @@
 
   // Find Class#get*Name() with a constant-class and replace it with a const-string if possible.
   public void rewriteClassGetName(AppView<?> appView, IRCode code) {
-    // Conflict with {@link CodeRewriter#collectClassInitializerDefaults}.
-    if (code.method().isClassInitializer()) {
-      return;
-    }
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     InstructionListIterator it = code.instructionListIterator();
     while (it.hasNext()) {
@@ -363,11 +359,16 @@
       // while its name is used to compute hash code, which won't be optimized, it's better not to
       // compute the name.
       if (!appView.options().testing.forceNameReflectionOptimization) {
-        EscapeAnalysis escapeAnalysis =
-            new EscapeAnalysis(appView, StringOptimizerEscapeAnalysisConfiguration.getInstance());
-        if (mayBeRenamed || escapeAnalysis.isEscaping(code, out)) {
+        if (mayBeRenamed) {
           continue;
         }
+        if (invokedMethod != factory.classMethods.getSimpleName) {
+          EscapeAnalysis escapeAnalysis =
+              new EscapeAnalysis(appView, StringOptimizerEscapeAnalysisConfiguration.getInstance());
+          if (escapeAnalysis.isEscaping(code, out)) {
+            continue;
+          }
+        }
       }
 
       String descriptor = baseType.toDescriptorString();
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index bbd5836..028ed02 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -38,6 +38,11 @@
   }
 
   @Override
+  public boolean isR8TestRunResult() {
+    return true;
+  }
+
+  @Override
   protected R8TestRunResult self() {
     return this;
   }
diff --git a/src/test/java/com/android/tools/r8/SingleTestRunResult.java b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
index 875e50d..d532547 100644
--- a/src/test/java/com/android/tools/r8/SingleTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
@@ -34,6 +34,10 @@
     this.result = result;
   }
 
+  public boolean isR8TestRunResult() {
+    return false;
+  }
+
   public AndroidApp app() {
     return app;
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java
index 5852266..b96ee7f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize.string;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -22,7 +23,6 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.Streams;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -93,17 +93,17 @@
   }
 
   private long countStringLength(MethodSubject method) {
-    return Streams.stream(method.iterateInstructions(instructionSubject -> {
-      if (instructionSubject.isInvoke()) {
-        return isStringLength(instructionSubject.getMethod());
-      }
-      return false;
-    })).count();
+    return method
+        .streamInstructions()
+        .filter(
+            instructionSubject ->
+                instructionSubject.isInvoke() && isStringLength(instructionSubject.getMethod()))
+        .count();
   }
 
   private long countNonZeroConstNumber(MethodSubject method) {
-    return Streams.stream(method.iterateInstructions(InstructionSubject::isConstNumber)).count()
-        - Streams.stream(method.iterateInstructions(instr -> instr.isConstNumber(0))).count();
+    return method.streamInstructions().filter(InstructionSubject::isConstNumber).count()
+        - method.streamInstructions().filter(instr -> instr.isConstNumber(0)).count();
   }
 
   private void test(
@@ -117,9 +117,13 @@
     ClassSubject mainClass = codeInspector.clazz(MAIN);
 
     MethodSubject clinit = mainClass.clinit();
-    assertThat(clinit, isPresent());
-    assertEquals(expectedStringLengthCountInClinit, countStringLength(clinit));
-    assertEquals(expectedConstNumberCountInClinit, countNonZeroConstNumber(clinit));
+    if (result.isR8TestRunResult()) {
+      assertThat(clinit, isAbsent());
+    } else {
+      assertThat(clinit, isPresent());
+      assertEquals(expectedStringLengthCountInClinit, countStringLength(clinit));
+      assertEquals(expectedConstNumberCountInClinit, countNonZeroConstNumber(clinit));
+    }
 
     MethodSubject m = mainClass.uniqueMethodWithName("instanceMethod");
     assertThat(m, isPresent());
@@ -154,7 +158,7 @@
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     // TODO(b/125303292): NAME_LENGTH is still not computed at compile time.
-    test(result, 1, 0, 0, 1);
+    test(result, 0, 0, 0, 1);
   }
 
   @Test
@@ -171,7 +175,6 @@
             .assertSuccessWithOutput(JAVA_OUTPUT);
     // No canonicalization in CF.
     int expectedConstNumber = parameters.isCfRuntime() ? 2 : 1;
-    // TODO(b/125303292): NAME_LENGTH is still not computed at compile time.
-    test(result, 1, 0, 0, expectedConstNumber);
+    test(result, 0, 0, 0, expectedConstNumber);
   }
 }