Merge commit 'e3cdcb7d' into dev-release
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
index 24e49d1..5b6de6e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
@@ -31,5 +31,6 @@
     public static final String DESCRIPTOR = getDescriptor(CLASS);
     public static final String classConstant = "classConstant";
     public static final String methodName = "methodName";
+    public static final String fieldName = "fieldName";
   }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
index e20f288..f87e32c 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
@@ -16,4 +16,6 @@
   String classTypeName() default "";
 
   String methodName() default "";
+
+  String fieldName() default "";
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
index 029ec75..cc6e4ee 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.ast.KeepEdgeException;
+import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern.Builder;
 import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
@@ -143,6 +145,7 @@
     private final Parent<KeepTarget> parent;
     private KeepQualifiedClassNamePattern classNamePattern = null;
     private KeepMethodNamePattern methodName = null;
+    private KeepFieldNamePattern fieldName = null;
 
     public KeepTargetVisitor(Parent<KeepTarget> parent) {
       this.parent = parent;
@@ -158,6 +161,10 @@
         methodName = KeepMethodNamePattern.exact((String) value);
         return;
       }
+      if (name.equals(Target.fieldName) && value instanceof String) {
+        fieldName = KeepFieldNamePattern.exact((String) value);
+        return;
+      }
       super.visit(name, value);
     }
 
@@ -167,10 +174,16 @@
       if (classNamePattern != null) {
         itemBuilder.setClassPattern(classNamePattern);
       }
+      if (methodName != null && fieldName != null) {
+        throw new KeepEdgeException("Cannot define both a field and a method pattern.");
+      }
       if (methodName != null) {
         itemBuilder.setMemberPattern(
             KeepMethodPattern.builder().setNamePattern(methodName).build());
       }
+      if (fieldName != null) {
+        itemBuilder.setMemberPattern(KeepFieldPattern.builder().setNamePattern(fieldName).build());
+      }
       KeepTarget target = KeepTarget.builder().setItem(itemBuilder.build()).build();
       parent.accept(target);
     }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
index 8307265..0a0ccc8 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
@@ -7,6 +7,9 @@
 import com.android.tools.r8.keepanno.annotations.KeepConstants.Target;
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
+import com.android.tools.r8.keepanno.ast.KeepEdgeException;
+import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern.KeepFieldNameExactPattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern.KeepMethodNameExactPattern;
@@ -84,7 +87,31 @@
     if (memberPattern.isAll()) {
       throw new Unimplemented();
     }
-    KeepMethodPattern method = memberPattern.asMethod();
+    if (memberPattern.isMethod()) {
+      writeMethod(memberPattern.asMethod(), targetVisitor);
+    } else if (memberPattern.isField()) {
+      writeField(memberPattern.asField(), targetVisitor);
+    } else {
+      throw new KeepEdgeException("Unexpected member pattern: " + memberPattern);
+    }
+  }
+
+  private void writeField(KeepFieldPattern field, AnnotationVisitor targetVisitor) {
+    KeepFieldNameExactPattern exactFieldName = field.getNamePattern().asExact();
+    if (exactFieldName != null) {
+      targetVisitor.visit(Target.fieldName, exactFieldName.getName());
+    } else {
+      throw new Unimplemented();
+    }
+    if (!field.getAccessPattern().isAny()) {
+      throw new Unimplemented();
+    }
+    if (!field.getTypePattern().isAny()) {
+      throw new Unimplemented();
+    }
+  }
+
+  private void writeMethod(KeepMethodPattern method, AnnotationVisitor targetVisitor) {
     KeepMethodNameExactPattern exactMethodName = method.getNamePattern().asExact();
     if (exactMethodName != null) {
       targetVisitor.visit(Target.methodName, exactMethodName.getName());
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
index 89be76b..b2da25a 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
@@ -39,7 +39,12 @@
  *   UNQUALIFIED_CLASS_NAME_PATTERN ::= any | exact simple-class-name
  *   EXTENDS_PATTERN ::= any | QUALIFIED_CLASS_NAME_PATTERN
  *
- *   MEMBER_PATTERN ::= none | all | METHOD_PATTERN
+ *   MEMBER_PATTERN ::= none | all | FIELD_PATTERN | METHOD_PATTERN
+ *
+ *   FIELD_PATTERN
+ *     ::= FIELD_ACCESS_PATTERN
+ *           FIELD_TYPE_PATTERN
+ *           FIELD_NAME_PATTERN;
  *
  *   METHOD_PATTERN
  *     ::= METHOD_ACCESS_PATTERN
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldAccessPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldAccessPattern.java
new file mode 100644
index 0000000..056430b
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldAccessPattern.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2022, 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.keepanno.ast;
+
+// TODO: finish this.
+public abstract class KeepFieldAccessPattern {
+
+  public static KeepFieldAccessPattern any() {
+    return Any.getInstance();
+  }
+
+  public abstract boolean isAny();
+
+  private static class Any extends KeepFieldAccessPattern {
+
+    private static final Any INSTANCE = new Any();
+
+    private static Any getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    public boolean isAny() {
+      return true;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      return this == obj;
+    }
+
+    @Override
+    public int hashCode() {
+      return System.identityHashCode(this);
+    }
+
+    @Override
+    public String toString() {
+      return "*";
+    }
+  }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldNamePattern.java
new file mode 100644
index 0000000..3067966
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldNamePattern.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2022, 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.keepanno.ast;
+
+public abstract class KeepFieldNamePattern {
+
+  public static KeepFieldNamePattern any() {
+    return Any.getInstance();
+  }
+
+  public static KeepFieldNamePattern exact(String methodName) {
+    return new KeepFieldNameExactPattern(methodName);
+  }
+
+  private KeepFieldNamePattern() {}
+
+  public boolean isAny() {
+    return false;
+  }
+
+  public final boolean isExact() {
+    return asExact() != null;
+  }
+
+  public KeepFieldNameExactPattern asExact() {
+    return null;
+  }
+
+  private static class Any extends KeepFieldNamePattern {
+    private static final Any INSTANCE = new Any();
+
+    public static Any getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    public boolean isAny() {
+      return true;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      return this == obj;
+    }
+
+    @Override
+    public int hashCode() {
+      return System.identityHashCode(this);
+    }
+
+    @Override
+    public String toString() {
+      return "*";
+    }
+  }
+
+  public static class KeepFieldNameExactPattern extends KeepFieldNamePattern {
+    private final String name;
+
+    public KeepFieldNameExactPattern(String name) {
+      assert name != null;
+      this.name = name;
+    }
+
+    @Override
+    public KeepFieldNameExactPattern asExact() {
+      return this;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      KeepFieldNameExactPattern that = (KeepFieldNameExactPattern) o;
+      return name.equals(that.name);
+    }
+
+    @Override
+    public int hashCode() {
+      return name.hashCode();
+    }
+
+    @Override
+    public String toString() {
+      return name;
+    }
+  }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java
new file mode 100644
index 0000000..7137033
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2022, 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.keepanno.ast;
+
+import java.util.Objects;
+
+public final class KeepFieldPattern extends KeepMemberPattern {
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+
+    private KeepFieldAccessPattern accessPattern = KeepFieldAccessPattern.any();
+    private KeepFieldNamePattern namePattern = null;
+    private KeepFieldTypePattern typePattern = KeepFieldTypePattern.any();
+
+    private Builder() {}
+
+    public Builder self() {
+      return this;
+    }
+
+    public Builder setAccessPattern(KeepFieldAccessPattern accessPattern) {
+      this.accessPattern = accessPattern;
+      return self();
+    }
+
+    public Builder setNamePattern(KeepFieldNamePattern namePattern) {
+      this.namePattern = namePattern;
+      return self();
+    }
+
+    public Builder setTypePattern(KeepFieldTypePattern typePattern) {
+      this.typePattern = typePattern;
+      return self();
+    }
+
+    public KeepFieldPattern build() {
+      if (namePattern == null) {
+        throw new KeepEdgeException("Field pattern must declare a name pattern");
+      }
+      return new KeepFieldPattern(accessPattern, namePattern, typePattern);
+    }
+  }
+
+  private final KeepFieldAccessPattern accessPattern;
+  private final KeepFieldNamePattern namePattern;
+  private final KeepFieldTypePattern typePattern;
+
+  private KeepFieldPattern(
+      KeepFieldAccessPattern accessPattern,
+      KeepFieldNamePattern namePattern,
+      KeepFieldTypePattern typePattern) {
+    assert accessPattern != null;
+    assert namePattern != null;
+    assert typePattern != null;
+    this.accessPattern = accessPattern;
+    this.namePattern = namePattern;
+    this.typePattern = typePattern;
+  }
+
+  @Override
+  public KeepFieldPattern asField() {
+    return this;
+  }
+
+  public boolean isAnyField() {
+    return accessPattern.isAny() && namePattern.isAny() && typePattern.isAny();
+  }
+
+  public KeepFieldAccessPattern getAccessPattern() {
+    return accessPattern;
+  }
+
+  public KeepFieldNamePattern getNamePattern() {
+    return namePattern;
+  }
+
+  public KeepFieldTypePattern getTypePattern() {
+    return typePattern;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    KeepFieldPattern that = (KeepFieldPattern) o;
+    return accessPattern.equals(that.accessPattern)
+        && namePattern.equals(that.namePattern)
+        && typePattern.equals(that.typePattern);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(accessPattern, namePattern, typePattern);
+  }
+
+  @Override
+  public String toString() {
+    return "KeepFieldPattern{"
+        + "access="
+        + accessPattern
+        + ", name="
+        + namePattern
+        + ", type="
+        + typePattern
+        + '}';
+  }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldTypePattern.java
new file mode 100644
index 0000000..beaaa55
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldTypePattern.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2022, 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.keepanno.ast;
+
+public abstract class KeepFieldTypePattern {
+
+  public static KeepFieldTypePattern any() {
+    return SomeType.ANY_TYPE_INSTANCE;
+  }
+
+  public boolean isAny() {
+    return isType() && asType().isAny();
+  }
+
+  public boolean isType() {
+    return asType() != null;
+  }
+
+  public KeepTypePattern asType() {
+    return null;
+  }
+
+  private static class SomeType extends KeepFieldTypePattern {
+
+    private static final SomeType ANY_TYPE_INSTANCE = new SomeType(KeepTypePattern.any());
+
+    private final KeepTypePattern typePattern;
+
+    private SomeType(KeepTypePattern typePattern) {
+      assert typePattern != null;
+      this.typePattern = typePattern;
+    }
+
+    @Override
+    public KeepTypePattern asType() {
+      return typePattern;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      SomeType someType = (SomeType) o;
+      return typePattern.equals(someType.typePattern);
+    }
+
+    @Override
+    public int hashCode() {
+      return typePattern.hashCode();
+    }
+
+    @Override
+    public String toString() {
+      return typePattern.toString();
+    }
+  }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
index 25a57ab..b0f205e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
@@ -88,4 +88,12 @@
   public KeepMethodPattern asMethod() {
     return null;
   }
+
+  public final boolean isField() {
+    return asField() != null;
+  }
+
+  public KeepFieldPattern asField() {
+    return null;
+  }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
index 7c8c032..6781a22 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
@@ -5,6 +5,9 @@
 
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
+import com.android.tools.r8.keepanno.ast.KeepFieldAccessPattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodAccessPattern;
@@ -114,9 +117,25 @@
       printMethod(builder.append(' '), member.asMethod());
       return builder.append(" }");
     }
+    if (member.isField()) {
+      builder.append(" {");
+      printField(builder.append(' '), member.asField());
+      return builder.append(" }");
+    }
     throw new Unimplemented();
   }
 
+  private static StringBuilder printField(StringBuilder builder, KeepFieldPattern fieldPattern) {
+    if (fieldPattern.isAnyField()) {
+      return builder.append("<fields>;");
+    }
+    printAccess(builder, " ", fieldPattern.getAccessPattern());
+    printType(builder, fieldPattern.getTypePattern().asType());
+    builder.append(' ');
+    printFieldName(builder, fieldPattern.getNamePattern());
+    return builder.append(';');
+  }
+
   private static StringBuilder printMethod(StringBuilder builder, KeepMethodPattern methodPattern) {
     if (methodPattern.isAnyMethod()) {
       return builder.append("<methods>;");
@@ -143,6 +162,13 @@
         .append(')');
   }
 
+  private static StringBuilder printFieldName(
+      StringBuilder builder, KeepFieldNamePattern namePattern) {
+    return namePattern.isAny()
+        ? builder.append("*")
+        : builder.append(namePattern.asExact().getName());
+  }
+
   private static StringBuilder printMethodName(
       StringBuilder builder, KeepMethodNamePattern namePattern) {
     return namePattern.isAny()
@@ -175,6 +201,16 @@
     throw new Unimplemented();
   }
 
+  private static StringBuilder printAccess(
+      StringBuilder builder, String indent, KeepFieldAccessPattern accessPattern) {
+    if (accessPattern.isAny()) {
+      // No text will match any access pattern.
+      // Don't print the indent in this case.
+      return builder;
+    }
+    throw new Unimplemented();
+  }
+
   private static StringBuilder printClassName(
       StringBuilder builder, KeepQualifiedClassNamePattern classNamePattern) {
     if (classNamePattern.isAny()) {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java b/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
index c86857d..18d3932 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
@@ -16,6 +16,8 @@
 import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.ast.KeepEdge.Builder;
 import com.android.tools.r8.keepanno.ast.KeepEdgeException;
+import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
@@ -148,14 +150,21 @@
       itemBuilder.setClassPattern(KeepQualifiedClassNamePattern.exact(typeName));
     }
     AnnotationValue methodNameValue = getAnnotationValue(mirror, Target.methodName);
+    AnnotationValue fieldNameValue = getAnnotationValue(mirror, Target.fieldName);
+    if (methodNameValue != null && fieldNameValue != null) {
+      throw new KeepEdgeException("Cannot define both a method and a field name pattern");
+    }
     if (methodNameValue != null) {
       String methodName = AnnotationStringValueVisitor.getString(methodNameValue);
       itemBuilder.setMemberPattern(
           KeepMethodPattern.builder()
               .setNamePattern(KeepMethodNamePattern.exact(methodName))
               .build());
+    } else if (fieldNameValue != null) {
+      String fieldName = AnnotationStringValueVisitor.getString(fieldNameValue);
+      itemBuilder.setMemberPattern(
+          KeepFieldPattern.builder().setNamePattern(KeepFieldNamePattern.exact(fieldName)).build());
     }
-
     builder.setItem(itemBuilder.build());
   }
 
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index 3295333..dcd5329 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -616,6 +616,7 @@
     "-keepattributes EnclosingMethod",
     "-keepattributes InnerClasses",
     "-dontwarn sun.misc.Unsafe",
-    "-dontwarn wrapper.**"
+    "-dontwarn wrapper.**",
+    "-dontwarn sun.nio.fs.UnixFileSystemProvider"
   ]
 }
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
index 4649132..4864e0b 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -233,7 +233,8 @@
     // We cannot reliably create a stub that will have the same throwing
     // behavior for all VMs. We only create stubs for exceptions to allow them being present in
     // catch handlers. See b/b/258270051 for more information.
-    if (!isThrowable(libraryClass)) {
+    if (!isThrowable(libraryClass)
+        || appView.options().apiModelingOptions().stubNonThrowableClasses) {
       return;
     }
     if (appView
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 63d7d65..43e7099 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -397,6 +397,23 @@
     return true;
   }
 
+  private static class PrunePreambleMethodVisitor extends MethodVisitor {
+
+    private boolean inPreamble = true;
+
+    public PrunePreambleMethodVisitor(MethodVisitor methodVisitor) {
+      super(InternalOptions.ASM_VERSION, methodVisitor);
+    }
+
+    @Override
+    public void visitLineNumber(int line, Label start) {
+      if (!inPreamble || line != 0) {
+        inPreamble = false;
+        super.visitLineNumber(line, start);
+      }
+    }
+  }
+
   @Override
   public void writeCf(
       ProgramMethod method,
@@ -422,12 +439,20 @@
             || (appView.enableWholeProgramOptimizations()
                 && classFileVersion.isEqualTo(CfVersion.V1_6)
                 && !options.shouldKeepStackMapTable());
+    PrunePreambleMethodVisitor prunePreambleVisitor = new PrunePreambleMethodVisitor(visitor);
     for (CfInstruction instruction : instructions) {
       if (discardFrames && instruction instanceof CfFrame) {
         continue;
       }
       instruction.write(
-          appView, method, dexItemFactory, graphLens, initClassLens, namingLens, rewriter, visitor);
+          appView,
+          method,
+          dexItemFactory,
+          graphLens,
+          initClassLens,
+          namingLens,
+          rewriter,
+          prunePreambleVisitor);
     }
     visitor.visitEnd();
     visitor.visitMaxs(maxStack, maxLocals);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java b/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
index e901e23..d274600 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
@@ -4,12 +4,10 @@
 
 package com.android.tools.r8.ir.analysis.equivalence;
 
-import static com.google.common.base.Predicates.not;
-import static com.google.common.base.Predicates.or;
-
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -23,8 +21,10 @@
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.WorkList;
 import java.util.List;
 import java.util.Set;
+import java.util.function.Predicate;
 
 /**
  * Analysis that can be used to determine if the behavior of one basic block is subsumed by the
@@ -45,19 +45,59 @@
     this.context = code.context();
   }
 
-  public boolean isSubsumedBy(BasicBlock block, BasicBlock other) {
-    return isSubsumedBy(block.iterator(), other.iterator(), null);
+  public boolean isSubsumedBy(Value conditionValue, BasicBlock block, BasicBlock other) {
+    return isSubsumedBy(conditionValue, block.iterator(), other.iterator(), null);
+  }
+
+  private boolean dependsOnConditionValue(Value conditionValue, Instruction instruction) {
+    if (instruction.isAssume()) {
+      // Assume instructions are just virtual alias instructions so they are not materializing uses.
+      return false;
+    }
+    WorkList<Assume> assumptionUses = null;
+    for (Value value : instruction.inValues()) {
+      Assume assumption = value.isPhi() ? null : value.getDefinition().asAssume();
+      if (assumption != null) {
+        if (assumptionUses == null) {
+          assumptionUses = WorkList.newIdentityWorkList();
+        }
+        assumptionUses.addIfNotSeen(assumption);
+      }
+    }
+    if (assumptionUses != null) {
+      while (assumptionUses.hasNext()) {
+        Assume next = assumptionUses.next();
+        for (Value assumptionArgument : next.inValues()) {
+          if (assumptionArgument == conditionValue) {
+            return true;
+          }
+          Assume indirectAssumption =
+              assumptionArgument.isPhi() ? null : assumptionArgument.getDefinition().asAssume();
+          if (indirectAssumption != null) {
+            assumptionUses.addIfNotSeen(indirectAssumption);
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  private Instruction skipNonDependentInstructionsUntil(
+      InstructionIterator iterator, Value conditionValue, Predicate<Instruction> predicate) {
+    return iterator.nextUntil(
+        predicate.or(i -> i.isJumpInstruction() || dependsOnConditionValue(conditionValue, i)));
   }
 
   private boolean isSubsumedBy(
-      InstructionIterator iterator, InstructionIterator otherIterator, Set<BasicBlock> visited) {
+      Value conditionValue,
+      InstructionIterator iterator,
+      InstructionIterator otherIterator,
+      Set<BasicBlock> visited) {
     // Skip over block-local instructions (i.e., instructions that define values that are not used
     // outside the block itself) that do not have side effects.
     Instruction instruction =
-        iterator.nextUntil(
-            or(
-                not(this::isBlockLocalInstructionWithoutSideEffects),
-                Instruction::isJumpInstruction));
+        skipNonDependentInstructionsUntil(
+            iterator, conditionValue, this::isNonLocalDefinitionOrSideEffecting);
 
     // If the instruction defines a value with non-local usages, then we would need a dominator
     // analysis to verify that all these non-local usages can in fact be replaced by a value
@@ -68,8 +108,8 @@
 
     // Skip over non-throwing instructions in the other block.
     Instruction otherInstruction =
-        otherIterator.nextUntil(
-            or(this::instructionMayHaveSideEffects, Instruction::isJumpInstruction));
+        skipNonDependentInstructionsUntil(
+            otherIterator, conditionValue, this::instructionMayHaveSideEffects);
     assert otherInstruction != null;
 
     if (instruction.isGoto()) {
@@ -101,7 +141,7 @@
         visited = SetUtils.newIdentityHashSet(instruction.getBlock());
       }
       if (visited.add(targetBlock)) {
-        return isSubsumedBy(targetBlock.iterator(), otherIterator, visited);
+        return isSubsumedBy(conditionValue, targetBlock.iterator(), otherIterator, visited);
       }
       // Guard against cycles in the control flow graph.
       return false;
@@ -125,8 +165,8 @@
 
       otherIterator = targetBlock.iterator();
       otherInstruction =
-          otherIterator.nextUntil(
-              or(this::instructionMayHaveSideEffects, Instruction::isJumpInstruction));
+          skipNonDependentInstructionsUntil(
+              otherIterator, conditionValue, this::instructionMayHaveSideEffects);
 
       // If following the first goto instruction leads to another goto instruction, then we need to
       // keep track of the set of visited blocks to guard against cycles in the control flow graph.
@@ -150,7 +190,7 @@
             || otherSingleTarget.getDefinition().getOptimizationInfo().mayHaveSideEffects()) {
           return false;
         }
-        return isSubsumedBy(iterator, otherIterator, visited);
+        return isSubsumedBy(conditionValue, iterator, otherIterator, visited);
       }
       return false;
     }
@@ -172,8 +212,8 @@
     return false;
   }
 
-  private boolean isBlockLocalInstructionWithoutSideEffects(Instruction instruction) {
-    return definesBlockLocalValue(instruction) && !instructionMayHaveSideEffects(instruction);
+  private boolean isNonLocalDefinitionOrSideEffecting(Instruction instruction) {
+    return !definesBlockLocalValue(instruction) || instructionMayHaveSideEffects(instruction);
   }
 
   private boolean definesBlockLocalValue(Instruction instruction) {
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 1d4e435..f0ca38f 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
@@ -1135,7 +1135,8 @@
       // behavior of the default block, or if the switch case is unreachable.
       if (switchCaseAnalyzer.switchCaseIsUnreachable(theSwitch, switchAbstractValue, i)) {
         eliminator.markSwitchCaseForRemoval(i);
-      } else if (behavioralSubsumption.isSubsumedBy(targetBlock, defaultTarget)) {
+      } else if (behavioralSubsumption.isSubsumedBy(
+          theSwitch.value(), targetBlock, defaultTarget)) {
         eliminator.markSwitchCaseForRemoval(i);
         hasSwitchCaseToDefaultRewrite = true;
       }
@@ -2642,7 +2643,8 @@
 
         // Unable to determine which branch will be taken. Check if the true target can safely be
         // rewritten to the false target.
-        if (behavioralSubsumption.isSubsumedBy(theIf.getTrueTarget(), theIf.fallthroughBlock())) {
+        if (behavioralSubsumption.isSubsumedBy(
+            theIf.inValues().get(0), theIf.getTrueTarget(), theIf.fallthroughBlock())) {
           simplifyIfWithKnownCondition(code, block, theIf, theIf.fallthroughBlock());
           simplified = true;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
index 407717c..5f0bea9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
@@ -608,7 +608,7 @@
       }
 
       @Override
-      protected List<Void> getFinalStateForRoots(Collection<StringBuilderNode> roots) {
+      protected List<Void> getFinalStateForRoots(Collection<? extends StringBuilderNode> roots) {
         return null;
       }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java
index a3f2d38..3c032b3 100644
--- a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.errors.Unreachable;
 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.DexMethodSignature;
@@ -15,31 +16,35 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
-import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepMethodInfo;
+import com.android.tools.r8.utils.DepthFirstSearchWorkListBase.StatefulDepthFirstSearchWorkList;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.MapUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.WorkList;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
 import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 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.Function;
 
 public class ProtoNormalizer {
 
@@ -60,47 +65,93 @@
   private void run(ExecutorService executorService) throws ExecutionException {
     GlobalReservationState globalReservationState = computeGlobalReservationState(executorService);
 
-    // TODO(b/173398086): This uses a single LocalReservationState for the entire program. This
-    //  should process the strongly connected program components in parallel, each with their own
-    //  LocalReservationState.
-    LocalReservationState localReservationState = new LocalReservationState();
+    // To ensure we do not add collisions of method signatures when creating the new method
+    // signatures we keep a map of methods in the type hierarchy, similar to what we do for
+    // minification.
     ProtoNormalizerGraphLens.Builder lensBuilder = ProtoNormalizerGraphLens.builder(appView);
-    for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
-      Map<DexMethodSignature, DexMethodSignature> newInstanceInitializerSignatures =
-          computeNewInstanceInitializerSignatures(
-              clazz, localReservationState, globalReservationState);
-      clazz
-          .getMethodCollection()
-          .replaceMethods(
-              method -> {
-                DexMethodSignature methodSignature = method.getSignature();
-                DexMethodSignature newMethodSignature =
-                    method.isInstanceInitializer()
-                        ? newInstanceInitializerSignatures.get(methodSignature)
-                        : localReservationState.getAndReserveNewMethodSignature(
-                            methodSignature, dexItemFactory, globalReservationState);
-                if (methodSignature.equals(newMethodSignature)) {
-                  return method;
-                }
-                DexMethod newMethodReference = newMethodSignature.withHolder(clazz, dexItemFactory);
-                RewrittenPrototypeDescription prototypeChanges =
-                    lensBuilder.recordNewMethodSignature(method, newMethodReference);
-                // TODO(b/195112263): Assert that the method does not have any optimization info.
-                //  If/when enabling proto normalization after the final round of tree shaking, this
-                //  should simply clear the optimization info, or replace it by a
-                //  ThrowingMethodOptimizationInfo since we should never use the optimization info
-                //  after this point.
-                return method.toTypeSubstitutedMethod(
-                    newMethodReference,
-                    builder -> {
-                      if (!prototypeChanges.isEmpty()) {
-                        builder
-                            .apply(prototypeChanges.createParameterAnnotationsRemover(method))
-                            .setGenericSignature(MethodTypeSignature.noSignature());
-                      }
-                    });
-              });
-    }
+    new StatefulDepthFirstSearchWorkList<DexClass, LocalReservationState, Void>() {
+
+      @Override
+      @SuppressWarnings("ReturnValueIgnored")
+      protected TraversalContinuation<Void, LocalReservationState> process(
+          DFSNodeWithState<DexClass, LocalReservationState> node,
+          Function<DexClass, DFSNodeWithState<DexClass, LocalReservationState>> childNodeConsumer) {
+        DexClass clazz = node.getNode();
+        node.setState(new LocalReservationState());
+        if (clazz.getSuperType() != null) {
+          appView
+              .contextIndependentDefinitionForWithResolutionResult(clazz.getSuperType())
+              .forEachClassResolutionResult(childNodeConsumer::apply);
+        }
+        return TraversalContinuation.doContinue();
+      }
+
+      @Override
+      protected TraversalContinuation<Void, LocalReservationState> joiner(
+          DFSNodeWithState<DexClass, LocalReservationState> node,
+          List<DFSNodeWithState<DexClass, LocalReservationState>> childStates) {
+        DexClass clazz = node.getNode();
+        assert childStates.size() >= 1
+            || clazz.getType() == dexItemFactory.objectType
+            || clazz.hasMissingSuperType(appView.appInfo());
+        // We can have multiple child states if there are multiple definitions of a class.
+        assert childStates.size() <= 1 || options.loadAllClassDefinitions;
+        LocalReservationState localReservationState = node.getState();
+        if (!childStates.isEmpty()) {
+          localReservationState.linkParent(childStates.get(0).getState());
+        }
+        Map<DexMethodSignature, DexMethodSignature> newInstanceInitializerSignatures =
+            clazz.isProgramClass()
+                ? computeNewInstanceInitializerSignatures(
+                    clazz.asProgramClass(), localReservationState, globalReservationState)
+                : null;
+        clazz
+            .getMethodCollection()
+            .replaceMethods(
+                method -> {
+                  DexMethodSignature methodSignature = method.getSignature();
+                  if (!clazz.isProgramClass()) {
+                    // We cannot change the signature of the method. Record it to ensure we do not
+                    // change a program signature in a sub class to override this.
+                    localReservationState.recordNoSignatureChange(methodSignature, dexItemFactory);
+                    return method;
+                  } else {
+                    assert newInstanceInitializerSignatures != null;
+                    DexMethodSignature newMethodSignature =
+                        method.isInstanceInitializer()
+                            ? newInstanceInitializerSignatures.get(methodSignature)
+                            : localReservationState.getAndReserveNewMethodSignature(
+                                methodSignature, dexItemFactory, globalReservationState);
+                    if (methodSignature.equals(newMethodSignature)) {
+                      // This method could not be optimized, record it as identity mapping to ensure
+                      // we keep it going forward.
+                      localReservationState.recordNoSignatureChange(
+                          methodSignature, dexItemFactory);
+                      return method;
+                    }
+                    DexMethod newMethodReference =
+                        newMethodSignature.withHolder(clazz.asProgramClass(), dexItemFactory);
+                    RewrittenPrototypeDescription prototypeChanges =
+                        lensBuilder.recordNewMethodSignature(method, newMethodReference);
+                    // TODO(b/195112263): Assert that the method does not have optimization info.
+                    // If/when enabling proto normalization after the final round of tree shaking,
+                    // this should simply clear the optimization info, or replace it by a
+                    // ThrowingMethodOptimizationInfo since we should never use the optimization
+                    // info after this point.
+                    return method.toTypeSubstitutedMethod(
+                        newMethodReference,
+                        builder -> {
+                          if (!prototypeChanges.isEmpty()) {
+                            builder
+                                .apply(prototypeChanges.createParameterAnnotationsRemover(method))
+                                .setGenericSignature(MethodTypeSignature.noSignature());
+                          }
+                        });
+                  }
+                });
+        return TraversalContinuation.doContinue(localReservationState);
+      }
+    }.run(appView.appInfo().classesWithDeterministicOrder());
 
     if (!lensBuilder.isEmpty()) {
       appView.rewriteWithLens(lensBuilder.build());
@@ -331,15 +382,13 @@
     if (appInfo.isBootstrapMethod(method)) {
       return true;
     }
-    ObjectAllocationInfoCollection objectAllocationInfoCollection =
-        appInfo.getObjectAllocationInfoCollection();
-    if (method.getHolder().isInterface()
+    // As long as we do not consider interface method and overrides as optimizable we can change
+    // method signatures in a top-down traversal.
+    return method.getHolder().isInterface()
         && method.getDefinition().isAbstract()
-        && objectAllocationInfoCollection.isImmediateInterfaceOfInstantiatedLambda(
-            method.getHolder())) {
-      return true;
-    }
-    return false;
+        && appInfo
+            .getObjectAllocationInfoCollection()
+            .isImmediateInterfaceOfInstantiatedLambda(method.getHolder());
   }
 
   static class GlobalReservationState {
@@ -389,8 +438,10 @@
 
   static class LocalReservationState {
 
-    MutableBidirectionalOneToOneMap<DexMethodSignature, DexMethodSignature> newMethodSignatures =
-        new BidirectionalOneToOneHashMap<>();
+    private final List<LocalReservationState> parents = new ArrayList<>(2);
+
+    private final MutableBidirectionalOneToOneMap<DexMethodSignature, DexMethodSignature>
+        newMethodSignatures = new BidirectionalOneToOneHashMap<>();
 
     DexMethodSignature getNewMethodSignature(
         DexMethodSignature methodSignature,
@@ -414,33 +465,68 @@
         GlobalReservationState globalReservationState,
         boolean reserve) {
       if (globalReservationState.isUnoptimizable(methodSignature)) {
-        assert !newMethodSignatures.containsKey(methodSignature);
+        assert getReserved(methodSignature) == null
+            || methodSignature.equals(getReserved(methodSignature));
         return methodSignature;
       }
-      DexMethodSignature reservedSignature = newMethodSignatures.get(methodSignature);
+      DexMethodSignature reservedSignature = getReserved(methodSignature);
       if (reservedSignature != null) {
-        assert reservedSignature
-            .getParameters()
-            .equals(globalReservationState.getReservedParameters(methodSignature));
         return reservedSignature;
       }
       DexTypeList reservedParameters =
           globalReservationState.getReservedParameters(methodSignature);
       DexMethodSignature newMethodSignature =
           methodSignature.withParameters(reservedParameters, dexItemFactory);
-      if (newMethodSignatures.containsValue(newMethodSignature)) {
+      if (isDestinationTaken(newMethodSignature)) {
         int index = 1;
         String newMethodBaseName = methodSignature.getName().toString();
         do {
           DexString newMethodName = dexItemFactory.createString(newMethodBaseName + "$" + index);
           newMethodSignature = newMethodSignature.withName(newMethodName);
           index++;
-        } while (newMethodSignatures.containsValue(newMethodSignature));
+        } while (isDestinationTaken(newMethodSignature));
       }
       if (reserve) {
         newMethodSignatures.put(methodSignature, newMethodSignature);
       }
       return newMethodSignature;
     }
+
+    private void linkParent(LocalReservationState parent) {
+      this.parents.add(parent);
+    }
+
+    private DexMethodSignature getReserved(DexMethodSignature signature) {
+      WorkList<LocalReservationState> workList = WorkList.newIdentityWorkList(this);
+      while (workList.hasNext()) {
+        LocalReservationState localReservationState = workList.next();
+        DexMethodSignature reservedSignature =
+            localReservationState.newMethodSignatures.get(signature);
+        if (reservedSignature != null) {
+          return reservedSignature;
+        }
+        workList.addIfNotSeen(localReservationState.parents);
+      }
+      return null;
+    }
+
+    private boolean isDestinationTaken(DexMethodSignature signature) {
+      WorkList<LocalReservationState> workList = WorkList.newIdentityWorkList(this);
+      while (workList.hasNext()) {
+        LocalReservationState localReservationState = workList.next();
+        if (localReservationState.newMethodSignatures.containsValue(signature)) {
+          return true;
+        }
+        workList.addIfNotSeen(localReservationState.parents);
+      }
+      return false;
+    }
+
+    public void recordNoSignatureChange(
+        DexMethodSignature methodSignature, DexItemFactory factory) {
+      if (!methodSignature.getName().equals(factory.constructorMethodName)) {
+        newMethodSignatures.put(methodSignature, methodSignature);
+      }
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java b/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
index 37a55bb..a4d1c8f 100644
--- a/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
+++ b/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
@@ -115,7 +115,7 @@
   /** The joining of state during backtracking of the algorithm. */
   abstract TraversalContinuation<TB, TC> internalOnJoin(T node);
 
-  protected abstract List<TC> getFinalStateForRoots(Collection<N> roots);
+  protected abstract List<TC> getFinalStateForRoots(Collection<? extends N> roots);
 
   final T internalEnqueueNode(N value) {
     T dfsNode = nodeToNodeWithStateMap.computeIfAbsent(value, this::createDfsNode);
@@ -138,7 +138,7 @@
     return run(Arrays.asList(roots));
   }
 
-  public final TraversalContinuation<TB, List<TC>> run(Collection<N> roots) {
+  public final TraversalContinuation<TB, List<TC>> run(Collection<? extends N> roots) {
     roots.forEach(this::internalEnqueueNode);
     while (!workList.isEmpty()) {
       T node = workList.removeLast();
@@ -199,7 +199,7 @@
     }
 
     @Override
-    protected List<TC> getFinalStateForRoots(Collection<N> roots) {
+    protected List<TC> getFinalStateForRoots(Collection<? extends N> roots) {
       return null;
     }
   }
@@ -264,7 +264,7 @@
     }
 
     @Override
-    public List<S> getFinalStateForRoots(Collection<N> roots) {
+    public List<S> getFinalStateForRoots(Collection<? extends N> roots) {
       return ListUtils.map(roots, root -> getNodeStateForNode(root).state);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 98d5a27..09641c3 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1805,6 +1805,9 @@
         System.getProperty("com.android.tools.r8.disableApiModeling") == null;
     public boolean reportUnknownApiReferences =
         System.getProperty("com.android.tools.r8.reportUnknownApiReferences") != null;
+    // TODO(b/259076765): Remove when resolved.
+    public boolean stubNonThrowableClasses =
+        System.getProperty("com.android.tools.r8.stubNonThrowableClasses") != null;
 
     // TODO(b/232823652): Enable when we can compute the offset correctly.
     public boolean useMemoryMappedByteBuffer = false;
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
index 6be9cc3..834a3df 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -16,6 +16,9 @@
 public class SetUtils {
 
   public static <T> boolean containsAnyOf(Set<T> set, Iterable<T> elements) {
+    if (set.isEmpty()) {
+      return false;
+    }
     for (T element : elements) {
       if (set.contains(element)) {
         return true;
diff --git a/src/test/java/com/android/tools/r8/cf/CompanionClassPreamblePositionTest.java b/src/test/java/com/android/tools/r8/cf/CompanionClassPreamblePositionTest.java
new file mode 100644
index 0000000..49d7ef6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/CompanionClassPreamblePositionTest.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2022, 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.cf;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.transformers.ClassFileTransformer.LineTranslation;
+import com.android.tools.r8.transformers.MethodTransformer.MethodContext;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class CompanionClassPreamblePositionTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("no line", "has line");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDefaultCfRuntime()
+        .withApiLevel(AndroidApiLevel.B)
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  public CompanionClassPreamblePositionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testZeroInInput() throws Exception {
+    testForJvm()
+        .addProgramClasses(TestClass.class, A.class)
+        .addProgramClassFileData(getTransformedI(true))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkZeroLineIsPresent);
+  }
+
+  @Test
+  public void testNoAddedZeroD8() throws Exception {
+    Path out =
+        testForD8(parameters.getBackend())
+            .addProgramClasses(TestClass.class, A.class)
+            .addProgramClassFileData(getTransformedI(false))
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .writeToZip();
+    testForJvm()
+        .addProgramFiles(out)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkZeroLineNotPresent);
+  }
+
+  private void checkZeroLineIsPresent(CodeInspector inspector) throws Exception {
+    // ASM skips zero lines so use javap to check the presence of a zero line.
+    ClassSubject itf = inspector.clazz(I.class);
+    assertThat(itf.javap(true), containsString("line 0: 0"));
+  }
+
+  private void checkZeroLineNotPresent(CodeInspector inspector) throws Exception {
+    ClassSubject companion = inspector.companionClassFor(I.class);
+    assertThat(companion.javap(true), not(containsString("line 0: 0")));
+  }
+
+  private byte[] getTransformedI(boolean includeZero) throws Exception {
+    return transformer(I.class)
+        .setPredictiveLineNumbering(
+            new LineTranslation() {
+              Int2IntMap map = new Int2IntOpenHashMap();
+
+              @Override
+              public int translate(MethodContext context, int line) {
+                if (context.getReference().getMethodName().equals("foo")) {
+                  int newLine = map.computeIfAbsent(line, ignore -> map.size());
+                  return newLine == 0 ? (includeZero ? 0 : -1) : newLine;
+                }
+                return line;
+              }
+            })
+        .transform();
+  }
+
+  interface I {
+    default void foo() {
+      System.out.println("no line"); // Transform removes/replaces this line entry.
+      System.out.println("has line");
+    }
+  }
+
+  static class A implements I {}
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
new file mode 100644
index 0000000..fcdab96
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
@@ -0,0 +1,218 @@
+// Copyright (c) 2022, 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.keepanno;
+
+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;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.JavaCompilerTool;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.keepanno.annotations.KeepConstants.Edge;
+import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
+import com.android.tools.r8.keepanno.asm.KeepEdgeWriter;
+import com.android.tools.r8.keepanno.ast.KeepEdge;
+import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor;
+import com.android.tools.r8.keepanno.processor.KeepEdgeProcessor;
+import com.android.tools.r8.keepanno.testsource.KeepClassAndDefaultConstructorSource;
+import com.android.tools.r8.keepanno.testsource.KeepFieldSource;
+import com.android.tools.r8.keepanno.testsource.KeepSourceEdges;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.AnnotationVisitor;
+
+@RunWith(Parameterized.class)
+public class KeepEdgeAnnotationsTest extends TestBase {
+
+  private static class ParamWrapper {
+    private final Class<?> clazz;
+    private final TestParameters params;
+
+    public ParamWrapper(Class<?> clazz, TestParameters params) {
+      this.clazz = clazz;
+      this.params = params;
+    }
+
+    @Override
+    public String toString() {
+      return clazz.getSimpleName() + ", " + params.toString();
+    }
+  }
+
+  private static final Path KEEP_ANNO_PATH =
+      Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "keepanno");
+
+  private static List<Class<?>> getTestClasses() {
+    return ImmutableList.of(KeepClassAndDefaultConstructorSource.class, KeepFieldSource.class);
+  }
+
+  private final TestParameters parameters;
+  private final Class<?> source;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static List<ParamWrapper> data() {
+    TestParametersCollection params =
+        getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+    return getTestClasses().stream()
+        .flatMap(c -> params.stream().map(p -> new ParamWrapper(c, p)))
+        .collect(Collectors.toList());
+  }
+
+  public KeepEdgeAnnotationsTest(ParamWrapper wrapper) {
+    this.parameters = wrapper.params;
+    this.source = wrapper.clazz;
+  }
+
+  private String getExpected() {
+    return KeepSourceEdges.getExpected(source);
+  }
+
+  @Test
+  public void testProcessorClassfiles() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    Path out =
+        JavaCompilerTool.create(parameters.getRuntime().asCf(), temp)
+            .addAnnotationProcessors(typeName(KeepEdgeProcessor.class))
+            .addClasspathFiles(KEEP_ANNO_PATH)
+            .addClassNames(Collections.singletonList(typeName(source)))
+            .addClasspathFiles(Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "test"))
+            .addClasspathFiles(ToolHelper.DEPS)
+            .compile();
+
+    CodeInspector inspector = new CodeInspector(out);
+    checkSynthesizedKeepEdgeClass(inspector, out);
+    // The source is added as a classpath name but not part of the compilation unit output.
+    assertThat(inspector.clazz(source), isAbsent());
+
+    testForJvm()
+        .addProgramClassesAndInnerClasses(source)
+        .addProgramFiles(out)
+        .run(parameters.getRuntime(), source)
+        .assertSuccessWithOutput(getExpected());
+  }
+
+  @Test
+  public void testProcessorJavaSource() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    Path out =
+        JavaCompilerTool.create(parameters.getRuntime().asCf(), temp)
+            .addSourceFiles(ToolHelper.getSourceFileForTestClass(source))
+            .addAnnotationProcessors(typeName(KeepEdgeProcessor.class))
+            .addClasspathFiles(KEEP_ANNO_PATH)
+            .addClasspathFiles(ToolHelper.DEPS)
+            .compile();
+    testForJvm()
+        .addProgramFiles(out)
+        .run(parameters.getRuntime(), source)
+        .assertSuccessWithOutput(getExpected())
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(source), isPresent());
+              checkSynthesizedKeepEdgeClass(inspector, out);
+            });
+  }
+
+  @Test
+  public void testAsmReader() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    Set<KeepEdge> expectedEdges = KeepSourceEdges.getExpectedEdges(source);
+    ClassReference clazz = Reference.classFromClass(source);
+    // Original bytes of the test class.
+    byte[] original = ToolHelper.getClassAsBytes(source);
+    // Strip out all the annotations to ensure they are actually added again.
+    byte[] stripped =
+        transformer(source)
+            .addClassTransformer(
+                new ClassTransformer() {
+                  @Override
+                  public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+                    // Ignore all input annotations.
+                    return null;
+                  }
+                })
+            .transform();
+    // Manually add in the expected edges again.
+    byte[] readded =
+        transformer(stripped, clazz)
+            .addClassTransformer(
+                new ClassTransformer() {
+
+                  @Override
+                  public void visitEnd() {
+                    for (KeepEdge edge : expectedEdges) {
+                      KeepEdgeWriter.writeEdge(edge, super::visitAnnotation);
+                    }
+                    super.visitEnd();
+                  }
+                })
+            .transform();
+
+    // Read the edges from each version.
+    Set<KeepEdge> originalEdges = KeepEdgeReader.readKeepEdges(original);
+    Set<KeepEdge> strippedEdges = KeepEdgeReader.readKeepEdges(stripped);
+    Set<KeepEdge> readdedEdges = KeepEdgeReader.readKeepEdges(readded);
+
+    // The edges are compared to the "expected" ast to ensure we don't hide failures in reading or
+    // writing.
+    assertEquals(Collections.emptySet(), strippedEdges);
+    assertEquals(expectedEdges, originalEdges);
+    assertEquals(expectedEdges, readdedEdges);
+  }
+
+  @Test
+  public void testExtractAndRun() throws Exception {
+    List<String> rules = getKeepRulesForClass(source);
+    testForR8(parameters.getBackend())
+        .addClasspathFiles(KEEP_ANNO_PATH)
+        .addProgramClassesAndInnerClasses(source)
+        .addKeepRules(rules)
+        .addKeepMainRule(source)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), source)
+        .assertSuccessWithOutput(getExpected());
+  }
+
+  private List<String> getKeepRulesForClass(Class<?> clazz) throws IOException {
+    Set<KeepEdge> keepEdges = KeepEdgeReader.readKeepEdges(ToolHelper.getClassAsBytes(clazz));
+    List<String> rules = new ArrayList<>();
+    KeepRuleExtractor extractor = new KeepRuleExtractor(rules::add);
+    keepEdges.forEach(extractor::extract);
+    return rules;
+  }
+
+  private void checkSynthesizedKeepEdgeClass(CodeInspector inspector, Path data)
+      throws IOException {
+    String synthesizedEdgesClassName =
+        KeepEdgeProcessor.getClassTypeNameForSynthesizedEdges(source.getTypeName());
+    ClassSubject synthesizedEdgesClass = inspector.clazz(synthesizedEdgesClassName);
+    assertThat(synthesizedEdgesClass, isPresent());
+    assertThat(synthesizedEdgesClass.annotation(Edge.CLASS.getTypeName()), isPresent());
+    String entry = ZipUtils.zipEntryNameForClass(synthesizedEdgesClass.getFinalReference());
+    byte[] bytes = ZipUtils.readSingleEntry(data, entry);
+    Set<KeepEdge> keepEdges = KeepEdgeReader.readKeepEdges(bytes);
+    assertEquals(KeepSourceEdges.getExpectedEdges(source), keepEdges);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/asm/KeepEdgeAsmTest.java b/src/test/java/com/android/tools/r8/keepanno/asm/KeepEdgeAsmTest.java
deleted file mode 100644
index b9ebfa2..0000000
--- a/src/test/java/com/android/tools/r8/keepanno/asm/KeepEdgeAsmTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (c) 2022, 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.keepanno.asm;
-
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.keepanno.ast.KeepEdge;
-import com.android.tools.r8.keepanno.testsource.KeepClassAndDefaultConstructorSource;
-import com.android.tools.r8.keepanno.testsource.KeepSourceEdges;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.transformers.ClassTransformer;
-import java.util.Collections;
-import java.util.Set;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.objectweb.asm.AnnotationVisitor;
-
-@RunWith(Parameterized.class)
-public class KeepEdgeAsmTest extends TestBase {
-
-  private static final Class<?> SOURCE = KeepClassAndDefaultConstructorSource.class;
-
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
-  }
-
-  public KeepEdgeAsmTest(TestParameters parameters) {
-    parameters.assertNoneRuntime();
-  }
-
-  @Test
-  public void testAsmReader() throws Exception {
-    Set<KeepEdge> expectedEdges = KeepSourceEdges.getExpectedEdges(SOURCE);
-    ClassReference clazz = Reference.classFromClass(SOURCE);
-    // Original bytes of the test class.
-    byte[] original = ToolHelper.getClassAsBytes(SOURCE);
-    // Strip out all the annotations to ensure they are actually added again.
-    byte[] stripped =
-        transformer(SOURCE)
-            .addClassTransformer(
-                new ClassTransformer() {
-                  @Override
-                  public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
-                    // Ignore all input annotations.
-                    return null;
-                  }
-                })
-            .transform();
-    // Manually add in the expected edges again.
-    byte[] readded =
-        transformer(stripped, clazz)
-            .addClassTransformer(
-                new ClassTransformer() {
-
-                  @Override
-                  public void visitEnd() {
-                    for (KeepEdge edge : expectedEdges) {
-                      KeepEdgeWriter.writeEdge(edge, super::visitAnnotation);
-                    }
-                    super.visitEnd();
-                  }
-                })
-            .transform();
-
-    // Read the edges from each version.
-    Set<KeepEdge> originalEdges = KeepEdgeReader.readKeepEdges(original);
-    Set<KeepEdge> strippedEdges = KeepEdgeReader.readKeepEdges(stripped);
-    Set<KeepEdge> readdedEdges = KeepEdgeReader.readKeepEdges(readded);
-
-    // The edges are compared to the "expected" ast to ensure we don't hide failures in reading or
-    // writing.
-    assertEquals(Collections.emptySet(), strippedEdges);
-    assertEquals(expectedEdges, originalEdges);
-    assertEquals(expectedEdges, readdedEdges);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractorTest.java b/src/test/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractorTest.java
deleted file mode 100644
index 94001ec..0000000
--- a/src/test/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractorTest.java
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (c) 2022, 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.keepanno.keeprules;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
-import com.android.tools.r8.keepanno.ast.KeepEdge;
-import com.android.tools.r8.keepanno.testsource.KeepClassAndDefaultConstructorSource;
-import com.android.tools.r8.keepanno.testsource.KeepSourceEdges;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class KeepRuleExtractorTest extends TestBase {
-
-  private static final Class<?> SOURCE = KeepClassAndDefaultConstructorSource.class;
-  private static final String EXPECTED = KeepSourceEdges.getExpected(SOURCE);
-  private static final Path KEEP_ANNO_PATH =
-      Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "keepanno");
-
-  private final TestParameters parameters;
-
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
-  }
-
-  public KeepRuleExtractorTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void test() throws Exception {
-    List<String> rules = getKeepRulesForClass(SOURCE);
-    testForR8(parameters.getBackend())
-        .addClasspathFiles(KEEP_ANNO_PATH)
-        .addProgramClassesAndInnerClasses(SOURCE)
-        .addKeepRules(rules)
-        .addKeepMainRule(SOURCE)
-        .setMinApi(parameters.getApiLevel())
-        .run(parameters.getRuntime(), SOURCE)
-        .assertSuccessWithOutput(EXPECTED);
-  }
-
-  private List<String> getKeepRulesForClass(Class<?> clazz) throws IOException {
-    Set<KeepEdge> keepEdges = KeepEdgeReader.readKeepEdges(ToolHelper.getClassAsBytes(clazz));
-    List<String> rules = new ArrayList<>();
-    KeepRuleExtractor extractor = new KeepRuleExtractor(rules::add);
-    keepEdges.forEach(extractor::extract);
-    return rules;
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessorTest.java b/src/test/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessorTest.java
deleted file mode 100644
index 83f60a3..0000000
--- a/src/test/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessorTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright (c) 2022, 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.keepanno.processor;
-
-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;
-
-import com.android.tools.r8.JavaCompilerTool;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.keepanno.annotations.KeepConstants.Edge;
-import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
-import com.android.tools.r8.keepanno.ast.KeepEdge;
-import com.android.tools.r8.keepanno.testsource.KeepClassAndDefaultConstructorSource;
-import com.android.tools.r8.keepanno.testsource.KeepSourceEdges;
-import com.android.tools.r8.utils.ZipUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Collections;
-import java.util.Set;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class KeepEdgeProcessorTest extends TestBase {
-
-  private static final Path KEEP_ANNO_PATH =
-      Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "keepanno");
-  private static final Class<?> SOURCE = KeepClassAndDefaultConstructorSource.class;
-  private static final String EXPECTED = KeepSourceEdges.getExpected(SOURCE);
-
-  private final TestParameters parameters;
-
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDefaultCfRuntime().build();
-  }
-
-  public KeepEdgeProcessorTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void testClassfile() throws Exception {
-    Path out =
-        JavaCompilerTool.create(parameters.getRuntime().asCf(), temp)
-            .addAnnotationProcessors(typeName(KeepEdgeProcessor.class))
-            .addClasspathFiles(KEEP_ANNO_PATH)
-            .addClassNames(Collections.singletonList(typeName(SOURCE)))
-            .addClasspathFiles(Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "test"))
-            .addClasspathFiles(ToolHelper.DEPS)
-            .compile();
-
-    CodeInspector inspector = new CodeInspector(out);
-    checkSynthesizedKeepEdgeClass(inspector, out);
-    // The source is added as a classpath name but not part of the compilation unit output.
-    assertThat(inspector.clazz(SOURCE), isAbsent());
-
-    testForJvm()
-        .addProgramClasses(SOURCE, KeepClassAndDefaultConstructorSource.A.class)
-        .addProgramFiles(out)
-        .run(parameters.getRuntime(), SOURCE)
-        .assertSuccessWithOutput(EXPECTED);
-  }
-
-  @Test
-  public void testJavaSource() throws Exception {
-    Path out =
-        JavaCompilerTool.create(parameters.getRuntime().asCf(), temp)
-            .addSourceFiles(ToolHelper.getSourceFileForTestClass(SOURCE))
-            .addAnnotationProcessors(typeName(KeepEdgeProcessor.class))
-            .addClasspathFiles(KEEP_ANNO_PATH)
-            .addClasspathFiles(ToolHelper.DEPS)
-            .compile();
-
-    testForJvm()
-        .addProgramFiles(out)
-        .run(parameters.getRuntime(), SOURCE)
-        .assertSuccessWithOutput(EXPECTED)
-        .inspect(
-            inspector -> {
-              assertThat(inspector.clazz(SOURCE), isPresent());
-              checkSynthesizedKeepEdgeClass(inspector, out);
-            });
-  }
-
-  private void checkSynthesizedKeepEdgeClass(CodeInspector inspector, Path data)
-      throws IOException {
-    String synthesizedEdgesClassName =
-        KeepEdgeProcessor.getClassTypeNameForSynthesizedEdges(SOURCE.getTypeName());
-    ClassSubject synthesizedEdgesClass = inspector.clazz(synthesizedEdgesClassName);
-    assertThat(synthesizedEdgesClass, isPresent());
-    assertThat(synthesizedEdgesClass.annotation(Edge.CLASS.getTypeName()), isPresent());
-    String entry = ZipUtils.zipEntryNameForClass(synthesizedEdgesClass.getFinalReference());
-    byte[] bytes = ZipUtils.readSingleEntry(data, entry);
-    Set<KeepEdge> keepEdges = KeepEdgeReader.readKeepEdges(bytes);
-    assertEquals(KeepSourceEdges.getExpectedEdges(SOURCE), keepEdges);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepFieldSource.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepFieldSource.java
new file mode 100644
index 0000000..6f577e4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepFieldSource.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2022, 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.keepanno.testsource;
+
+import com.android.tools.r8.keepanno.annotations.KeepEdge;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import java.lang.reflect.Field;
+
+@KeepEdge(
+    consequences = {
+      // Keep the reflectively accessed field.
+      @KeepTarget(classConstant = KeepFieldSource.A.class, fieldName = "f")
+    })
+public class KeepFieldSource {
+
+  public static class A {
+
+    public int f;
+
+    public A(int x) {
+      f = x;
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    int x = 42 + args.length;
+    Object o = System.nanoTime() > 0 ? new A(x) : null;
+    Field f = o.getClass().getDeclaredField("f");
+    int y = f.getInt(o);
+    if (x == y) {
+      System.out.println("The values match!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
index 5637edf..7af9aee 100644
--- a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
+++ b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
@@ -5,6 +5,8 @@
 
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
+import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
@@ -25,6 +27,9 @@
     if (clazz.equals(KeepClassAndDefaultConstructorSource.class)) {
       return getKeepClassAndDefaultConstructorSourceEdges();
     }
+    if (clazz.equals(KeepFieldSource.class)) {
+      return getKeepFieldSourceEdges();
+    }
     throw new RuntimeException();
   }
 
@@ -32,6 +37,9 @@
     if (clazz.equals(KeepClassAndDefaultConstructorSource.class)) {
       return getKeepClassAndDefaultConstructorSourceExpected();
     }
+    if (clazz.equals(KeepFieldSource.class)) {
+      return getKeepFieldSourceExpected();
+    }
     throw new RuntimeException();
   }
 
@@ -57,4 +65,21 @@
     KeepEdge edge = KeepEdge.builder().setConsequences(consequences).build();
     return Collections.singleton(edge);
   }
+
+  public static String getKeepFieldSourceExpected() {
+    return StringUtils.lines("The values match!");
+  }
+
+  public static Set<KeepEdge> getKeepFieldSourceEdges() {
+    Class<?> clazz = KeepFieldSource.A.class;
+    KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
+    KeepFieldPattern fieldPattern =
+        KeepFieldPattern.builder().setNamePattern(KeepFieldNamePattern.exact("f")).build();
+    KeepItemPattern fieldItem =
+        KeepItemPattern.builder().setClassPattern(name).setMemberPattern(fieldPattern).build();
+    KeepTarget fieldTarget = KeepTarget.builder().setItem(fieldItem).build();
+    KeepConsequences consequences = KeepConsequences.builder().addTarget(fieldTarget).build();
+    KeepEdge edge = KeepEdge.builder().setConsequences(consequences).build();
+    return Collections.singleton(edge);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingAmbiguousDispatchTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingAmbiguousDispatchTest.java
new file mode 100644
index 0000000..2b41739
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingAmbiguousDispatchTest.java
@@ -0,0 +1,164 @@
+// Copyright (c) 2022, 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 org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DiagnosticsMatcher;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MemberRebindingAmbiguousDispatchTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean abstractMethodOnSuperClass;
+
+  @Parameter(2)
+  public boolean interfaceAsSymbolicReference;
+
+  @Parameters(name = "{0}, abstractMethodOnSuperClass: {1}, interfaceAsSymbolicReference {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        BooleanUtils.values(),
+        BooleanUtils.values());
+  }
+
+  private void setupInput(TestBuilder<?, ?> testBuilder) {
+    testBuilder
+        .addProgramClasses(Main.class, SuperInterface.class)
+        .applyIf(
+            abstractMethodOnSuperClass,
+            b -> b.addProgramClassFileData(getSuperClassWithFooAsAbstract()),
+            b -> b.addProgramClasses(SuperClass.class))
+        .applyIf(
+            interfaceAsSymbolicReference,
+            b -> b.addProgramClassFileData(getProgramClassWithInvokeToInterface()),
+            b -> b.addProgramClasses(ProgramClass.class));
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .apply(this::setupInput)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  private boolean desugaringWithoutSupport() {
+    return parameters.isDexRuntime()
+        && interfaceAsSymbolicReference
+        && !parameters.canUseDefaultAndStaticInterfaceMethods();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeFalse(desugaringWithoutSupport());
+    testForR8(parameters.getBackend())
+        .apply(this::setupInput)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testR8AssertionError() {
+    assumeTrue(desugaringWithoutSupport());
+    // TODO(b/259227990): We should not fail compilation.
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForR8(parameters.getBackend())
+                .apply(this::setupInput)
+                .setMinApi(parameters.getApiLevel())
+                .addKeepMainRule(Main.class)
+                .compileWithExpectedDiagnostics(
+                    diagnostics ->
+                        diagnostics.assertErrorThatMatches(
+                            DiagnosticsMatcher.diagnosticException(AssertionError.class))));
+  }
+
+  private void checkOutput(TestRunResult<?> result) {
+    if (parameters.isDexRuntime()
+        && parameters.getDexRuntimeVersion().isDalvik()
+        && interfaceAsSymbolicReference) {
+      result.assertFailureWithErrorThatThrows(VerifyError.class);
+    } else if (parameters.isDexRuntime()
+        && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V7_0_0)
+        && interfaceAsSymbolicReference
+        && !parameters.canUseDefaultAndStaticInterfaceMethods()) {
+      result.assertFailureWithErrorThatThrows(ClassNotFoundException.class);
+    } else if (abstractMethodOnSuperClass || interfaceAsSymbolicReference) {
+      result.assertFailureWithErrorThatThrows(AbstractMethodError.class);
+    } else {
+      result.assertSuccessWithOutputLines("SuperClass::foo");
+    }
+  }
+
+  private byte[] getSuperClassWithFooAsAbstract() throws Exception {
+    return transformer(SuperClassAbstract.class)
+        .setClassDescriptor(descriptor(SuperClass.class))
+        .transform();
+  }
+
+  private byte[] getProgramClassWithInvokeToInterface() throws Exception {
+    return transformer(ProgramClass.class)
+        .transformMethodInsnInMethod(
+            "foo",
+            (opcode, owner, name, descriptor, isInterface, visitor) ->
+                visitor.visitMethodInsn(
+                    opcode, binaryName(SuperInterface.class), name, descriptor, true))
+        .transform();
+  }
+
+  public abstract static class SuperClassAbstract {
+
+    public abstract void foo();
+  }
+
+  public abstract static class SuperClass {
+
+    public void foo() {
+      System.out.println("SuperClass::foo");
+    }
+  }
+
+  public interface SuperInterface {
+
+    void foo();
+  }
+
+  public static class ProgramClass extends SuperClass implements SuperInterface {
+
+    @Override
+    public void foo() {
+      super.foo();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new ProgramClass().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingAmbiguousDispatchToLibraryTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingAmbiguousDispatchToLibraryTest.java
new file mode 100644
index 0000000..81ac099
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingAmbiguousDispatchToLibraryTest.java
@@ -0,0 +1,152 @@
+// Copyright (c) 2022, 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 org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MemberRebindingAmbiguousDispatchToLibraryTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean abstractMethodOnSuperClass;
+
+  @Parameter(2)
+  public boolean interfaceAsSymbolicReference;
+
+  @Parameters(name = "{0}, abstractMethodOnSuperClass: {1}, interfaceAsSymbolicReference {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevelsAlsoForCf().build(),
+        BooleanUtils.values(),
+        BooleanUtils.values());
+  }
+
+  private void setupInput(TestBuilder<?, ?> testBuilder) throws Exception {
+    testBuilder
+        .addProgramClasses(Main.class)
+        .addProgramClassFileData(getProgramClass())
+        .addDefaultRuntimeLibrary(parameters)
+        .addLibraryClasses(SuperInterface.class)
+        .addLibraryClassFileData(getSuperClass());
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .apply(this::setupInput)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .apply(this::setupInput)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathClasses(SuperInterface.class)
+        .addRunClasspathClassFileData(getSuperClass())
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .apply(this::setupInput)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .compile()
+        .addRunClasspathClasses(SuperInterface.class)
+        .addRunClasspathClassFileData(getSuperClass())
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  private void checkOutput(TestRunResult<?> result) {
+    if (parameters.isDexRuntime() && interfaceAsSymbolicReference) {
+      if (parameters.getDexRuntimeVersion().isDalvik()) {
+        result.assertFailureWithErrorThatThrows(VerifyError.class);
+      } else if (parameters.getDexRuntimeVersion().isOlderThan(Version.V7_0_0)) {
+        result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+      } else {
+        // TODO(b/259227990): If the SuperClass.foo() is not abstract we produce a working program.
+        result.assertFailureWithErrorThatThrows(AbstractMethodError.class);
+      }
+    } else if (abstractMethodOnSuperClass || interfaceAsSymbolicReference) {
+      result.assertFailureWithErrorThatThrows(AbstractMethodError.class);
+    } else {
+      result.assertSuccessWithOutputLines("SuperClass::foo");
+    }
+  }
+
+  private byte[] getSuperClass() throws Exception {
+    return abstractMethodOnSuperClass
+        ? transformer(SuperClassAbstract.class)
+            .setClassDescriptor(descriptor(SuperClass.class))
+            .transform()
+        : transformer(SuperClass.class).transform();
+  }
+
+  private byte[] getProgramClass() throws Exception {
+    return interfaceAsSymbolicReference
+        ? transformer(ProgramClass.class)
+            .transformMethodInsnInMethod(
+                "foo",
+                (opcode, owner, name, descriptor, isInterface, visitor) ->
+                    visitor.visitMethodInsn(
+                        opcode, binaryName(SuperInterface.class), name, descriptor, true))
+            .transform()
+        : transformer(ProgramClass.class).transform();
+  }
+
+  public abstract static class SuperClassAbstract {
+
+    public abstract void foo();
+  }
+
+  public abstract static class SuperClass {
+
+    public void foo() {
+      System.out.println("SuperClass::foo");
+    }
+  }
+
+  public interface SuperInterface {
+
+    void foo();
+  }
+
+  public static class ProgramClass extends SuperClass implements SuperInterface {
+
+    @Override
+    public void foo() {
+      super.foo();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new ProgramClass().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDestinationOverrideLibraryTest.java b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDestinationOverrideLibraryTest.java
index 3b7a115..f58336a 100644
--- a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDestinationOverrideLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDestinationOverrideLibraryTest.java
@@ -4,10 +4,6 @@
 
 package com.android.tools.r8.optimize.proto;
 
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertThrows;
-
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
@@ -44,26 +40,19 @@
 
   @Test
   public void testR8() throws Exception {
-    // TODO(b/258720808): We should not fail compilation.
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForR8(parameters.getBackend())
-                .addProgramClasses(Main.class, ProgramClass.class, X.class)
-                .addDefaultRuntimeLibrary(parameters)
-                .addLibraryClasses(LibraryClass.class)
-                .setMinApi(parameters.getApiLevel())
-                .addKeepMainRule(Main.class)
-                .addDontObfuscate()
-                .enableInliningAnnotations()
-                .enableNoMethodStaticizingAnnotations()
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      diagnostics.assertErrorMessageThatMatches(
-                          containsString(
-                              "went from not overriding a library method to overriding a library"
-                                  + " method"));
-                    }));
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, ProgramClass.class, X.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addLibraryClasses(LibraryClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addDontObfuscate()
+        .enableInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
+        .compile()
+        .addBootClasspathClasses(LibraryClass.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
   }
 
   public static class LibraryClass {
diff --git a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDuplicateMethodTest.java b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDuplicateMethodTest.java
new file mode 100644
index 0000000..c935224
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDuplicateMethodTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2022, 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.optimize.proto;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ProtoNormalizationDuplicateMethodTest extends TestBase {
+
+  private final String[] EXPECTED = new String[] {"Base::foo-7Calling Sub::foo8"};
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addKeepMethodRules(
+            Reference.methodFromMethod(
+                B.class.getDeclaredMethod("foo$1", int.class, int.class, String.class)))
+        .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @NoHorizontalClassMerging
+  public static class B {
+
+    @NeverInline
+    public void foo(int i, String s, int j) {
+      System.out.println("Base::foo-" + i + s + j);
+    }
+
+    @NeverInline
+    public void foo$1(int i, int j, String s) {
+      throw new RuntimeException("Should never be called: " + i + j + s);
+    }
+  }
+
+  @NoHorizontalClassMerging
+  public static class A {
+
+    @NeverInline
+    public void foo(String s, int i, int j) {
+      System.out.println("B-" + i + s + j);
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      if (System.currentTimeMillis() == 0) {
+        new A().foo("Calling Sub::foo", 3, 4);
+        new B().foo$1(5, 6, "Calling Sub::foo");
+      }
+      new B().foo(7, "Calling Sub::foo", 8);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationIntroduceCollisionTest.java b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationIntroduceCollisionTest.java
new file mode 100644
index 0000000..947e3e7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationIntroduceCollisionTest.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2022, 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.optimize.proto;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ProtoNormalizationIntroduceCollisionTest extends TestBase {
+
+  private final String[] EXPECTED = new String[] {"Base::foo-42Calling B::foo1337"};
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class, Base.class, Sub.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(Base.class)
+        .enableInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  public static class Base {
+
+    @NeverInline
+    public void foo(int i, int j, String s) {
+      System.out.println("Base::foo-" + i + s + j);
+    }
+  }
+
+  @NoVerticalClassMerging
+  public static class Sub extends Base {
+
+    @NeverInline
+    public void foo(String s, int i, int j) {
+      System.out.println("Sub::foo-" + s + i + j);
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      if (System.currentTimeMillis() == 0) {
+        callFoo(new Base());
+        new Sub().foo("Calling Sub::foo", 1, 2);
+      }
+      callFoo(new Sub());
+    }
+
+    public static void callFoo(Base b) {
+      b.foo(42, 1337, "Calling B::foo");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithVirtualMethodCollisionTest.java b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithVirtualMethodCollisionTest.java
index c179493..c3997c9 100644
--- a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithVirtualMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithVirtualMethodCollisionTest.java
@@ -36,9 +36,6 @@
   private final String[] EXPECTED =
       new String[] {"A::foo", "B", "A", "B::foo", "B", "A", "B::foo", "B", "A"};
 
-  private final String[] R8_EXPECTED =
-      new String[] {"A::foo", "B", "A", "A::foo", "B", "A", "B::foo", "B", "A"};
-
   @Test
   public void testRuntime() throws Exception {
     testForRuntime(parameters)
@@ -59,8 +56,7 @@
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/258720808): We should not produce incorrect results.
-        .assertSuccessWithOutputLines(R8_EXPECTED)
+        .assertSuccessWithOutputLines(EXPECTED)
         .inspect(
             inspector -> {
               ClassSubject bClassSubject = inspector.clazz(B.class);
@@ -72,7 +68,7 @@
               TypeSubject bTypeSubject = bClassSubject.asTypeSubject();
               TypeSubject aTypeSubject = aClassSubject.asTypeSubject();
 
-              MethodSubject fooMethodSubject = aClassSubject.uniqueMethodWithOriginalName("foo");
+              MethodSubject fooMethodSubject = aClassSubject.uniqueMethodWithFinalName("foo$1");
               assertThat(fooMethodSubject, isPresent());
               assertThat(fooMethodSubject, hasParameters(aTypeSubject, bTypeSubject));
 
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 63efc58..8108730 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -1239,25 +1239,47 @@
     return setPredictiveLineNumbering(predicate, 0);
   }
 
+  public interface LineTranslation {
+    int translate(MethodContext context, int line);
+  }
+
+  private static class IncrementingLineNumbers implements LineTranslation {
+    private final MethodPredicate predicate;
+    private final int startingLineNumber;
+    private final Map<MethodReference, Integer> lines = new HashMap<>();
+
+    public IncrementingLineNumbers(MethodPredicate predicate, int startingLineNumber) {
+      this.predicate = predicate;
+      this.startingLineNumber = startingLineNumber;
+    }
+
+    @Override
+    public int translate(MethodContext context, int line) {
+      if (MethodPredicate.testContext(predicate, context)) {
+        MethodReference method = context.getReference();
+        int nextLine = lines.getOrDefault(method, startingLineNumber);
+        // Increment the actual line content by 100 so that each one is clearly distinct
+        // from a PC value for any of the methods.
+        lines.put(method, nextLine + 100);
+        return nextLine;
+      }
+      return line;
+    }
+  }
+
   public ClassFileTransformer setPredictiveLineNumbering(
       MethodPredicate predicate, int startingLineNumber) {
+    return setPredictiveLineNumbering(new IncrementingLineNumbers(predicate, startingLineNumber));
+  }
+
+  public ClassFileTransformer setPredictiveLineNumbering(LineTranslation translation) {
     return addMethodTransformer(
         new MethodTransformer() {
-          private final Map<MethodReference, Integer> lines = new HashMap<>();
-
           @Override
           public void visitLineNumber(int line, Label start) {
-            if (MethodPredicate.testContext(predicate, getContext())) {
-              Integer nextLine =
-                  lines.getOrDefault(getContext().getReference(), startingLineNumber);
-              if (nextLine > 0) {
-                super.visitLineNumber(nextLine, start);
-              }
-              // Increment the actual line content by 100 so that each one is clearly distinct
-              // from a PC value for any of the methods.
-              lines.put(getContext().getReference(), nextLine + 100);
-            } else {
-              super.visitLineNumber(line, start);
+            int newLine = translation.translate(getContext(), line);
+            if (newLine >= 0) {
+              super.visitLineNumber(newLine, start);
             }
           }
         });
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index e353ee6..ba1898d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -260,7 +260,7 @@
   }
 
   @Override
-  public String disassembleUsingJavap(boolean verbose) throws Exception {
+  public String javap(boolean verbose) throws Exception {
     throw new Unreachable("Cannot disassembly an absent class");
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 0b2b52c..abcdc0f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -264,7 +264,11 @@
 
   public abstract ClassNamingForNameMapper getNaming();
 
-  public abstract String disassembleUsingJavap(boolean verbose) throws Exception;
+  public abstract String javap(boolean verbose) throws Exception;
+
+  public void disassembleUsingJavap(boolean verbose) throws Exception {
+    System.out.println(javap(true));
+  }
 
   public abstract String asmify(TemporaryFolder tempFolder, boolean debug) throws Exception;
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index ff35c6e..99489c7 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -627,7 +627,7 @@
   }
 
   @Override
-  public String disassembleUsingJavap(boolean verbose) throws Exception {
+  public String javap(boolean verbose) throws Exception {
     assert dexClass.origin != null;
     List<String> command = new ArrayList<>();
     command.add(
@@ -644,7 +644,6 @@
     command.add(parts.get(1).replace(".class", ""));
     ProcessResult processResult = ToolHelper.runProcess(new ProcessBuilder(command));
     assert processResult.exitCode == 0;
-    System.out.println(processResult.stdout);
     return processResult.stdout;
   }
 
diff --git a/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1 b/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
index 6a3c83d..a75d743 100644
--- a/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
+++ b/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
@@ -1 +1 @@
-9f148123a86ba0f40e58a71f9ad59c723ecb4607
\ No newline at end of file
+baf1112d9c5ee962bf5085a81289f43b9f38697e
\ No newline at end of file
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 8149313..64a2a31 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -404,6 +404,13 @@
   return len(version) == 40
 
 def run1(out, args, otherargs, jdkhome=None):
+  jvmargs = []
+  compilerargs = []
+  for arg in otherargs:
+    if arg.startswith('-D'):
+      jvmargs.append(arg)
+    else:
+      compilerargs.append(arg)
   with utils.TempDir() as temp:
     if out:
       temp = out
@@ -429,6 +436,7 @@
     prepare_r8_wrapper(jar, temp, jdkhome)
     prepare_d8_wrapper(jar, temp, jdkhome)
     cmd = [jdk.GetJavaExecutable(jdkhome)]
+    cmd.extend(jvmargs)
     if args.debug_agent:
       if not args.nolib:
         print("WARNING: Running debugging agent on r8lib is questionable...")
@@ -500,7 +508,7 @@
       cmd.extend(['--enable-missing-library-api-modeling'])
     if args.threads:
       cmd.extend(['--threads', args.threads])
-    cmd.extend(otherargs)
+    cmd.extend(compilerargs)
     utils.PrintCmd(cmd)
     try:
       print(subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode('utf-8'))