Version 1.4.50

Cherry-pick: Cache synthesized classes at AppInfo level.
CL: https://r8-review.googlesource.com/c/r8/+/34320

Cherry-pick: Refactor InstructionIterator to use ListIterator
CL: https://r8-review.googlesource.com/c/r8/+/33111

Cherry-pick: Add tests and TODOs for rebinding of fields
CL: https://r8-review.googlesource.com/c/r8/+/33840

Cherry-pick: Revert "Revert "Disallow const instructions after throwing instructions""
CL: https://r8-review.googlesource.com/c/r8/+/33227

Cherry-pick: Don't allow fill-array-data in consistency check.
CL: https://r8-review.googlesource.com/c/r8/+/33109

Cherry-pick: Refactor rewriteWithConstant to make code clearer
CL: https://r8-review.googlesource.com/c/r8/+/33225

Cherry-pick: Ignore <clinit> of field holder in member value propagation
CL: https://r8-review.googlesource.com/c/r8/+/33844

Bug: 123857022, 113374256, 124155517, 124593221, 74379749
Change-Id: Ia74e53a026ff1c70fc9c859675dd87275c50d2c8
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index b31bcbd..a6b94b0 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.4.49";
+  public static final String LABEL = "1.4.50";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 028f5a2..db83f6a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -3,22 +3,17 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap.Builder;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.Queue;
-import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 
@@ -28,6 +23,10 @@
   public final DexItemFactory dexItemFactory;
   private final ConcurrentHashMap<DexType, Map<Descriptor<?,?>, KeyedDexItem<?>>> definitions =
       new ConcurrentHashMap<>();
+  // For some optimizations, e.g. optimizing synthetic classes, we may need to resolve the current
+  // class being optimized.
+  private ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses =
+      new ConcurrentHashMap<>();
 
   public AppInfo(DexApplication application) {
     this.app = application;
@@ -48,6 +47,16 @@
     this(application);
   }
 
+  public void addSynthesizedClass(DexProgramClass clazz) {
+    assert clazz.type.isD8R8SynthesizedClassType();
+    DexProgramClass previous = synthesizedClasses.put(clazz.type, clazz);
+    assert previous == null || previous == clazz;
+  }
+
+  public Collection<DexProgramClass> getSynthesizedClassesForSanityCheck() {
+    return Collections.unmodifiableCollection(synthesizedClasses.values());
+  }
+
   private Map<Descriptor<?,?>, KeyedDexItem<?>> computeDefinitions(DexType type) {
     Builder<Descriptor<?,?>, KeyedDexItem<?>> builder = ImmutableMap.builder();
     DexClass clazz = app.definitionFor(type);
@@ -78,6 +87,11 @@
   }
 
   public DexClass definitionFor(DexType type) {
+    DexProgramClass cached = synthesizedClasses.get(type);
+    if (cached != null) {
+      assert app.definitionFor(type) == null;
+      return cached;
+    }
     return app.definitionFor(type);
   }
 
@@ -524,51 +538,6 @@
     return result;
   }
 
-  public boolean canTriggerStaticInitializer(DexType type, boolean ignoreTypeItself) {
-    DexClass clazz = definitionFor(type);
-    assert clazz != null;
-    return canTriggerStaticInitializer(clazz, ignoreTypeItself);
-  }
-
-  public boolean canTriggerStaticInitializer(DexClass clazz, boolean ignoreTypeItself) {
-    Set<DexType> knownInterfaces = Sets.newIdentityHashSet();
-
-    // Process superclass chain.
-    DexClass current = clazz;
-    while (current != null && current.type != dexItemFactory.objectType) {
-      if (canTriggerStaticInitializer(current) && (!ignoreTypeItself || current != clazz)) {
-        return true;
-      }
-      knownInterfaces.addAll(Arrays.asList(current.interfaces.values));
-      current = current.superType != null ? definitionFor(current.superType) : null;
-    }
-
-    // Process interfaces.
-    Queue<DexType> queue = new ArrayDeque<>(knownInterfaces);
-    while (!queue.isEmpty()) {
-      DexType iface = queue.remove();
-      DexClass definition = definitionFor(iface);
-      if (canTriggerStaticInitializer(definition)) {
-        return true;
-      }
-      if (!definition.isInterface()) {
-        throw new Unreachable(iface.toSourceString() + " is expected to be an interface");
-      }
-
-      for (DexType superIface : definition.interfaces.values) {
-        if (knownInterfaces.add(superIface)) {
-          queue.add(superIface);
-        }
-      }
-    }
-    return false;
-  }
-
-  public static boolean canTriggerStaticInitializer(DexClass clazz) {
-    // Assume it *may* trigger if we didn't find the definition.
-    return clazz == null || clazz.hasClassInitializer();
-  }
-
   public interface ResolutionResult {
 
     DexEncodedMethod asResultOfResolve();
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 828d98d..5e65aa3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -567,7 +567,8 @@
   }
 
   public boolean classInitializationMayHaveSideEffects(AppInfo appInfo, Predicate<DexType> ignore) {
-    if (ignore.test(type)) {
+    if (ignore.test(type)
+        || appInfo.dexItemFactory.libraryTypesWithoutStaticInitialization.contains(type)) {
       return false;
     }
     if (hasNonTrivialClassInitializer()) {
@@ -576,6 +577,15 @@
     if (defaultValuesForStaticFieldsMayTriggerAllocation()) {
       return true;
     }
+    return initializationOfParentTypesMayHaveSideEffects(appInfo, ignore);
+  }
+
+  public boolean initializationOfParentTypesMayHaveSideEffects(AppInfo appInfo) {
+    return initializationOfParentTypesMayHaveSideEffects(appInfo, Predicates.alwaysFalse());
+  }
+
+  public boolean initializationOfParentTypesMayHaveSideEffects(
+      AppInfo appInfo, Predicate<DexType> ignore) {
     for (DexType iface : interfaces.values) {
       if (iface.classInitializationMayHaveSideEffects(appInfo, ignore)) {
         return true;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 42cc0ce..96f34bc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 
 public class DexEncodedField extends KeyedDexItem<DexField> {
 
@@ -109,22 +110,23 @@
   }
 
   // Returns a const instructions if this field is a compile time final const.
-  public Instruction valueAsConstInstruction(AppInfo appInfo, Value dest) {
+  public Instruction valueAsConstInstruction(AppInfoWithLiveness appInfo, Value dest) {
     // The only way to figure out whether the DexValue contains the final value
     // is ensure the value is not the default or check <clinit> is not present.
-    if (accessFlags.isStatic() && accessFlags.isPublic() && accessFlags.isFinal()) {
-      DexClass clazz = appInfo.definitionFor(field.getHolder());
+    boolean isEffectivelyFinal =
+        (accessFlags.isFinal() || !appInfo.isFieldWritten(field))
+            && !appInfo.isPinned(field);
+    if (!isEffectivelyFinal) {
+      return null;
+    }
+    if (accessFlags.isStatic()) {
+      DexClass clazz = appInfo.definitionFor(field.clazz);
       assert clazz != null : "Class for the field must be present";
       return getStaticValue().asConstInstruction(clazz.hasClassInitializer(), dest);
     }
     return null;
   }
 
-  public DexEncodedField toRenamedField(DexString name, DexItemFactory dexItemFactory) {
-    return new DexEncodedField(dexItemFactory.createField(field.clazz, field.type, name),
-        accessFlags, annotations, staticValue);
-  }
-
   public DexEncodedField toTypeSubstitutedField(DexField field) {
     if (this.field == field) {
       return this;
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 8bdf115..8cd38d0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -298,6 +298,7 @@
   public final DexType metafactoryType = createType("Ljava/lang/invoke/LambdaMetafactory;");
   public final DexType callSiteType = createType("Ljava/lang/invoke/CallSite;");
   public final DexType lookupType = createType("Ljava/lang/invoke/MethodHandles$Lookup;");
+  public final DexType iteratorType = createType("Ljava/util/Iterator;");
   public final DexType serializableType = createType("Ljava/io/Serializable;");
   public final DexType externalizableType = createType("Ljava/io/Externalizable;");
   public final DexType comparableType = createType("Ljava/lang/Comparable;");
@@ -348,6 +349,9 @@
           createString("makeConcat")
       );
 
+  public final Set<DexType> libraryTypesWithoutStaticInitialization =
+      ImmutableSet.of(iteratorType, serializableType);
+
   private boolean skipNameValidationForTesting = false;
 
   public void setSkipNameValidationForTesting(boolean skipNameValidationForTesting) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 9182811..1a880f6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -10,10 +10,13 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.desugar.Java8MethodRewriter;
+import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
@@ -121,11 +124,25 @@
     return implementedInterfaces(appInfo).contains(appInfo.dexItemFactory.serializableType);
   }
 
+  public boolean classInitializationMayHaveSideEffects(AppInfo appInfo) {
+    return classInitializationMayHaveSideEffects(appInfo, Predicates.alwaysFalse());
+  }
+
   public boolean classInitializationMayHaveSideEffects(AppInfo appInfo, Predicate<DexType> ignore) {
     DexClass clazz = appInfo.definitionFor(this);
     return clazz == null || clazz.classInitializationMayHaveSideEffects(appInfo, ignore);
   }
 
+  public boolean initializationOfParentTypesMayHaveSideEffects(AppInfo appInfo) {
+    return initializationOfParentTypesMayHaveSideEffects(appInfo, Predicates.alwaysFalse());
+  }
+
+  public boolean initializationOfParentTypesMayHaveSideEffects(
+      AppInfo appInfo, Predicate<DexType> ignore) {
+    DexClass clazz = appInfo.definitionFor(this);
+    return clazz == null || clazz.initializationOfParentTypesMayHaveSideEffects(appInfo, ignore);
+  }
+
   public boolean isUnknown() {
     return hierarchyLevel == UNKNOWN_LEVEL;
   }
@@ -453,7 +470,9 @@
         || name.contains(DISPATCH_CLASS_NAME_SUFFIX)
         || name.contains(LAMBDA_CLASS_NAME_PREFIX)
         || name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX)
-        || name.contains(OutlineOptions.CLASS_NAME);
+        || name.contains(OutlineOptions.CLASS_NAME)
+        || name.contains(TwrCloseResourceRewriter.UTILITY_CLASS_NAME)
+        || name.contains(Java8MethodRewriter.UTILITY_CLASS_NAME_PREFIX);
   }
 
   public int elementSizeForPrimitiveArrayType() {
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index bac74f6..402b56d 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -388,9 +388,7 @@
   public abstract DexMethod getRenamedMethodSignature(DexMethod originalMethod);
 
   public DexEncodedMethod mapDexEncodedMethod(
-      DexEncodedMethod originalEncodedMethod,
-      AppInfo appInfo,
-      Map<DexType, DexProgramClass> synthesizedClasses) {
+      DexEncodedMethod originalEncodedMethod, AppInfo appInfo) {
     DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.method);
     // Note that:
     // * Even if `newMethod` is the same as `originalEncodedMethod.method`, we still need to look it
@@ -398,14 +396,7 @@
     // * We can't directly use AppInfo#definitionFor(DexMethod) since definitions may not be
     //   updated either yet.
     DexClass newHolder = appInfo.definitionFor(newMethod.holder);
-
-    // TODO(b/120130831): Need to ensure that all synthesized classes are part of the application.
-    if (newHolder == null) {
-      newHolder = synthesizedClasses.get(newMethod.holder);
-    }
-
     assert newHolder != null;
-
     DexEncodedMethod newEncodedMethod = newHolder.lookupMethod(newMethod);
     assert newEncodedMethod != null;
     return newEncodedMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index f961b80..649d1af 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -662,13 +662,9 @@
           }
           // After the throwing instruction only debug instructions and the final jump
           // instruction is allowed.
-          // TODO(ager): For now allow const instructions due to the way consts are pushed
-          // towards their use
           if (seenThrowing) {
             assert instruction.isDebugInstruction()
                 || instruction.isJumpInstruction()
-                || instruction.isConstInstruction()
-                || instruction.isNewArrayFilledData()
                 || instruction.isStore()
                 || instruction.isPop();
           }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java
index b98662c..cdb40bf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java
@@ -19,10 +19,7 @@
 
   @Override
   public boolean hasNext() {
-    if (instructionIterator.hasNext()) {
-      return true;
-    }
-    return blockIterator.hasNext();
+    return instructionIterator.hasNext() || blockIterator.hasNext();
   }
 
   @Override
@@ -39,6 +36,35 @@
   }
 
   @Override
+  public boolean hasPrevious() {
+    return instructionIterator.hasPrevious() || blockIterator.hasPrevious();
+  }
+
+  @Override
+  public Instruction previous() {
+    if (instructionIterator.hasPrevious()) {
+      return instructionIterator.previous();
+    }
+    if (!blockIterator.hasPrevious()) {
+      throw new NoSuchElementException();
+    }
+    BasicBlock block = blockIterator.previous();
+    instructionIterator = block.listIterator(block.getInstructions().size());
+    assert instructionIterator.hasPrevious();
+    return instructionIterator.previous();
+  }
+
+  @Override
+  public int nextIndex() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public int previousIndex() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
   public void add(Instruction instruction) {
     instructionIterator.add(instruction);
   }
@@ -49,6 +75,11 @@
   }
 
   @Override
+  public void set(Instruction instruction) {
+    instructionIterator.set(instruction);
+  }
+
+  @Override
   public void replaceCurrentInstruction(Instruction newInstruction) {
     instructionIterator.replaceCurrentInstruction(newInstruction);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java
index 2f2b3f1..72aa3ee 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java
@@ -4,7 +4,10 @@
 
 package com.android.tools.r8.ir.code;
 
-public interface InstructionIterator extends NextUntilIterator<Instruction> {
+import java.util.ListIterator;
+
+public interface InstructionIterator
+    extends ListIterator<Instruction>, NextUntilIterator<Instruction> {
   /**
    * Replace the current instruction (aka the {@link Instruction} returned by the previous call to
    * {@link #next} with the passed in <code>newInstruction</code>.
@@ -22,15 +25,6 @@
    */
   void replaceCurrentInstruction(Instruction newInstruction);
 
-  /**
-   * Adds an instruction. The instruction will be added just before the current
-   * cursor position.
-   *
-   * The instruction will be assigned to the block it is added to.
-   *
-   * @param instruction The instruction to add.
-   */
-  void add(Instruction instruction);
 
   /**
    * Safe removal function that will insert a DebugLocalRead to take over the debug values if any
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index a5e5d78..c232d3f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -11,9 +11,7 @@
 import java.util.ListIterator;
 
 public interface InstructionListIterator
-    extends ListIterator<Instruction>,
-        NextUntilIterator<Instruction>,
-        PreviousUntilIterator<Instruction> {
+    extends InstructionIterator, PreviousUntilIterator<Instruction> {
 
   /**
    * Peek the previous instruction.
@@ -50,27 +48,6 @@
   }
 
   /**
-   * Safe removal function that will insert a DebugLocalRead to take over the debug values if any
-   * are associated with the current instruction.
-   */
-  void removeOrReplaceByDebugLocalRead();
-
-  /**
-   * Replace the current instruction (aka the {@link Instruction} returned by the previous call to
-   * {@link #next} with the passed in <code>newInstruction</code>.
-   *
-   * The current instruction will be completely detached from the instruction stream with uses
-   * of its in-values removed.
-   *
-   * If the current instruction produces an out-value the new instruction must also produce
-   * an out-value, and all uses of the current instructions out-value will be replaced by the
-   * new instructions out-value.
-   *
-   * @param newInstruction the instruction to insert instead of the current.
-   */
-  void replaceCurrentInstruction(Instruction newInstruction);
-
-  /**
    * Split the block into two blocks at the point of the {@link ListIterator} cursor. The existing
    * block will have all the instructions before the cursor, and the new block all the
    * instructions after the cursor.
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 0343e8a..edd33c8 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
@@ -89,12 +89,10 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -149,10 +147,6 @@
   private final OptimizationFeedback simpleOptimizationFeedback = new OptimizationFeedbackSimple();
   private DexString highestSortingString;
 
-  // For some optimizations, e.g. optimizing synthetic classes, we may need to resolve
-  // the current class being optimized.
-  private ConcurrentHashMap<DexType, DexProgramClass> cachedClasses = new ConcurrentHashMap<>();
-
   // The argument `appView` is only available when full program optimizations are allowed
   // (i.e., when running R8).
   private IRConverter(
@@ -352,30 +346,24 @@
       InterfaceMethodRewriter.Flavor includeAllResources,
       ExecutorService executorService)
       throws ExecutionException {
-    desugarInterfaceMethods(builder, includeAllResources, executorService, null);
-  }
-
-  private void desugarInterfaceMethods(
-      Builder<?> builder,
-      InterfaceMethodRewriter.Flavor includeAllResources,
-      ExecutorService executorService,
-      Map<DexType, DexProgramClass> synthesizedClasses)
-      throws ExecutionException {
     if (interfaceMethodRewriter != null) {
       interfaceMethodRewriter.desugarInterfaceMethods(
-          builder, includeAllResources, executorService, synthesizedClasses);
+          builder, includeAllResources, executorService);
     }
   }
 
-  private void synthesizeTwrCloseResourceUtilityClass(Builder<?> builder) {
+  private void synthesizeTwrCloseResourceUtilityClass(
+      Builder<?> builder, ExecutorService executorService)
+      throws ExecutionException {
     if (twrCloseResourceRewriter != null) {
-      twrCloseResourceRewriter.synthesizeUtilityClass(builder, options);
+      twrCloseResourceRewriter.synthesizeUtilityClass(builder, executorService, options);
     }
   }
 
-  private void synthesizeJava8UtilityClass(Builder<?> builder) {
+  private void synthesizeJava8UtilityClass(
+      Builder<?> builder, ExecutorService executorService) throws ExecutionException {
     if (java8MethodRewriter != null) {
-      java8MethodRewriter.synthesizeUtilityClass(builder, options);
+      java8MethodRewriter.synthesizeUtilityClass(builder, executorService, options);
     }
   }
 
@@ -398,8 +386,8 @@
 
     synthesizeLambdaClasses(builder, executor);
     desugarInterfaceMethods(builder, ExcludeDexResources, executor);
-    synthesizeTwrCloseResourceUtilityClass(builder);
-    synthesizeJava8UtilityClass(builder);
+    synthesizeTwrCloseResourceUtilityClass(builder, executor);
+    synthesizeJava8UtilityClass(builder, executor);
     processCovariantReturnTypeAnnotations(builder);
 
     handleSynthesizedClassMapping(builder);
@@ -590,21 +578,20 @@
     synthesizeLambdaClasses(builder, executorService);
 
     printPhase("Interface method desugaring");
-    Map<DexType, DexProgramClass> synthesizedClasses = new IdentityHashMap<>();
-    desugarInterfaceMethods(builder, IncludeAllResources, executorService, synthesizedClasses);
+    desugarInterfaceMethods(builder, IncludeAllResources, executorService);
 
     printPhase("Twr close resource utility class synthesis");
-    synthesizeTwrCloseResourceUtilityClass(builder);
-    synthesizeJava8UtilityClass(builder);
+    synthesizeTwrCloseResourceUtilityClass(builder, executorService);
+    synthesizeJava8UtilityClass(builder, executorService);
     handleSynthesizedClassMapping(builder);
 
     printPhase("Lambda merging finalization");
-    finalizeLambdaMerging(application, feedback, builder, executorService, synthesizedClasses);
+    finalizeLambdaMerging(application, feedback, builder, executorService);
 
     if (outliner != null) {
       printPhase("Outlining");
       timing.begin("IR conversion phase 2");
-      if (outliner.selectMethodsForOutlining(synthesizedClasses)) {
+      if (outliner.selectMethodsForOutlining()) {
         forEachSelectedOutliningMethod(
             executorService,
             (code, method) -> {
@@ -612,7 +599,8 @@
               outliner.identifyOutlineSites(code, method);
             });
         DexProgramClass outlineClass = outliner.buildOutlinerClass(computeOutlineClassType());
-        optimizeSynthesizedClass(outlineClass);
+        appInfo.addSynthesizedClass(outlineClass);
+        optimizeSynthesizedClass(outlineClass, executorService);
         forEachSelectedOutliningMethod(
             executorService,
             (code, method) -> {
@@ -636,6 +624,12 @@
       uninstantiatedTypeOptimization.logResults();
     }
 
+    // Check if what we've added to the application builder as synthesized classes are same as
+    // what we've added and used through AppInfo.
+    assert appInfo.getSynthesizedClassesForSanityCheck()
+            .containsAll(builder.getSynthesizedClasses())
+        && builder.getSynthesizedClasses()
+            .containsAll(appInfo.getSynthesizedClassesForSanityCheck());
     return builder.build();
   }
 
@@ -687,14 +681,13 @@
 
   private void finalizeLambdaMerging(
       DexApplication application,
-      OptimizationFeedback directFeedback,
+      OptimizationFeedback feedback,
       Builder<?> builder,
-      ExecutorService executorService,
-      Map<DexType, DexProgramClass> synthesizedClasses)
+      ExecutorService executorService)
       throws ExecutionException {
     if (lambdaMerger != null) {
       lambdaMerger.applyLambdaClassMapping(
-          application, this, directFeedback, builder, executorService, synthesizedClasses);
+          application, this, feedback, builder, executorService);
     }
   }
 
@@ -744,49 +737,24 @@
     return result;
   }
 
-  public DexClass definitionFor(DexType type) {
-    DexProgramClass cached = cachedClasses.get(type);
-    return cached != null ? cached : appInfo.definitionFor(type);
-  }
-
-  public void optimizeSynthesizedClass(DexProgramClass clazz) {
-    try {
-      enterCachedClass(clazz);
-      // Process the generated class, but don't apply any outlining.
-      clazz.forEachMethod(this::optimizeSynthesizedMethod);
-    } finally {
-      leaveCachedClass(clazz);
-    }
+  public void optimizeSynthesizedClass(
+      DexProgramClass clazz, ExecutorService executorService)
+      throws ExecutionException {
+    Set<DexEncodedMethod> methods = Sets.newIdentityHashSet();
+    clazz.forEachMethod(methods::add);
+    // Process the generated class, but don't apply any outlining.
+    optimizeSynthesizedMethodsConcurrently(methods, executorService);
   }
 
   public void optimizeSynthesizedClasses(
       Collection<DexProgramClass> classes, ExecutorService executorService)
       throws ExecutionException {
     Set<DexEncodedMethod> methods = Sets.newIdentityHashSet();
-    try {
-      for (DexProgramClass clazz : classes) {
-        enterCachedClass(clazz);
-        clazz.forEachMethod(methods::add);
-      }
-      // Process the generated class, but don't apply any outlining.
-      optimizeSynthesizedMethods(methods, executorService);
-    } finally {
-      for (DexProgramClass clazz : classes) {
-        leaveCachedClass(clazz);
-      }
+    for (DexProgramClass clazz : classes) {
+      clazz.forEachMethod(methods::add);
     }
-  }
-
-  public void optimizeMethodOnSynthesizedClass(DexProgramClass clazz, DexEncodedMethod method) {
-    if (!method.isProcessed()) {
-      try {
-        enterCachedClass(clazz);
-        // Process the generated method, but don't apply any outlining.
-        optimizeSynthesizedMethod(method);
-      } finally {
-        leaveCachedClass(clazz);
-      }
-    }
+    // Process the generated class, but don't apply any outlining.
+    optimizeSynthesizedMethodsConcurrently(methods, executorService);
   }
 
   public void optimizeSynthesizedMethod(DexEncodedMethod method) {
@@ -801,7 +769,7 @@
     }
   }
 
-  public void optimizeSynthesizedMethods(
+  public void optimizeSynthesizedMethodsConcurrently(
       Collection<DexEncodedMethod> methods, ExecutorService executorService)
       throws ExecutionException {
     List<Future<?>> futures = new ArrayList<>();
@@ -821,16 +789,6 @@
     ThreadUtils.awaitFutures(futures);
   }
 
-  private void enterCachedClass(DexProgramClass clazz) {
-    DexProgramClass previous = cachedClasses.put(clazz.type, clazz);
-    assert previous == null;
-  }
-
-  private void leaveCachedClass(DexProgramClass clazz) {
-    DexProgramClass existing = cachedClasses.remove(clazz.type);
-    assert existing == clazz;
-  }
-
   private String logCode(InternalOptions options, DexEncodedMethod method) {
     return options.useSmaliSyntax ? method.toSmaliString(null) : method.codeToString();
   }
@@ -1191,7 +1149,8 @@
       // original method signature (this could have changed as a result of, for example, class
       // merging). Then, we find the type that now corresponds to the the original holder.
       DexMethod originalSignature = graphLense().getOriginalMethodSignature(method.method);
-      DexClass originalHolder = definitionFor(graphLense().lookupType(originalSignature.holder));
+      DexClass originalHolder = appInfo.definitionFor(
+          graphLense().lookupType(originalSignature.holder));
       if (originalHolder.hasKotlinInfo()) {
         KotlinInfo kotlinInfo = originalHolder.getKotlinInfo();
         if (kotlinInfo.hasNonNullParameterHints()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 7c22bbf..fd968d6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -210,7 +210,15 @@
             iterator.replaceCurrentInstruction(newInvoke);
 
             if (constantReturnMaterializingInstruction != null) {
-              iterator.add(constantReturnMaterializingInstruction);
+              if (block.hasCatchHandlers()) {
+                // Split the block to ensure no instructions after throwing instructions.
+                iterator
+                    .split(code, blocks)
+                    .listIterator()
+                    .add(constantReturnMaterializingInstruction);
+              } else {
+                iterator.add(constantReturnMaterializingInstruction);
+              }
             }
 
             DexType actualReturnType = actualTarget.proto.returnType;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 968b200..a27b096 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -60,7 +60,7 @@
     DexType superType = clazz.superType;
     // If superClass definition is missing, just skip this part and let real processing of its
     // subclasses report the error if it is required.
-    DexClass superClass = superType == null ? null : rewriter.findDefinitionFor(superType);
+    DexClass superClass = superType == null ? null : rewriter.appInfo.definitionFor(superType);
     if (superClass != null && superType != rewriter.factory.objectType) {
       if (superClass.isInterface()) {
         throw new CompilationError("Interface `" + superClass.toSourceString()
@@ -96,10 +96,10 @@
 
   private DexEncodedMethod addForwardingMethod(DexEncodedMethod defaultMethod, DexClass clazz) {
     DexMethod method = defaultMethod.method;
+    DexClass target = rewriter.appInfo.definitionFor(method.holder);
     // NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
     // even if this results in invalid code, these classes are never desugared.
-    assert rewriter.findDefinitionFor(method.holder) != null
-        && !rewriter.findDefinitionFor(method.holder).isLibraryClass();
+    assert target != null && !target.isLibraryClass();
     // New method will have the same name, proto, and also all the flags of the
     // default method, including bridge flag.
     DexMethod newMethod = rewriter.factory.createMethod(clazz.type, method.proto, method.name);
@@ -167,7 +167,7 @@
       if (current.superType == null) {
         break;
       } else {
-        DexClass superClass = rewriter.findDefinitionFor(current.superType);
+        DexClass superClass = rewriter.appInfo.definitionFor(current.superType);
         if (superClass != null) {
           current = superClass;
         } else {
@@ -205,7 +205,7 @@
       DexType superType = current.superType;
       DexClass superClass = null;
       if (superType != null) {
-        superClass = rewriter.findDefinitionFor(superType);
+        superClass = rewriter.appInfo.definitionFor(superType);
         // It's available or we would have failed while analyzing the hierarchy for interfaces.
         assert superClass != null;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index e79017d..abd38a5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication.Builder;
@@ -80,6 +81,7 @@
   public static final String PRIVATE_METHOD_PREFIX = "$private$";
 
   private final AppView<? extends AppInfoWithLiveness> appView;
+  final AppInfo appInfo;
   private final IRConverter converter;
   private final InternalOptions options;
   final DexItemFactory factory;
@@ -121,6 +123,7 @@
     assert converter != null;
     this.appView = appView;
     this.converter = converter;
+    this.appInfo = converter.appInfo;
     this.options = options;
     this.factory = options.itemFactory;
   }
@@ -156,7 +159,7 @@
         if (instruction.isInvokeStatic()) {
           InvokeStatic invokeStatic = instruction.asInvokeStatic();
           DexMethod method = invokeStatic.getInvokedMethod();
-          DexClass clazz = findDefinitionFor(method.holder);
+          DexClass clazz = appInfo.definitionFor(method.holder);
           if (Java8MethodRewriter.hasJava8MethodRewritePrefix(method.holder)) {
             // We did not create this code yet, but it will not require rewriting.
             continue;
@@ -187,7 +190,7 @@
                         invokeStatic.outValue(), invokeStatic.arguments()));
                 requiredDispatchClasses
                     .computeIfAbsent(clazz.asLibraryClass(), k -> Sets.newConcurrentHashSet())
-                    .add(findDefinitionFor(encodedMethod.method.holder).asProgramClass());
+                    .add(appInfo.definitionFor(encodedMethod.method.holder).asProgramClass());
               }
             } else {
               instructions.replaceCurrentInstruction(
@@ -201,7 +204,7 @@
         if (instruction.isInvokeSuper()) {
           InvokeSuper invokeSuper = instruction.asInvokeSuper();
           DexMethod method = invokeSuper.getInvokedMethod();
-          DexClass clazz = findDefinitionFor(method.holder);
+          DexClass clazz = appInfo.definitionFor(method.holder);
           if (clazz == null) {
             // NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
             // exception but we can not report it as error since it can also be the intended
@@ -218,7 +221,7 @@
             // WARNING: This may result in incorrect code on older platforms!
             // Retarget call to an appropriate method of companion class.
             DexMethod amendedMethod = amendDefaultMethod(
-                findDefinitionFor(encodedMethod.method.holder), method);
+                appInfo.definitionFor(encodedMethod.method.holder), method);
             instructions.replaceCurrentInstruction(
                 new InvokeStatic(defaultAsMethodOfCompanionClass(amendedMethod),
                     invokeSuper.outValue(), invokeSuper.arguments()));
@@ -233,7 +236,7 @@
             continue;
           }
 
-          DexClass clazz = findDefinitionFor(method.holder);
+          DexClass clazz = appInfo.definitionFor(method.holder);
           if (clazz == null) {
             // Report missing class since we don't know if it is an interface.
             warnMissingType(encodedMethod.method, method.holder);
@@ -279,7 +282,7 @@
 
   private void reportStaticInterfaceMethodHandle(DexMethod referencedFrom, DexMethodHandle handle) {
     if (handle.type.isInvokeStatic()) {
-      DexClass holderClass = findDefinitionFor(handle.asMethod().holder);
+      DexClass holderClass = appInfo.definitionFor(handle.asMethod().holder);
       // NOTE: If the class definition is missing we can't check. Let it be handled as any other
       // missing call target.
       if (holderClass == null) {
@@ -292,15 +295,6 @@
     }
   }
 
-  /**
-   * Returns the class definition for the specified type.
-   *
-   * @return may return null if no definition for the given type is available.
-   */
-  final DexClass findDefinitionFor(DexType type) {
-    return converter.definitionFor(type);
-  }
-
   // Gets the companion class for the interface `type`.
   final DexType getCompanionClassType(DexType type) {
     assert type.isClassType();
@@ -334,7 +328,7 @@
   }
 
   private boolean isInMainDexList(DexType iface) {
-    return converter.appInfo.isInMainDexList(iface);
+    return appInfo.isInMainDexList(iface);
   }
 
   // Represent a static interface method as a method of companion class.
@@ -393,8 +387,7 @@
   public void desugarInterfaceMethods(
       Builder<?> builder,
       Flavor flavour,
-      ExecutorService executorService,
-      Map<DexType, DexProgramClass> synthesizedClasses)
+      ExecutorService executorService)
       throws ExecutionException {
     // Process all classes first. Add missing forwarding methods to
     // replace desugared default interface methods.
@@ -409,13 +402,10 @@
       // are just moved from interfaces and don't need to be re-processed.
       DexProgramClass synthesizedClass = entry.getValue();
       builder.addSynthesizedClass(synthesizedClass, isInMainDexList(entry.getKey()));
-
-      if (synthesizedClasses != null) {
-        synthesizedClasses.put(synthesizedClass.type, synthesizedClass);
-      }
+      appInfo.addSynthesizedClass(synthesizedClass);
     }
 
-    converter.optimizeSynthesizedMethods(synthesizedMethods, executorService);
+    converter.optimizeSynthesizedMethodsConcurrently(synthesizedMethods, executorService);
 
     // Cached data is not needed any more.
     clear();
@@ -528,7 +518,7 @@
     if (isCompanionClassType(holder)) {
       holder = getInterfaceClassType(holder);
     }
-    DexClass clazz = converter.appInfo.definitionFor(holder);
+    DexClass clazz = appInfo.definitionFor(holder);
     return clazz == null ? Origin.unknown() : clazz.getOrigin();
   }
 
@@ -550,7 +540,7 @@
       DexClass implementing,
       DexType iface) {
     DefaultMethodsHelper helper = new DefaultMethodsHelper();
-    DexClass definedInterface = findDefinitionFor(iface);
+    DexClass definedInterface = appInfo.definitionFor(iface);
     if (definedInterface == null) {
       warnMissingInterface(classToDesugar, implementing, iface);
       return helper.wrapInCollection();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 9d5e5fc..58ec9ed 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -322,7 +322,7 @@
         if (!seenBefore.add(superType)) {
           continue;
         }
-        DexClass clazz = rewriter.findDefinitionFor(superType);
+        DexClass clazz = rewriter.appInfo.definitionFor(superType);
         if (clazz != null) {
           if (clazz.lookupVirtualMethod(method.method) != null) {
             return false;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
index 9e2e9ab..e930bcf 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
@@ -37,9 +37,12 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.BiFunction;
 
 public final class Java8MethodRewriter {
+  public static final String UTILITY_CLASS_NAME_PREFIX = "$r8$java8methods$utility";
   private static final String UTILITY_CLASS_DESCRIPTOR_PREFIX = "L$r8$java8methods$utility";
   private final Set<DexType> holders = Sets.newConcurrentHashSet();
   private final IRConverter converter;
@@ -88,7 +91,9 @@
     return clazz.descriptor.toString().startsWith(UTILITY_CLASS_DESCRIPTOR_PREFIX);
   }
 
-  public void synthesizeUtilityClass(Builder<?> builder, InternalOptions options) {
+  public void synthesizeUtilityClass(
+      Builder<?> builder, ExecutorService executorService, InternalOptions options)
+      throws ExecutionException {
     if (holders.isEmpty()) {
       return;
     }
@@ -134,7 +139,8 @@
       code.setUpContext(utilityClass);
       boolean addToMainDexList = referencingClasses.stream()
           .anyMatch(clazz -> converter.appInfo.isInMainDexList(clazz.type));
-      converter.optimizeSynthesizedClass(utilityClass);
+      converter.appInfo.addSynthesizedClass(utilityClass);
+      converter.optimizeSynthesizedClass(utilityClass, executorService);
       builder.addSynthesizedClass(utilityClass, addToMainDexList);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 865a62c..cff5392 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -153,8 +153,9 @@
             synthesizeVirtualMethods(mainMethod),
             rewriter.factory.getSkipNameValidationForTesting());
     // Optimize main method.
-    rewriter.converter.optimizeMethodOnSynthesizedClass(
-        clazz, clazz.lookupVirtualMethod(mainMethod));
+    rewriter.converter.appInfo.addSynthesizedClass(clazz);
+    rewriter.converter.optimizeSynthesizedMethod(clazz.lookupVirtualMethod(mainMethod));
+
     // The method addSynthesizedFrom() may be called concurrently. To avoid a Concurrent-
     // ModificationException we must use synchronization.
     synchronized (synthesizedFrom) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index ade6feb..d0faabc 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -180,14 +180,15 @@
   /** Generates lambda classes and adds them to the builder. */
   public void synthesizeLambdaClasses(Builder<?> builder, ExecutorService executorService)
       throws ExecutionException {
+    for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
+      DexProgramClass synthesizedClass = lambdaClass.getLambdaClass();
+      appInfo.addSynthesizedClass(synthesizedClass);
+      builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
+    }
     converter.optimizeSynthesizedClasses(
         knownLambdaClasses.values().stream()
             .map(LambdaClass::getLambdaClass).collect(ImmutableSet.toImmutableSet()),
         executorService);
-    for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
-      DexProgramClass synthesizedClass = lambdaClass.getLambdaClass();
-      builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
-    }
   }
 
   public Set<DexCallSite> getDesugaredCallSites() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index e1e534e..c06e158 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -31,6 +31,8 @@
 import java.lang.reflect.Method;
 import java.util.Collections;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 
 // Try with resources outlining processor. Handles $closeResource methods
 // synthesized by java 9 compiler.
@@ -45,6 +47,7 @@
 // tree shaking to remove them since now they should not be referenced.
 //
 public final class TwrCloseResourceRewriter {
+  public static final String UTILITY_CLASS_NAME = "$r8$twr$utility";
   public static final String UTILITY_CLASS_DESCRIPTOR = "L$r8$twr$utility;";
 
   private final IRConverter converter;
@@ -107,7 +110,9 @@
         && original.proto == converter.appInfo.dexItemFactory.twrCloseResourceMethodProto;
   }
 
-  public void synthesizeUtilityClass(Builder<?> builder, InternalOptions options) {
+  public void synthesizeUtilityClass(
+      Builder<?> builder, ExecutorService executorService, InternalOptions options)
+      throws ExecutionException {
     if (referencingClasses.isEmpty()) {
       return;
     }
@@ -144,7 +149,8 @@
     // Process created class and method.
     boolean addToMainDexList = referencingClasses.stream()
         .anyMatch(clazz -> converter.appInfo.isInMainDexList(clazz.type));
-    converter.optimizeSynthesizedClass(utilityClass);
+    converter.appInfo.addSynthesizedClass(utilityClass);
+    converter.optimizeSynthesizedClass(utilityClass, executorService);
     builder.addSynthesizedClass(utilityClass, addToMainDexList);
   }
 
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 a07410c..e723a55 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
@@ -1843,7 +1843,7 @@
       return;
     }
 
-    DexClass clazz = definitionFor(method.method.getHolder());
+    DexClass clazz = appInfo.definitionFor(method.method.getHolder());
     if (clazz == null) {
       return;
     }
@@ -2044,10 +2044,6 @@
     }
   }
 
-  DexClass definitionFor(DexType type) {
-    return converter.definitionFor(type);
-  }
-
   public void removeTrivialCheckCastAndInstanceOfInstructions(
       IRCode code, boolean enableWholeProgramOptimizations) {
     if (!enableWholeProgramOptimizations) {
@@ -2153,7 +2149,7 @@
     if (baseType.isPrimitiveType()) {
       return false;
     }
-    DexClass clazz = definitionFor(baseType);
+    DexClass clazz = appInfo.definitionFor(baseType);
     if (clazz == null) {
       // Conservatively say yes.
       return true;
@@ -3502,7 +3498,7 @@
       if (type == dexItemFactory.throwableType) {
         return true;
       }
-      DexClass dexClass = definitionFor(type);
+      DexClass dexClass = appInfo.definitionFor(type);
       if (dexClass == null) {
         throw new CompilationError("Class or interface " + type.toSourceString() +
             " required for desugaring of try-with-resources is not found.");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 434bb04..7ebc21c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -17,18 +17,20 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardMemberRule;
 import com.google.common.collect.Sets;
+import java.util.ListIterator;
 import java.util.Set;
 import java.util.function.Predicate;
 
@@ -114,174 +116,207 @@
     }
   }
 
-  private void replaceInstructionFromProguardRule(RuleType ruleType, InstructionIterator iterator,
-      Instruction current, Instruction replacement) {
-    if (ruleType == RuleType.ASSUME_NO_SIDE_EFFECTS) {
+  private boolean tryConstantReplacementFromProguard(
+      IRCode code,
+      Set<Value> affectedValues,
+      ListIterator<BasicBlock> blocks,
+      InstructionListIterator iterator,
+      Instruction current,
+      ProguardMemberRuleLookup lookup) {
+    Instruction replacement = constantReplacementFromProguardRule(lookup.rule, code, current);
+    if (replacement == null) {
+      // Check to see if a value range can be assumed.
+      setValueRangeFromProguardRule(lookup.rule, current.outValue());
+      return false;
+    }
+    affectedValues.add(replacement.outValue());
+    if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS) {
       iterator.replaceCurrentInstruction(replacement);
     } else {
+      assert lookup.type == RuleType.ASSUME_VALUES;
       if (current.outValue() != null) {
         assert replacement.outValue() != null;
         current.outValue().replaceUsers(replacement.outValue());
       }
       replacement.setPosition(current.getPosition());
-      iterator.add(replacement);
+      if (current.getBlock().hasCatchHandlers()) {
+        iterator.split(code, blocks).listIterator().add(replacement);
+      } else {
+        iterator.add(replacement);
+      }
+    }
+    return true;
+  }
+
+  private void rewriteInvokeMethodWithConstantValues(
+      IRCode code,
+      DexType callingContext,
+      Set<Value> affectedValues,
+      ListIterator<BasicBlock> blocks,
+      InstructionListIterator iterator,
+      InvokeMethod current) {
+    DexMethod invokedMethod = current.getInvokedMethod();
+    DexType invokedHolder = invokedMethod.getHolder();
+    if (!invokedHolder.isClassType()) {
+      return;
+    }
+    // TODO(70550443): Maybe check all methods here.
+    DexEncodedMethod definition = appInfo.lookup(current.getType(), invokedMethod, callingContext);
+    ProguardMemberRuleLookup lookup = lookupMemberRule(definition);
+    boolean invokeReplaced = false;
+    if (lookup != null) {
+      boolean outValueNullOrNotUsed = current.outValue() == null || !current.outValue().isUsed();
+      if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS && outValueNullOrNotUsed) {
+        // Remove invoke if marked as having no side effects and the return value is not used.
+        iterator.removeOrReplaceByDebugLocalRead();
+        invokeReplaced = true;
+      } else if (!outValueNullOrNotUsed) {
+        // Check to see if a constant value can be assumed.
+        invokeReplaced =
+            tryConstantReplacementFromProguard(
+                code, affectedValues, blocks, iterator, current, lookup);
+      }
+    }
+    if (invokeReplaced || current.outValue() == null) {
+      return;
+    }
+    // No Proguard rule could replace the instruction check for knowledge about the return value.
+    DexEncodedMethod target = current.lookupSingleTarget(appInfo, callingContext);
+    if (target == null) {
+      return;
+    }
+    if (target.getOptimizationInfo().neverReturnsNull() && current.outValue().canBeNull()) {
+      Value knownToBeNonNullValue = current.outValue();
+      knownToBeNonNullValue.markNeverNull();
+      TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
+      assert typeLattice.isNullable() && typeLattice.isReference();
+      knownToBeNonNullValue.narrowing(appInfo, typeLattice.asNonNullable());
+      affectedValues.addAll(knownToBeNonNullValue.affectedValues());
+    }
+    if (target.getOptimizationInfo().returnsConstant()) {
+      long constant = target.getOptimizationInfo().getReturnedConstant();
+      ConstNumber replacement =
+          createConstNumberReplacement(
+              code, constant, current.outValue().getTypeLattice(), current.getLocalInfo());
+      affectedValues.add(replacement.outValue());
+      current.outValue().replaceUsers(replacement.outValue());
+      current.setOutValue(null);
+      replacement.setPosition(current.getPosition());
+      current.moveDebugValues(replacement);
+      if (current.getBlock().hasCatchHandlers()) {
+        iterator.split(code, blocks).listIterator().add(replacement);
+      } else {
+        iterator.add(replacement);
+      }
+    }
+  }
+
+  private void rewriteStaticGetWithConstantValues(
+      IRCode code,
+      Predicate<DexEncodedMethod> isProcessedConcurrently,
+      Set<Value> affectedValues,
+      ListIterator<BasicBlock> blocks,
+      InstructionListIterator iterator,
+      StaticGet current) {
+    DexField field = current.getField();
+
+    // TODO(b/123857022): Should be able to use definitionFor().
+    DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
+    if (target == null) {
+      return;
+    }
+    // Check if a this value is known const.
+    Instruction replacement = target.valueAsConstInstruction(appInfo, current.dest());
+    if (replacement != null) {
+      affectedValues.add(replacement.outValue());
+      iterator.replaceCurrentInstruction(replacement);
+      return;
+    }
+    ProguardMemberRuleLookup lookup = lookupMemberRule(target);
+    if (lookup != null
+        && lookup.type == RuleType.ASSUME_VALUES
+        && tryConstantReplacementFromProguard(
+            code, affectedValues, blocks, iterator, current, lookup)) {
+      return;
+    }
+    if (current.dest() != null) {
+      // In case the class holder of this static field satisfying following criteria:
+      //   -- cannot trigger other static initializer except for its own
+      //   -- is final
+      //   -- has a class initializer which is classified as trivial
+      //      (see CodeRewriter::computeClassInitializerInfo) and
+      //      initializes the field being accessed
+      //
+      // ... and the field itself is not pinned by keep rules (in which case it might
+      // be updated outside the class constructor, e.g. via reflections), it is safe
+      // to assume that the static-get instruction reads the value it initialized value
+      // in class initializer and is never null.
+      DexClass holderDefinition = appInfo.definitionFor(field.getHolder());
+      if (holderDefinition != null
+          && holderDefinition.accessFlags.isFinal()
+          && !field.getHolder().initializationOfParentTypesMayHaveSideEffects(appInfo)) {
+        Value outValue = current.dest();
+        DexEncodedMethod classInitializer = holderDefinition.getClassInitializer();
+        if (classInitializer != null && !isProcessedConcurrently.test(classInitializer)) {
+          TrivialInitializer info =
+              classInitializer.getOptimizationInfo().getTrivialInitializerInfo();
+          if (info != null
+              && ((TrivialClassInitializer) info).field == field
+              && !appInfo.isPinned(field)
+              && outValue.canBeNull()) {
+            outValue.markNeverNull();
+            TypeLatticeElement typeLattice = outValue.getTypeLattice();
+            assert typeLattice.isNullable() && typeLattice.isReference();
+            outValue.narrowing(appInfo, typeLattice.asNonNullable());
+            affectedValues.addAll(outValue.affectedValues());
+          }
+        }
+      }
+    }
+  }
+
+  private void rewritePutWithConstantValues(
+      InstructionIterator iterator, FieldInstruction current) {
+    DexField field = current.getField();
+    // TODO(b/123857022): Should be possible to use definitionFor().
+    DexEncodedField target =
+        current.isInstancePut()
+            ? appInfo.lookupInstanceTarget(field.getHolder(), field)
+            : appInfo.lookupStaticTarget(field.getHolder(), field);
+    // TODO(b/123857022): Should be possible to use `!isFieldRead(field)`.
+    if (target != null && !appInfo.isFieldRead(target.field)) {
+      // Remove writes to dead (i.e. never read) fields.
+      iterator.removeOrReplaceByDebugLocalRead();
     }
   }
 
   /**
    * Replace invoke targets and field accesses with constant values where possible.
-   * <p>
-   * Also assigns value ranges to values where possible.
+   *
+   * <p>Also assigns value ranges to values where possible.
    */
   public void rewriteWithConstantValues(
       IRCode code, DexType callingContext, Predicate<DexEncodedMethod> isProcessedConcurrently) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
-    InstructionIterator iterator = code.instructionIterator();
-    while (iterator.hasNext()) {
-      Instruction current = iterator.next();
-      if (current.isInvokeMethod()) {
-        InvokeMethod invoke = current.asInvokeMethod();
-        DexMethod invokedMethod = invoke.getInvokedMethod();
-        DexType invokedHolder = invokedMethod.getHolder();
-        if (!invokedHolder.isClassType()) {
-          continue;
-        }
-        // TODO(70550443): Maybe check all methods here.
-        DexEncodedMethod definition = appInfo
-            .lookup(invoke.getType(), invokedMethod, callingContext);
-
-        boolean invokeReplaced = false;
-        ProguardMemberRuleLookup lookup = lookupMemberRule(definition);
-        if (lookup != null) {
-          if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS
-              && (invoke.outValue() == null || !invoke.outValue().isUsed())) {
-            // Remove invoke if marked as having no side effects and the return value is not used.
-            iterator.remove();
-            invokeReplaced = true;
-          } else if (invoke.outValue() != null && invoke.outValue().isUsed()) {
-            // Check to see if a constant value can be assumed.
-            Instruction replacement =
-                constantReplacementFromProguardRule(lookup.rule, code, invoke);
-            if (replacement != null) {
-              affectedValues.add(replacement.outValue());
-              replaceInstructionFromProguardRule(lookup.type, iterator, current, replacement);
-              invokeReplaced = true;
-            } else {
-              // Check to see if a value range can be assumed.
-              setValueRangeFromProguardRule(lookup.rule, current.outValue());
-            }
-          }
-        }
-
-        // If no Proguard rule could replace the instruction check for knowledge about the
-        // return value.
-        if (!invokeReplaced && invoke.outValue() != null) {
-          DexEncodedMethod target = invoke.lookupSingleTarget(appInfo, callingContext);
-          if (target != null) {
-            if (target.getOptimizationInfo().neverReturnsNull() && invoke.outValue().canBeNull()) {
-              Value knownToBeNonNullValue = invoke.outValue();
-              knownToBeNonNullValue.markNeverNull();
-              TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
-              assert typeLattice.isNullable() && typeLattice.isReference();
-              knownToBeNonNullValue.narrowing(appInfo, typeLattice.asNonNullable());
-              affectedValues.addAll(knownToBeNonNullValue.affectedValues());
-            }
-            if (target.getOptimizationInfo().returnsConstant()) {
-              long constant = target.getOptimizationInfo().getReturnedConstant();
-              ConstNumber replacement = createConstNumberReplacement(
-                  code, constant, invoke.outValue().getTypeLattice(), invoke.getLocalInfo());
-              affectedValues.add(replacement.outValue());
-              invoke.outValue().replaceUsers(replacement.outValue());
-              invoke.setOutValue(null);
-              replacement.setPosition(invoke.getPosition());
-              invoke.moveDebugValues(replacement);
-              iterator.add(replacement);
-            }
-          }
-        }
-      } else if (current.isInstancePut()) {
-        InstancePut instancePut = current.asInstancePut();
-        DexField field = instancePut.getField();
-        DexEncodedField target = appInfo.lookupInstanceTarget(field.getHolder(), field);
-        if (target != null) {
-          // Remove writes to dead (i.e. never read) fields.
-          if (!isFieldRead(target, false) && instancePut.object().isNeverNull()) {
-            iterator.remove();
-          }
-        }
-      } else if (current.isStaticGet()) {
-        StaticGet staticGet = current.asStaticGet();
-        DexField field = staticGet.getField();
-        DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
-        ProguardMemberRuleLookup lookup = null;
-        if (target != null) {
-          // Check if a this value is known const.
-          Instruction replacement = target.valueAsConstInstruction(appInfo, staticGet.dest());
-          if (replacement == null) {
-            lookup = lookupMemberRule(target);
-            if (lookup != null) {
-              replacement = constantReplacementFromProguardRule(lookup.rule, code, staticGet);
-            }
-          }
-          if (replacement == null) {
-            // If no const replacement was found, at least store the range information.
-            if (lookup != null) {
-              setValueRangeFromProguardRule(lookup.rule, staticGet.dest());
-            }
-          }
-          if (replacement != null) {
-            affectedValues.add(replacement.outValue());
-            // Ignore assumenosideeffects for fields.
-            if (lookup != null && lookup.type == RuleType.ASSUME_VALUES) {
-              replaceInstructionFromProguardRule(lookup.type, iterator, current, replacement);
-            } else {
-              iterator.replaceCurrentInstruction(replacement);
-            }
-          } else if (staticGet.dest() != null) {
-            // In case the class holder of this static field satisfying following criteria:
-            //   -- cannot trigger other static initializer except for its own
-            //   -- is final
-            //   -- has a class initializer which is classified as trivial
-            //      (see CodeRewriter::computeClassInitializerInfo) and
-            //      initializes the field being accessed
-            //
-            // ... and the field itself is not pinned by keep rules (in which case it might
-            // be updated outside the class constructor, e.g. via reflections), it is safe
-            // to assume that the static-get instruction reads the value it initialized value
-            // in class initializer and is never null.
-            //
-            DexClass holderDefinition = appInfo.definitionFor(field.getHolder());
-            if (holderDefinition != null
-                && holderDefinition.accessFlags.isFinal()
-                && !appInfo.canTriggerStaticInitializer(field.getHolder(), true)) {
-              Value outValue = staticGet.dest();
-              DexEncodedMethod classInitializer = holderDefinition.getClassInitializer();
-              if (classInitializer != null && !isProcessedConcurrently.test(classInitializer)) {
-                TrivialInitializer info =
-                    classInitializer.getOptimizationInfo().getTrivialInitializerInfo();
-                if (info != null
-                    && ((TrivialClassInitializer) info).field == field
-                    && !appInfo.isPinned(field)
-                    && outValue.canBeNull()) {
-                  outValue.markNeverNull();
-                  TypeLatticeElement typeLattice = outValue.getTypeLattice();
-                  assert typeLattice.isNullable() && typeLattice.isReference();
-                  outValue.narrowing(appInfo, typeLattice.asNonNullable());
-                  affectedValues.addAll(outValue.affectedValues());
-                }
-              }
-            }
-          }
-        }
-      } else if (current.isStaticPut()) {
-        StaticPut staticPut = current.asStaticPut();
-        DexField field = staticPut.getField();
-        DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
-        if (target != null) {
-          // Remove writes to dead (i.e. never read) fields.
-          if (!isFieldRead(target, true)) {
-            iterator.removeOrReplaceByDebugLocalRead();
-          }
+    ListIterator<BasicBlock> blocks = code.blocks.listIterator();
+    while (blocks.hasNext()) {
+      BasicBlock block = blocks.next();
+      InstructionListIterator iterator = block.listIterator();
+      while (iterator.hasNext()) {
+        Instruction current = iterator.next();
+        if (current.isInvokeMethod()) {
+          rewriteInvokeMethodWithConstantValues(
+              code, callingContext, affectedValues, blocks, iterator, current.asInvokeMethod());
+        } else if (current.isInstancePut() || current.isStaticPut()) {
+          rewritePutWithConstantValues(iterator, current.asFieldInstruction());
+        } else if (current.isStaticGet()) {
+          rewriteStaticGetWithConstantValues(
+              code,
+              isProcessedConcurrently,
+              affectedValues,
+              blocks,
+              iterator,
+              current.asStaticGet());
         }
       }
     }
@@ -290,14 +325,4 @@
     }
     assert code.isConsistentSSA();
   }
-
-  private boolean isFieldRead(DexEncodedField field, boolean isStatic) {
-    if (appInfo.fieldsRead.contains(field.field)
-        || appInfo.isPinned(field.field)) {
-      return true;
-    }
-    // For library classes we don't know whether a field is read.
-    DexClass holder = appInfo.definitionFor(field.field.clazz);
-    return holder == null || holder.isLibraryClass();
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 87b2537..452f4ed 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -1228,7 +1228,7 @@
     }
   }
 
-  public boolean selectMethodsForOutlining(Map<DexType, DexProgramClass> synthesizedClasses) {
+  public boolean selectMethodsForOutlining() {
     assert methodsSelectedForOutlining.size() == 0;
     assert outlineSites.size() == 0;
     for (List<DexEncodedMethod> outlineMethods : candidateMethodLists) {
@@ -1237,7 +1237,7 @@
           methodsSelectedForOutlining.add(
               converter
                   .graphLense()
-                  .mapDexEncodedMethod(outlineMethod, appInfo, synthesizedClasses));
+                  .mapDexEncodedMethod(outlineMethod, appInfo));
         }
       }
     }
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 2972ffd..1f4c1c3 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
@@ -234,6 +234,6 @@
     }
 
     // Check for static initializers in this class or any of interfaces it implements.
-    return !appInfo.canTriggerStaticInitializer(clazz, true);
+    return !clazz.initializationOfParentTypesMayHaveSideEffects(appInfo);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index cdf37c4..d6ca0e0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -116,12 +116,14 @@
     if (!eligibleClass.isClassType()) {
       return false;
     }
-    eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
-    if (eligibleClassDefinition == null && lambdaRewriter != null) {
+    if (lambdaRewriter != null) {
       // Check if the class is synthesized for a desugared lambda
       eligibleClassDefinition = lambdaRewriter.getLambdaClass(eligibleClass);
       isDesugaredLambda = eligibleClassDefinition != null;
     }
+    if (eligibleClassDefinition == null) {
+      eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
+    }
     return eligibleClassDefinition != null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index b988a89..ab6f635 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.ir.conversion.CallSiteInformation;
 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.Outliner;
 import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
@@ -36,6 +37,7 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import com.google.common.base.Predicates;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
@@ -194,8 +196,7 @@
       IRConverter converter,
       OptimizationFeedback feedback,
       Builder<?> builder,
-      ExecutorService executorService,
-      Map<DexType, DexProgramClass> synthesizedClasses)
+      ExecutorService executorService)
       throws ExecutionException {
     if (lambdas.isEmpty()) {
       return;
@@ -218,18 +219,29 @@
     this.strategyFactory = ApplyStrategy::new;
 
     // Add synthesized lambda group classes to the builder.
-    converter.optimizeSynthesizedClasses(lambdaGroupsClasses.values(), executorService);
 
     for (Entry<LambdaGroup, DexProgramClass> entry : lambdaGroupsClasses.entrySet()) {
       DexProgramClass synthesizedClass = entry.getValue();
-      synthesizedClasses.put(synthesizedClass.type, synthesizedClass);
+      converter.appInfo.addSynthesizedClass(synthesizedClass);
       builder.addSynthesizedClass(
           synthesizedClass, entry.getKey().shouldAddToMainDex(converter.appInfo));
+      // Eventually, we need to process synthesized methods in the lambda group.
+      // Otherwise, abstract SynthesizedCode will be flown to Enqueuer.
+      // But that process should not see the holder. Otherwise, lambda calls in the main dispatch
+      // method became recursive calls via the lense rewriter. They should remain, then inliner
+      // will inline methods from mergee lambdas to the main dispatch method.
+      // Then, there is a dilemma: other sub optimizations trigger subtype lookup that will throw
+      // NPE if it cannot find the holder for this synthesized lambda group.
+      // One hack here is to mark those methods `processed` so that the lense rewriter is skipped.
+      synthesizedClass.forEachMethod(encodedMethod -> {
+        encodedMethod.markProcessed(ConstraintWithTarget.NEVER);
+      });
     }
+    converter.optimizeSynthesizedClasses(lambdaGroupsClasses.values(), executorService);
 
     // Rewrite lambda class references into lambda group class
     // references inside methods from the processing queue.
-    rewriteLambdaReferences(converter, synthesizedClasses, feedback);
+    rewriteLambdaReferences(converter, feedback);
     this.strategyFactory = null;
   }
 
@@ -304,10 +316,7 @@
     }
   }
 
-  private void rewriteLambdaReferences(
-      IRConverter converter,
-      Map<DexType, DexProgramClass> synthesizedClasses,
-      OptimizationFeedback feedback) {
+  private void rewriteLambdaReferences(IRConverter converter, OptimizationFeedback feedback) {
     List<DexEncodedMethod> methods =
         methodsToReprocess
             .stream()
@@ -315,9 +324,9 @@
             .collect(Collectors.toList());
     for (DexEncodedMethod method : methods) {
       DexEncodedMethod mappedMethod =
-          converter.graphLense().mapDexEncodedMethod(method, converter.appInfo, synthesizedClasses);
+          converter.graphLense().mapDexEncodedMethod(method, converter.appInfo);
       converter.processMethod(mappedMethod, feedback,
-          x -> false, CallSiteInformation.empty(), Outliner::noProcessing);
+          Predicates.alwaysFalse(), CallSiteInformation.empty(), Outliner::noProcessing);
       assert mappedMethod.isProcessed();
     }
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index d08f3e6..e4d2cec 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1980,11 +1980,11 @@
     /**
      * Set of all fields which may be touched by a get operation. This is actual field definitions.
      */
-    public final SortedSet<DexField> fieldsRead;
+    private final SortedSet<DexField> fieldsRead;
     /**
      * Set of all fields which may be touched by a put operation. This is actual field definitions.
      */
-    public final SortedSet<DexField> fieldsWritten;
+    private final SortedSet<DexField> fieldsWritten;
     /**
      * Set of all field ids used in instance field reads, along with access context.
      */
@@ -2354,7 +2354,8 @@
 
     public boolean isInstantiatedDirectly(DexType type) {
       assert type.isClassType();
-      return instantiatedTypes.contains(type)
+      return type.isD8R8SynthesizedClassType()
+          || instantiatedTypes.contains(type)
           || instantiatedLambdas.contains(type)
           || instantiatedAnnotationTypes.contains(type);
     }
@@ -2381,6 +2382,31 @@
       return isInstantiatedDirectly(type) || isInstantiatedIndirectly(type);
     }
 
+    public boolean isFieldRead(DexField field) {
+      return fieldsRead.contains(field)
+          // TODO(b/121354886): Pinned fields should be in `fieldsRead`.
+          || isPinned(field)
+          // Fields in the class that is synthesized by D8/R8 would be used soon.
+          || field.getHolder().isD8R8SynthesizedClassType()
+          // For library classes we don't know whether a field is read.
+          || isLibraryField(field);
+    }
+
+    public boolean isFieldWritten(DexField field) {
+      return fieldsWritten.contains(field)
+          // TODO(b/121354886): Pinned fields should be in `fieldsWritten`.
+          || isPinned(field)
+          // Fields in the class that is synthesized by D8/R8 would be used soon.
+          || field.clazz.isD8R8SynthesizedClassType()
+          // For library classes we don't know whether a field is rewritten.
+          || isLibraryField(field);
+    }
+
+    private boolean isLibraryField(DexField field) {
+      DexClass holder = definitionFor(field.clazz);
+      return holder == null || holder.isLibraryClass();
+    }
+
     private Object2BooleanMap<DexReference> joinIdentifierNameStrings(
         Set<DexReference> explicit, Set<DexReference> implicit) {
       Object2BooleanMap<DexReference> result = new Object2BooleanArrayMap<>();
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index acc1e78..8017878 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -228,8 +228,8 @@
     Predicate<DexField> isReachableOrReferencedField =
         field ->
             appInfo.liveFields.contains(field)
-                || appInfo.fieldsRead.contains(field)
-                || appInfo.fieldsWritten.contains(field);
+                || appInfo.isFieldRead(field)
+                || appInfo.isFieldWritten(field);
     int firstUnreachable =
         firstUnreachableIndex(Arrays.asList(fields), isReachableOrReferencedField);
     // Return the original array if all fields are used.
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/IllegalFieldRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/IllegalFieldRebindingTest.java
new file mode 100644
index 0000000..cc35cbd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/IllegalFieldRebindingTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2019, 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.memberrebinding;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.memberrebinding.testclasses.IllegalFieldRebindingTestClasses;
+import com.android.tools.r8.memberrebinding.testclasses.IllegalFieldRebindingTestClasses.B;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IllegalFieldRebindingTest extends TestBase {
+
+  private final Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public IllegalFieldRebindingTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test() throws Exception {
+    String expectedOutput = StringUtils.lines("42");
+
+    if (backend == Backend.CF) {
+      testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+    }
+
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addInnerClasses(IllegalFieldRebindingTest.class)
+            .addInnerClasses(IllegalFieldRebindingTestClasses.class)
+            .addKeepMainRule(TestClass.class)
+            .enableMergeAnnotations()
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    ClassSubject classSubject = inspector.clazz(TestClass.class);
+    assertThat(classSubject, isPresent());
+
+    MethodSubject methodSubject = classSubject.mainMethod();
+    assertThat(methodSubject, isPresent());
+
+    // Verify that the static-put instruction has not been removed.
+    assertEquals(
+        1, methodSubject.streamInstructions().filter(InstructionSubject::isStaticPut).count());
+  }
+
+  @Test
+  public void otherTest() throws Exception {
+    String expectedOutput = StringUtils.lines("0");
+
+    if (backend == Backend.CF) {
+      testForJvm()
+          .addTestClasspath()
+          .run(OtherTestClass.class)
+          .assertSuccessWithOutput(expectedOutput);
+    }
+
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addInnerClasses(IllegalFieldRebindingTest.class)
+            .addInnerClasses(IllegalFieldRebindingTestClasses.class)
+            .addKeepMainRule(OtherTestClass.class)
+            .enableMergeAnnotations()
+            .run(OtherTestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    // The static-get instruction in OtherTestClass.main() should have been replaced by the
+    // constant 0, which makes the remaining classes unreachable.
+    assertEquals(1, inspector.allClasses().size());
+
+    ClassSubject classSubject = inspector.clazz(OtherTestClass.class);
+    assertThat(classSubject, isPresent());
+
+    MethodSubject methodSubject = classSubject.mainMethod();
+    assertThat(methodSubject, isPresent());
+
+    // Verify that a constant 0 is being loaded in main().
+    assertEquals(
+        1,
+        methodSubject
+            .streamInstructions()
+            .filter(instruction -> instruction.isConstNumber(0))
+            .count());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      // Cannot be member rebound because A is inaccessible to this package. However, the
+      // instruction cannot be removed as the field `A.f` is actually read.
+      B.f = 42;
+      B.print();
+    }
+  }
+
+  static class OtherTestClass {
+
+    public static void main(String[] args) {
+      // Cannot be member rebound because A is inaccessible to this package.
+      // However, the instruction should still be replaced by the constant 0.
+      System.out.println(B.f);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/testclasses/IllegalFieldRebindingTestClasses.java b/src/test/java/com/android/tools/r8/memberrebinding/testclasses/IllegalFieldRebindingTestClasses.java
new file mode 100644
index 0000000..64b4d1f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/testclasses/IllegalFieldRebindingTestClasses.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2019, 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.memberrebinding.testclasses;
+
+import com.android.tools.r8.NeverMerge;
+
+public class IllegalFieldRebindingTestClasses {
+
+  @NeverMerge
+  static class A {
+    public static int f;
+  }
+
+  public static class B extends A {
+
+    public static void print() {
+      System.out.println(A.f);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
index d8631ee..6731209 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
@@ -287,8 +287,7 @@
         mainClass,
         ImmutableList.of(mainClass, StaticFieldNotInitialized.class),
         keepMainProguardConfiguration(mainClass),
-        // TODO(74379749): Proguard does not keep the class with the un-initialized static field.
-        this::checkAllClassesPresentOnlyMainWithDefaultConstructor,
+        this::checkOnlyMainPresent,
         this::checkOnlyMainPresent);
   }