Merge commit '254a39d21274c47cdaed865ec0a5f0bb7ba51896' into dev-release
diff --git a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
index b02dc00..3ac81cd 100644
--- a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
+++ b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
@@ -28,6 +28,7 @@
           "j$/nio/file/spi/FileTypeDetector",
           "j$/nio/file/Path",
           "j$/nio/file/WatchEvent",
+          "j$/nio/file/WatchEvent$Kind",
           "j$/nio/file/OpenOption",
           "j$/nio/file/attribute/FileAttribute");
 
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
index 6d66d0a..01c493f 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
@@ -31,5 +31,11 @@
 
   String methodName() default "";
 
+  String methodReturnType() default "";
+
+  String[] methodParameters() default {""};
+
   String fieldName() default "";
+
+  String fieldType() default "";
 }
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 260c31c..0018dfc 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
@@ -37,8 +37,23 @@
   // Implicit hidden item which is "super type" of Condition and Target.
   public static final class Item {
     public static final String classConstant = "classConstant";
+
     public static final String methodName = "methodName";
+    public static final String methodReturnType = "methodReturnType";
+    public static final String methodParameters = "methodParameters";
+
     public static final String fieldName = "fieldName";
+    public static final String fieldType = "fieldType";
+
+    // Default values for the optional entries. The defaults should be chosen such that they do
+    // not coincide with any actual valid value. E.g., the empty string in place of a name or type.
+    // These must be 1:1 with the value defined on the actual annotation definition.
+    public static final String methodNameDefaultValue = "";
+    public static final String methodReturnTypeDefaultValue = "";
+    public static final String[] methodParametersDefaultValue = new String[] {""};
+
+    public static final String fieldNameDefaultValue = "";
+    public static final String fieldTypeDefaultValue = "";
   }
 
   public static final class Condition {
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 f87e32c..73559d6 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
@@ -8,6 +8,20 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * A target for a keep edge.
+ *
+ * <p>The target denotes a keep item along with options for what to keep:
+ *
+ * <ul>
+ *   <li>a class, or pattern on classes;
+ *   <li>a method, or pattern on methods; or
+ *   <li>a field, or pattern on fields.
+ * </ul>
+ *
+ * <p>The structure of a target item is the same as for a condition item but has the additional keep
+ * options.
+ */
 @Target(ElementType.ANNOTATION_TYPE)
 @Retention(RetentionPolicy.CLASS)
 public @interface KeepTarget {
@@ -17,5 +31,11 @@
 
   String methodName() default "";
 
+  String methodReturnType() default "";
+
+  String[] methodParameters() default {""};
+
   String fieldName() default "";
+
+  String fieldType() 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 3545a25..672a99e7 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
@@ -14,15 +14,23 @@
 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.KeepFieldTypePattern;
 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;
+import com.android.tools.r8.keepanno.ast.KeepMethodParametersPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
+import com.android.tools.r8.keepanno.ast.KeepMethodReturnTypePattern;
 import com.android.tools.r8.keepanno.ast.KeepPreconditions;
 import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepTarget;
+import com.android.tools.r8.keepanno.ast.KeepTypePattern;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
+import java.util.function.Consumer;
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
@@ -115,14 +123,24 @@
     }
 
     private KeepItemPattern createItemContext() {
-      Type returnType = Type.getReturnType(methodDescriptor);
+      String returnTypeDescriptor = Type.getReturnType(methodDescriptor).getDescriptor();
       Type[] argumentTypes = Type.getArgumentTypes(methodDescriptor);
-      // TODO(b/248408342): Defaults are "any", support setting actual return type and params.
+      KeepMethodParametersPattern.Builder builder = KeepMethodParametersPattern.builder();
+      for (Type type : argumentTypes) {
+        builder.addParameterTypePattern(KeepTypePattern.fromDescriptor(type.getDescriptor()));
+      }
+      KeepMethodReturnTypePattern returnTypePattern =
+          "V".equals(returnTypeDescriptor)
+              ? KeepMethodReturnTypePattern.voidType()
+              : KeepMethodReturnTypePattern.fromType(
+                  KeepTypePattern.fromDescriptor(returnTypeDescriptor));
       return KeepItemPattern.builder()
           .setClassPattern(KeepQualifiedClassNamePattern.exact(className))
           .setMemberPattern(
               KeepMethodPattern.builder()
                   .setNamePattern(KeepMethodNamePattern.exact(methodName))
+                  .setReturnTypePattern(returnTypePattern)
+                  .setParametersPattern(builder.build())
                   .build())
           .build();
     }
@@ -159,12 +177,14 @@
     }
 
     private KeepItemPattern createItemContext() {
-      // TODO(b/248408342): Default type is "any", support setting actual field type.
+      KeepFieldTypePattern typePattern =
+          KeepFieldTypePattern.fromType(KeepTypePattern.fromDescriptor(fieldDescriptor));
       return KeepItemPattern.builder()
           .setClassPattern(KeepQualifiedClassNamePattern.exact(className))
           .setMemberPattern(
               KeepFieldPattern.builder()
                   .setNamePattern(KeepFieldNamePattern.exact(fieldName))
+                  .setTypePattern(typePattern)
                   .build())
           .build();
     }
@@ -320,13 +340,33 @@
     private final Parent<KeepItemPattern> parent;
 
     private KeepQualifiedClassNamePattern classNamePattern = null;
-    private KeepMethodNamePattern methodName = null;
-    private KeepFieldNamePattern fieldName = null;
+    private KeepMethodPattern.Builder lazyMethodBuilder = null;
+    private KeepFieldPattern.Builder lazyFieldBuilder = null;
 
     public KeepItemVisitorBase(Parent<KeepItemPattern> parent) {
       this.parent = parent;
     }
 
+    private KeepMethodPattern.Builder methodBuilder() {
+      if (lazyFieldBuilder != null) {
+        throw new KeepEdgeException("Cannot define both a field and a method pattern");
+      }
+      if (lazyMethodBuilder == null) {
+        lazyMethodBuilder = KeepMethodPattern.builder();
+      }
+      return lazyMethodBuilder;
+    }
+
+    private KeepFieldPattern.Builder fieldBuilder() {
+      if (lazyMethodBuilder != null) {
+        throw new KeepEdgeException("Cannot define both a field and a method pattern");
+      }
+      if (lazyFieldBuilder == null) {
+        lazyFieldBuilder = KeepFieldPattern.builder();
+      }
+      return lazyFieldBuilder;
+    }
+
     @Override
     public void visit(String name, Object value) {
       if (name.equals(Item.classConstant) && value instanceof Type) {
@@ -334,36 +374,100 @@
         return;
       }
       if (name.equals(Item.methodName) && value instanceof String) {
-        methodName = KeepMethodNamePattern.exact((String) value);
+        String methodName = (String) value;
+        if (!Item.methodNameDefaultValue.equals(methodName)) {
+          methodBuilder().setNamePattern(KeepMethodNamePattern.exact(methodName));
+        }
+        return;
+      }
+      if (name.equals(Item.methodReturnType) && value instanceof String) {
+        String returnType = (String) value;
+        if (!Item.methodReturnTypeDefaultValue.equals(returnType)) {
+          methodBuilder()
+              .setReturnTypePattern(KeepEdgeReaderUtils.methodReturnTypeFromString(returnType));
+        }
         return;
       }
       if (name.equals(Item.fieldName) && value instanceof String) {
-        fieldName = KeepFieldNamePattern.exact((String) value);
+        String fieldName = (String) value;
+        if (!Item.fieldNameDefaultValue.equals(fieldName)) {
+          fieldBuilder().setNamePattern(KeepFieldNamePattern.exact(fieldName));
+        }
+        return;
+      }
+      if (name.equals(Item.fieldType) && value instanceof String) {
+        String fieldType = (String) value;
+        if (!Item.fieldTypeDefaultValue.equals(fieldType)) {
+          fieldBuilder()
+              .setTypePattern(
+                  KeepFieldTypePattern.fromType(
+                      KeepEdgeReaderUtils.typePatternFromString(fieldType)));
+        }
         return;
       }
       super.visit(name, value);
     }
 
     @Override
+    public AnnotationVisitor visitArray(String name) {
+      if (name.equals(Item.methodParameters)) {
+        return new StringArrayVisitor(
+            params -> {
+              if (Arrays.asList(Item.methodParametersDefaultValue).equals(params)) {
+                return;
+              }
+              KeepMethodParametersPattern.Builder builder = KeepMethodParametersPattern.builder();
+              for (String param : params) {
+                builder.addParameterTypePattern(KeepEdgeReaderUtils.typePatternFromString(param));
+              }
+              methodBuilder().setParametersPattern(builder.build());
+            });
+      }
+      return super.visitArray(name);
+    }
+
+    @Override
     public void visitEnd() {
+      assert lazyMethodBuilder == null || lazyFieldBuilder == null;
       Builder itemBuilder = KeepItemPattern.builder();
       if (classNamePattern != null) {
         itemBuilder.setClassPattern(classNamePattern);
       }
-      if (methodName != null && fieldName != null) {
-        throw new KeepEdgeException("Cannot define both a field and a method pattern.");
+      if (lazyMethodBuilder != null) {
+        itemBuilder.setMemberPattern(lazyMethodBuilder.build());
       }
-      if (methodName != null) {
-        itemBuilder.setMemberPattern(
-            KeepMethodPattern.builder().setNamePattern(methodName).build());
-      }
-      if (fieldName != null) {
-        itemBuilder.setMemberPattern(KeepFieldPattern.builder().setNamePattern(fieldName).build());
+      if (lazyFieldBuilder != null) {
+        itemBuilder.setMemberPattern(lazyFieldBuilder.build());
       }
       parent.accept(itemBuilder.build());
     }
   }
 
+  private static class StringArrayVisitor extends AnnotationVisitorBase {
+
+    private final Consumer<List<String>> fn;
+    private final List<String> strings = new ArrayList<>();
+
+    public StringArrayVisitor(Consumer<List<String>> fn) {
+      this.fn = fn;
+    }
+
+    @Override
+    public void visit(String name, Object value) {
+      if (value instanceof String) {
+        strings.add((String) value);
+      } else {
+        super.visit(name, value);
+      }
+    }
+
+    @Override
+    public void visitEnd() {
+      super.visitEnd();
+      fn.accept(strings);
+    }
+  }
+
   private static class KeepTargetVisitor extends KeepItemVisitorBase {
 
     public KeepTargetVisitor(Parent<KeepTarget> parent) {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java
new file mode 100644
index 0000000..e18bf1e
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java
@@ -0,0 +1,69 @@
+// 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 com.android.tools.r8.keepanno.ast.KeepEdgeException;
+import com.android.tools.r8.keepanno.ast.KeepMethodReturnTypePattern;
+import com.android.tools.r8.keepanno.ast.KeepTypePattern;
+
+/**
+ * Utilities for mapping the syntax used in annotations to the keep-edge AST.
+ *
+ * <p>The AST explicitly avoids interpreting type strings as they are potentially ambiguous. These
+ * utilities define the mappings from such syntax strings into the AST.
+ */
+public class KeepEdgeReaderUtils {
+
+  public static KeepTypePattern typePatternFromString(String string) {
+    if (string.equals("<any>")) {
+      return KeepTypePattern.any();
+    }
+    return KeepTypePattern.fromDescriptor(javaTypeToDescriptor(string));
+  }
+
+  public static String javaTypeToDescriptor(String type) {
+    switch (type) {
+      case "boolean":
+        return "Z";
+      case "byte":
+        return "B";
+      case "short":
+        return "S";
+      case "int":
+        return "I";
+      case "long":
+        return "J";
+      case "float":
+        return "F";
+      case "double":
+        return "D";
+      default:
+        {
+          StringBuilder builder = new StringBuilder(type.length());
+          int i = type.length() - 1;
+          while (type.charAt(i) == ']') {
+            if (type.charAt(--i) != '[') {
+              throw new KeepEdgeException("Invalid type: " + type);
+            }
+            builder.append('[');
+            --i;
+          }
+          builder.append('L');
+          for (int j = 0; j <= i; j++) {
+            char c = type.charAt(j);
+            builder.append(c == '.' ? '/' : c);
+          }
+          builder.append(';');
+          return builder.toString();
+        }
+    }
+  }
+
+  public static KeepMethodReturnTypePattern methodReturnTypeFromString(String returnType) {
+    if ("void".equals(returnType)) {
+      return KeepMethodReturnTypePattern.voidType();
+    }
+    return KeepMethodReturnTypePattern.fromType(typePatternFromString(returnType));
+  }
+}
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 b2da25a..e1d6d4e 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
@@ -33,7 +33,7 @@
  *     ::= any
  *       | class QUALIFIED_CLASS_NAME_PATTERN extends EXTENDS_PATTERN { MEMBER_PATTERN }
  *
- *   TYPE_PATTERN ::= any
+ *   TYPE_PATTERN ::= any | exact type-descriptor
  *   PACKAGE_PATTERN ::= any | exact package-name
  *   QUALIFIED_CLASS_NAME_PATTERN ::= any | PACKAGE_PATTERN | UNQUALIFIED_CLASS_NAME_PATTERN
  *   UNQUALIFIED_CLASS_NAME_PATTERN ::= any | exact simple-class-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
index 7137033..1466fc1 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java
@@ -14,7 +14,7 @@
   public static class Builder {
 
     private KeepFieldAccessPattern accessPattern = KeepFieldAccessPattern.any();
-    private KeepFieldNamePattern namePattern = null;
+    private KeepFieldNamePattern namePattern = KeepFieldNamePattern.any();
     private KeepFieldTypePattern typePattern = KeepFieldTypePattern.any();
 
     private Builder() {}
@@ -39,9 +39,6 @@
     }
 
     public KeepFieldPattern build() {
-      if (namePattern == null) {
-        throw new KeepEdgeException("Field pattern must declare a name pattern");
-      }
       return new KeepFieldPattern(accessPattern, namePattern, 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
index beaaa55..3965699 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldTypePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldTypePattern.java
@@ -9,6 +9,10 @@
     return SomeType.ANY_TYPE_INSTANCE;
   }
 
+  public static KeepFieldTypePattern fromType(KeepTypePattern typePattern) {
+    return typePattern.isAny() ? any() : new SomeType(typePattern);
+  }
+
   public boolean isAny() {
     return isType() && asType().isAny();
   }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java
index 29442ff..73f8669 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java
@@ -3,11 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.ast;
 
+import com.google.common.collect.ImmutableList;
 import java.util.Collections;
 import java.util.List;
 
 public abstract class KeepMethodParametersPattern {
 
+  public static Builder builder() {
+    return new Builder();
+  }
+
   public static KeepMethodParametersPattern any() {
     return Any.getInstance();
   }
@@ -30,6 +35,25 @@
     return null;
   }
 
+  public static class Builder {
+    ImmutableList.Builder<KeepTypePattern> parameterPatterns = ImmutableList.builder();
+
+    private Builder() {}
+
+    public Builder addParameterTypePattern(KeepTypePattern typePattern) {
+      parameterPatterns.add(typePattern);
+      return this;
+    }
+
+    public KeepMethodParametersPattern build() {
+      List<KeepTypePattern> list = parameterPatterns.build();
+      if (list.isEmpty()) {
+        return Some.EMPTY_INSTANCE;
+      }
+      return new Some(list);
+    }
+  }
+
   private static class Some extends KeepMethodParametersPattern {
 
     private static final Some EMPTY_INSTANCE = new Some(Collections.emptyList());
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
index e00fedd..f84a4b1 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
@@ -35,11 +35,15 @@
       return self();
     }
 
-    public Builder setReturnTypeVoid() {
-      returnTypePattern = KeepMethodReturnTypePattern.voidType();
+    public Builder setReturnTypePattern(KeepMethodReturnTypePattern returnTypePattern) {
+      this.returnTypePattern = returnTypePattern;
       return self();
     }
 
+    public Builder setReturnTypeVoid() {
+      return setReturnTypePattern(KeepMethodReturnTypePattern.voidType());
+    }
+
     public Builder setParametersPattern(KeepMethodParametersPattern parametersPattern) {
       this.parametersPattern = parametersPattern;
       return self();
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java
index ccfc183..807b1bc 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java
@@ -14,6 +14,10 @@
     return VoidType.getInstance();
   }
 
+  public static KeepMethodReturnTypePattern fromType(KeepTypePattern typePattern) {
+    return typePattern.isAny() ? any() : new SomeType(typePattern);
+  }
+
   public boolean isAny() {
     return isType() && asType().isAny();
   }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java
index c7c24b5..872ad07 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java
@@ -9,7 +9,57 @@
     return Any.getInstance();
   }
 
+  public static KeepTypePattern fromDescriptor(String typeDescriptor) {
+    return new Some(typeDescriptor);
+  }
+
+  public boolean isAny() {
+    return false;
+  }
+
+  public String getDescriptor() {
+    return null;
+  }
+
+  private static class Some extends KeepTypePattern {
+
+    private final String descriptor;
+
+    private Some(String descriptor) {
+      assert descriptor != null;
+      this.descriptor = descriptor;
+    }
+
+    @Override
+    public String getDescriptor() {
+      return descriptor;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      Some some = (Some) o;
+      return descriptor.equals(some.descriptor);
+    }
+
+    @Override
+    public int hashCode() {
+      return descriptor.hashCode();
+    }
+
+    @Override
+    public String toString() {
+      return descriptor;
+    }
+  }
+
   private static class Any extends KeepTypePattern {
+
     private static final Any INSTANCE = new Any();
 
     public static Any getInstance() {
@@ -33,9 +83,7 @@
 
     @Override
     public String toString() {
-      return "*";
+      return "<any>";
     }
   }
-
-  public abstract boolean isAny();
 }
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 6781a22..286c07a 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,7 @@
 
 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.KeepFieldAccessPattern;
 import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
@@ -157,7 +158,7 @@
         .append('(')
         .append(
             parametersPattern.asList().stream()
-                .map(Object::toString)
+                .map(KeepRuleExtractor::getTypePatternString)
                 .collect(Collectors.joining(", ")))
         .append(')');
   }
@@ -185,10 +186,7 @@
   }
 
   private static StringBuilder printType(StringBuilder builder, KeepTypePattern typePattern) {
-    if (typePattern.isAny()) {
-      return builder.append("***");
-    }
-    throw new Unimplemented();
+    return builder.append(getTypePatternString(typePattern));
   }
 
   private static StringBuilder printAccess(
@@ -256,6 +254,70 @@
     }
   }
 
+  private static String getTypePatternString(KeepTypePattern typePattern) {
+    if (typePattern.isAny()) {
+      return "***";
+    }
+    return descriptorToJavaType(typePattern.getDescriptor());
+  }
+
+  private static String descriptorToJavaType(String descriptor) {
+    if (descriptor.isEmpty()) {
+      throw new KeepEdgeException("Invalid empty type descriptor");
+    }
+    if (descriptor.length() == 1) {
+      return primitiveDescriptorToJavaType(descriptor.charAt(0));
+    }
+    if (descriptor.charAt(0) == '[') {
+      return arrayDescriptorToJavaType(descriptor);
+    }
+    return classDescriptorToJavaType(descriptor);
+  }
+
+  private static String primitiveDescriptorToJavaType(char descriptor) {
+    switch (descriptor) {
+      case 'Z':
+        return "boolean";
+      case 'B':
+        return "byte";
+      case 'S':
+        return "short";
+      case 'I':
+        return "int";
+      case 'J':
+        return "long";
+      case 'F':
+        return "float";
+      case 'D':
+        return "double";
+      default:
+        throw new KeepEdgeException("Invalid primitive descriptor: " + descriptor);
+    }
+  }
+
+  private static String classDescriptorToJavaType(String descriptor) {
+    int last = descriptor.length() - 1;
+    if (descriptor.charAt(0) != 'L' || descriptor.charAt(last) != ';') {
+      throw new KeepEdgeException("Invalid class descriptor: " + descriptor);
+    }
+    return descriptor.substring(1, last).replace('/', '.');
+  }
+
+  private static String arrayDescriptorToJavaType(String descriptor) {
+    for (int i = 0; i < descriptor.length(); i++) {
+      char c = descriptor.charAt(i);
+      if (c != '[') {
+        StringBuilder builder = new StringBuilder();
+        builder.append(descriptorToJavaType(descriptor.substring(i)));
+        for (int j = 0; j < i; j++) {
+          builder.append("[]");
+        }
+        return builder.toString();
+      }
+    }
+    throw new KeepEdgeException("Invalid array descriptor: " + descriptor);
+  }
+
   private static class ItemRule {
     private final KeepTarget target;
     private final KeepOptions options;
diff --git a/src/library_desugar/java/j$/nio/file/StandardWatchEventKinds.java b/src/library_desugar/java/j$/nio/file/StandardWatchEventKinds.java
new file mode 100644
index 0000000..639b2c8
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/StandardWatchEventKinds.java
@@ -0,0 +1,13 @@
+// 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 j$.nio.file;
+
+public class StandardWatchEventKinds implements j$.nio.file.WatchEvent.Kind {
+
+  public static final j$.nio.file.WatchEvent.Kind ENTRY_CREATE = null;
+  public static final j$.nio.file.WatchEvent.Kind ENTRY_DELETE = null;
+  public static final j$.nio.file.WatchEvent.Kind ENTRY_MODIFY = null;
+  public static final j$.nio.file.WatchEvent.Kind OVERFLOW = null;
+}
diff --git a/src/library_desugar/java/j$/nio/file/WatchEvent.java b/src/library_desugar/java/j$/nio/file/WatchEvent.java
index fd977e9..41afd63 100644
--- a/src/library_desugar/java/j$/nio/file/WatchEvent.java
+++ b/src/library_desugar/java/j$/nio/file/WatchEvent.java
@@ -13,4 +13,14 @@
   public static j$.nio.file.WatchEvent<?> wrap_convert(java.nio.file.WatchEvent<?> option) {
     return null;
   }
+
+  public static java.nio.file.WatchEvent.Kind wrap_convert(j$.nio.file.WatchEvent.Kind kind) {
+    return null;
+  }
+
+  public static j$.nio.file.WatchEvent.Kind wrap_convert(java.nio.file.WatchEvent.Kind kind) {
+    return null;
+  }
+
+  public static interface Kind {}
 }
diff --git a/src/library_desugar/java/java/nio/file/WatchEventKindConversions.java b/src/library_desugar/java/java/nio/file/WatchEventKindConversions.java
new file mode 100644
index 0000000..b91a8cf
--- /dev/null
+++ b/src/library_desugar/java/java/nio/file/WatchEventKindConversions.java
@@ -0,0 +1,51 @@
+// 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 java.nio.file;
+
+/**
+ * The class java.nio.file.StandardWatchEventKinds.ENTRY_CREATE is final so it cannot be wrapped,
+ * but effectively it is used only for its 4 static fields, similarly to an enum while not being an
+ * enum. We convert them explicitely here when converting j$.nio.file.WatchEvent$Kind.
+ */
+public class WatchEventKindConversions {
+
+  public static j$.nio.file.WatchEvent.Kind convert(java.nio.file.WatchEvent.Kind kind) {
+    if (kind == null) {
+      return null;
+    }
+    if (kind == java.nio.file.StandardWatchEventKinds.ENTRY_CREATE) {
+      return j$.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
+    }
+    if (kind == java.nio.file.StandardWatchEventKinds.ENTRY_DELETE) {
+      return j$.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
+    }
+    if (kind == java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY) {
+      return j$.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
+    }
+    if (kind == java.nio.file.StandardWatchEventKinds.OVERFLOW) {
+      return j$.nio.file.StandardWatchEventKinds.OVERFLOW;
+    }
+    return j$.nio.file.WatchEvent.wrap_convert(kind);
+  }
+
+  public static java.nio.file.WatchEvent.Kind convert(j$.nio.file.WatchEvent.Kind kind) {
+    if (kind == null) {
+      return null;
+    }
+    if (kind == j$.nio.file.StandardWatchEventKinds.ENTRY_CREATE) {
+      return java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
+    }
+    if (kind == j$.nio.file.StandardWatchEventKinds.ENTRY_DELETE) {
+      return java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
+    }
+    if (kind == j$.nio.file.StandardWatchEventKinds.ENTRY_MODIFY) {
+      return java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
+    }
+    if (kind == j$.nio.file.StandardWatchEventKinds.OVERFLOW) {
+      return java.nio.file.StandardWatchEventKinds.OVERFLOW;
+    }
+    return j$.nio.file.WatchEvent.wrap_convert(kind);
+  }
+}
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index 4bcf1a3..e963121 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -283,7 +283,8 @@
       },
       "custom_conversion": {
         "java.nio.file.attribute.FileTime": "java.nio.file.attribute.FileAttributeConversions",
-        "java.nio.file.attribute.FileAttribute": "java.nio.file.attribute.FileAttributeConversions"
+        "java.nio.file.attribute.FileAttribute": "java.nio.file.attribute.FileAttributeConversions",
+        "java.nio.file.WatchEvent$Kind": "java.nio.file.WatchEventKindConversions"
       }
     },
     {
@@ -502,35 +503,11 @@
         "sun.misc.DesugarUnsafe": {
           "jdk.internal.misc.Unsafe" : "j$.sun.misc.DesugarUnsafe"
         },
-        "java.nio.file.attribute.": {
-          "j$.nio.file.attribute.": "java.nio.file.attribute."
-        },
-        "java.nio.file.OpenOption": {
-          "j$.nio.file.OpenOption": "java.nio.file.OpenOption"
-        },
-        "java.nio.file.StandardOpenOption": {
-          "j$.nio.file.StandardOpenOption": "java.nio.file.StandardOpenOption"
-        },
-        "java.nio.file.LinkOption": {
-          "j$.nio.file.LinkOption": "java.nio.file.LinkOption"
-        },
-        "java.nio.file.Files": {
-          "j$.nio.file.Files": "java.nio.file.Files"
-        },
-        "java.nio.file.FileSystem": {
-          "j$.nio.file.FileSystem": "java.nio.file.FileSystem"
-        },
-        "java.nio.file.spi.FileSystemProvider": {
-          "j$.nio.file.spi.FileSystemProvider": "java.nio.file.spi.FileSystemProvider"
-        },
-        "java.nio.file.Path": {
-          "j$.nio.file.Path": "java.nio.file.Path"
-        },
         "java.time.": {
           "j$.time.": "java.time."
         },
-        "java.nio.file.WatchEvent": {
-          "j$.nio.file.WatchEvent": "java.nio.file.WatchEvent"
+        "java.nio.file.": {
+          "j$.nio.file.": "java.nio.file."
         }
       },
       "retarget_method": {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
index 44f3fde..58e9d2a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
@@ -117,6 +117,11 @@
         if (appView.options().enableCheckAllInstructionsDuringStackMapVerification
             || instruction.canThrow()) {
           state = checkExceptionEdges(state, labelToFrameMap);
+          if (state.isError()) {
+            return fail(
+                CfCodeStackMapValidatingException.invalidStackMapForInstruction(
+                    method, i, instruction, state.asError().getMessage(), appView));
+          }
         }
       }
       eventConsumer.acceptInstructionState(instruction, state);
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 96a4596..e23386b 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -1126,11 +1126,16 @@
         boolean isValidOrder = stringIndex > prevStringIndex;
         assert isValidOrder
             : String.format(
-                "Out-of-order type ids (type #%s: `%s`, type #%s: `%s`)",
+                (indexedItems.getString(prevStringIndex).equals(indexedItems.getString(stringIndex))
+                        ? "Duplicate"
+                        : "Out-of-order")
+                    + " type ids (type #%s: `%s` string #%s, type #%s: `%s` string #%s)",
                 index - 1,
                 indexedItems.getString(prevStringIndex),
+                prevStringIndex,
                 index,
-                indexedItems.getString(stringIndex));
+                indexedItems.getString(stringIndex),
+                stringIndex);
 
         prevStringIndex = stringIndex;
       }
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 5b71a42..169a880 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -898,7 +898,8 @@
   }
 
   @Override
-  public Code getCodeAsInlining(DexMethod caller, DexMethod callee, DexItemFactory factory) {
+  public Code getCodeAsInlining(
+      DexMethod caller, DexMethod callee, DexItemFactory factory, boolean isCalleeD8R8Synthesized) {
     Position callerPosition = SyntheticPosition.builder().setLine(0).setMethod(caller).build();
     List<CfInstruction> newInstructions = new ArrayList<>(instructions.size() + 2);
     CfLabel firstLabel;
@@ -916,15 +917,18 @@
         newInstructions.add(
             new CfPosition(
                 oldPosition.getLabel(),
-                oldPosition.getPosition().withOutermostCallerPosition(callerPosition)));
+                newInlineePosition(
+                    callerPosition, oldPosition.getPosition(), isCalleeD8R8Synthesized)));
       } else {
         if (!instruction.isLabel() && !seenPosition) {
           Position preamblePosition =
-              SyntheticPosition.builder()
-                  .setMethod(callee)
-                  .setCallerPosition(callerPosition)
-                  .setLine(0)
-                  .build();
+              isCalleeD8R8Synthesized
+                  ? callerPosition
+                  : SyntheticPosition.builder()
+                      .setMethod(callee)
+                      .setCallerPosition(callerPosition)
+                      .setLine(0)
+                      .build();
           newInstructions.add(new CfPosition(firstLabel, preamblePosition));
           seenPosition = true;
         }
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index d0417b4..0475e2c 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.PositionBuilder;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
@@ -176,7 +177,35 @@
     return true;
   }
 
-  public Code getCodeAsInlining(DexMethod caller, DexMethod callee, DexItemFactory factory) {
+  public Code getCodeAsInlining(DexMethod caller, DexEncodedMethod callee, DexItemFactory factory) {
+    return getCodeAsInlining(caller, callee.getReference(), factory, callee.isD8R8Synthesized());
+  }
+
+  public Code getCodeAsInlining(
+      DexMethod caller, DexMethod callee, DexItemFactory factory, boolean isCalleeD8R8Synthesized) {
     throw new Unreachable();
   }
+
+  public Position newInlineePosition(
+      Position callerPosition, Position oldPosition, boolean isCalleeD8R8Synthesized) {
+    Position outermostCaller = oldPosition.getOutermostCaller();
+    if (!isCalleeD8R8Synthesized) {
+      return oldPosition.withOutermostCallerPosition(callerPosition);
+    }
+    // We can replace the position since the callee was synthesized by the compiler, however, if
+    // the position carries special information we need to copy it.
+    if (!outermostCaller.isOutline() && !outermostCaller.isRemoveInnerFramesIfThrowingNpe()) {
+      return oldPosition.replacePosition(outermostCaller, callerPosition);
+    }
+    assert !callerPosition.isOutline();
+    PositionBuilder<?, ?> positionBuilder =
+        outermostCaller
+            .builderWithCopy()
+            .setMethod(callerPosition.getMethod())
+            .setLine(callerPosition.getLine());
+    if (callerPosition.isRemoveInnerFramesIfThrowingNpe()) {
+      positionBuilder.setRemoveInnerFramesIfThrowingNpe(true);
+    }
+    return oldPosition.replacePosition(outermostCaller, positionBuilder.build());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 048e01d..8ca811b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -279,7 +279,8 @@
   }
 
   @Override
-  public Code getCodeAsInlining(DexMethod caller, DexMethod callee, DexItemFactory factory) {
+  public Code getCodeAsInlining(
+      DexMethod caller, DexMethod callee, DexItemFactory factory, boolean isCalleeD8R8Synthesized) {
     return new DexCode(
         registerSize,
         incomingRegisterSize,
@@ -287,11 +288,11 @@
         instructions,
         tries,
         handlers,
-        debugInfoAsInlining(caller, callee, factory));
+        debugInfoAsInlining(caller, callee, isCalleeD8R8Synthesized, factory));
   }
 
   private DexDebugInfo debugInfoAsInlining(
-      DexMethod caller, DexMethod callee, DexItemFactory factory) {
+      DexMethod caller, DexMethod callee, boolean isCalleeD8R8Synthesized, DexItemFactory factory) {
     Position callerPosition = SyntheticPosition.builder().setLine(0).setMethod(caller).build();
     EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(this, factory);
     if (eventBasedInfo == null) {
@@ -299,11 +300,13 @@
       // This is consistent with the building IR for inlining which will always ensure the method
       // has a position.
       Position preamblePosition =
-          SyntheticPosition.builder()
-              .setMethod(callee)
-              .setCallerPosition(callerPosition)
-              .setLine(0)
-              .build();
+          isCalleeD8R8Synthesized
+              ? callerPosition
+              : SyntheticPosition.builder()
+                  .setMethod(callee)
+                  .setCallerPosition(callerPosition)
+                  .setLine(0)
+                  .build();
       return new EventBasedDebugInfo(
           0,
           new DexString[callee.getArity()],
@@ -321,14 +324,17 @@
     int i = 0;
     newEvents[i++] =
         new SetPositionFrame(
-            frameBuilder.setMethod(callee).setCallerPosition(callerPosition).build());
+            isCalleeD8R8Synthesized
+                ? callerPosition
+                : frameBuilder.setMethod(callee).setCallerPosition(callerPosition).build());
     for (DexDebugEvent event : oldEvents) {
       if (event instanceof SetPositionFrame) {
         SetPositionFrame oldFrame = (SetPositionFrame) event;
         assert oldFrame.getPosition() != null;
         newEvents[i++] =
             new SetPositionFrame(
-                oldFrame.getPosition().withOutermostCallerPosition(callerPosition));
+                newInlineePosition(
+                    callerPosition, oldFrame.getPosition(), isCalleeD8R8Synthesized));
       } else {
         newEvents[i++] = event;
       }
@@ -554,7 +560,7 @@
             .append("(warning: has unhandled debug events @ pc:")
             .append(debugInfo.address)
             .append(", line:")
-            .append(debugInfo.line);
+            .append(debugInfo.getPosition().getLine());
       } else {
         builder.append("(has debug events past last pc)\n");
       }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
index f89353c..6679383 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
@@ -4,60 +4,38 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.Position.OutlineCallerPosition;
-import com.android.tools.r8.ir.code.Position.OutlineCallerPosition.OutlineCallerPositionBuilder;
-import com.android.tools.r8.ir.code.Position.OutlinePosition;
-import com.android.tools.r8.ir.code.Position.PositionBuilder;
-import com.android.tools.r8.ir.code.Position.SourcePosition;
-import com.android.tools.r8.utils.Int2StructuralItemArrayMap;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 import java.util.SortedSet;
 import java.util.TreeSet;
-import java.util.function.Function;
 
 public class DexDebugEntry {
 
   public final boolean lineEntry;
   public final int address;
-  public final int line;
   public final DexString sourceFile;
   public final boolean prologueEnd;
   public final boolean epilogueBegin;
   public final Map<Integer, DebugLocalInfo> locals;
-  public final DexMethod method;
-  public final Position callerPosition;
-  public final boolean isOutline;
-  public final DexMethod outlineCallee;
-  public final Int2StructuralItemArrayMap<Position> outlineCallerPositions;
+  private final Position position;
 
   public DexDebugEntry(
       boolean lineEntry,
       int address,
-      int line,
       DexString sourceFile,
       boolean prologueEnd,
       boolean epilogueBegin,
       ImmutableMap<Integer, DebugLocalInfo> locals,
-      DexMethod method,
-      Position callerPosition,
-      boolean isOutline,
-      DexMethod outlineCallee,
-      Int2StructuralItemArrayMap<Position> outlineCallerPositions) {
+      Position position) {
     this.lineEntry = lineEntry;
     this.address = address;
-    this.line = line;
     this.sourceFile = sourceFile;
     this.prologueEnd = prologueEnd;
     this.epilogueBegin = epilogueBegin;
     this.locals = locals;
-    this.method = method;
-    assert method != null;
-    this.callerPosition = callerPosition;
-    this.isOutline = isOutline;
-    this.outlineCallee = outlineCallee;
-    this.outlineCallerPositions = outlineCallerPositions;
+    this.position = position;
+    assert position != null;
   }
 
   @Override
@@ -74,24 +52,7 @@
     if (sourceFile != null) {
       builder.append(", file ").append(sourceFile);
     }
-    builder.append(", line ").append(line);
-    if (callerPosition != null) {
-      builder.append(":").append(method.name);
-      Position caller = callerPosition;
-      while (caller != null) {
-        builder.append(";").append(caller.getLine()).append(":").append(caller.getMethod().name);
-        caller = caller.getCallerPosition();
-      }
-    }
-    if (isOutline) {
-      builder.append(", isOutline = true");
-    }
-    if (outlineCallee != null) {
-      builder.append(", outlineCallee = ").append(outlineCallee);
-    }
-    if (outlineCallerPositions != null) {
-      builder.append(", outlineCallerPositions = ").append(outlineCallerPositions);
-    }
+    builder.append(", ").append(position);
     if (prologueEnd) {
       builder.append(", prologue_end = true");
     }
@@ -115,22 +76,11 @@
     return builder.toString();
   }
 
-  public Position toPosition(Function<Position, Position> canonicalizeCallerPosition) {
-    PositionBuilder<?, ?> positionBuilder;
-    if (outlineCallee != null) {
-      OutlineCallerPositionBuilder outlineCallerPositionBuilder =
-          OutlineCallerPosition.builder().setOutlineCallee(outlineCallee).setIsOutline(isOutline);
-      outlineCallerPositions.forEach(outlineCallerPositionBuilder::addOutlinePosition);
-      positionBuilder = outlineCallerPositionBuilder;
-    } else if (isOutline) {
-      positionBuilder = OutlinePosition.builder();
-    } else {
-      positionBuilder = SourcePosition.builder().setFile(sourceFile);
-    }
-    return positionBuilder
-        .setLine(line)
-        .setMethod(method)
-        .setCallerPosition(canonicalizeCallerPosition.apply(callerPosition))
-        .build();
+  public Position getPosition() {
+    return position;
+  }
+
+  public int getLine() {
+    return position.getLine();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
index 5a4fd84..e323ab8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
@@ -46,7 +46,6 @@
   private boolean epilogueBegin = false;
   private final Map<Integer, LocalEntry> locals = new HashMap<>();
   private final Int2ReferenceMap<DebugLocalInfo> arguments = new Int2ReferenceArrayMap<>();
-  private final DexMethod method;
 
   // Delayed construction of an entry. Is finalized once locals information has been collected.
   private DexDebugEntry pending = null;
@@ -60,13 +59,11 @@
 
   public DexDebugEntryBuilder(int startLine, DexMethod method) {
     assert method != null;
-    this.method = method;
     positionState = new DexDebugPositionState(startLine, method);
   }
 
   public DexDebugEntryBuilder(DexEncodedMethod method, DexItemFactory factory) {
     assert method != null && method.getReference() != null;
-    this.method = method.getReference();
     DexCode code = method.getCode().asDexCode();
     EventBasedDebugInfo info = code.getDebugInfo().asEventBasedInfo();
     // Only event based debug info supports conversion to entries.
@@ -164,31 +161,21 @@
           new DexDebugEntry(
               pending.lineEntry,
               pending.address,
-              pending.line,
               pending.sourceFile,
               pending.prologueEnd,
               pending.epilogueBegin,
               getLocals(),
-              pending.method,
-              pending.callerPosition,
-              pending.isOutline,
-              pending.outlineCallee,
-              pending.outlineCallerPositions));
+              pending.getPosition()));
     }
     pending =
         new DexDebugEntry(
             lineEntry,
             positionState.getCurrentPc(),
-            positionState.getCurrentLine(),
-            positionState.getCurrentFile(),
+            null,
             prologueEnd,
             epilogueBegin,
             null,
-            positionState.getCurrentMethod(),
-            positionState.getCurrentCallerPosition(),
-            positionState.isOutline(),
-            positionState.getOutlineCallee(),
-            positionState.getOutlineCallerPositions());
+            positionState.getPosition());
     prologueEnd = false;
     epilogueBegin = false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java b/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
index 10a06d5..96eb206 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
@@ -15,7 +15,8 @@
 import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.utils.Int2StructuralItemArrayMap;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 
 /**
  * State machine to process and accumulate position-related DexDebugEvents. Clients should retrieve
@@ -25,12 +26,8 @@
 
   private int currentPc = 0;
   private int currentLine;
-  private DexString currentFile = null;
-  private DexMethod currentMethod;
-  private Position currentCallerPosition = null;
-  private boolean isOutline;
-  private DexMethod outlineCallee;
-  private Int2StructuralItemArrayMap<Position> outlineCallerPositions;
+  protected DexMethod currentMethod;
+  protected Position currentPosition;
 
   public DexDebugPositionState(int startLine, DexMethod method) {
     currentLine = startLine;
@@ -53,10 +50,7 @@
     assert setPositionFrame.getPosition() != null;
     Position position = setPositionFrame.getPosition();
     currentMethod = position.getMethod();
-    currentCallerPosition = position.getCallerPosition();
-    outlineCallee = position.getOutlineCallee();
-    outlineCallerPositions = position.getOutlinePositions();
-    isOutline = position.isOutline();
+    currentPosition = position;
   }
 
   @Override
@@ -68,7 +62,7 @@
 
   @Override
   public void visit(SetFile setFile) {
-    currentFile = setFile.fileName;
+    // Empty.
   }
 
   @Override
@@ -104,33 +98,14 @@
     return currentLine;
   }
 
-  public DexString getCurrentFile() {
-    return currentFile;
-  }
-
-  public DexMethod getCurrentMethod() {
-    return currentMethod;
-  }
-
-  public Position getCurrentCallerPosition() {
-    return currentCallerPosition;
-  }
-
-  public boolean isOutline() {
-    return isOutline;
-  }
-
-  public DexMethod getOutlineCallee() {
-    return outlineCallee;
-  }
-
-  public Int2StructuralItemArrayMap<Position> getOutlineCallerPositions() {
-    return outlineCallerPositions;
-  }
-
-  public void resetOutlineInformation() {
-    isOutline = false;
-    outlineCallee = null;
-    outlineCallerPositions = null;
+  public Position getPosition() {
+    if (currentPosition == null) {
+      return (getCurrentLine() > 0 ? SourcePosition.builder() : SyntheticPosition.builder())
+          .setLine(getCurrentLine())
+          .setMethod(currentMethod)
+          .build();
+    } else {
+      return currentPosition.builderWithCopy().setLine(getCurrentLine()).build();
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 888bbfe..94b8840 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -2289,7 +2289,7 @@
     private final Set<DexString> varHandleSetMethods =
         createStrings(setString, "setOpaque", "setRelease", "setVolatile");
 
-    private final Set<DexString> varHandleCompareAndSetMethods =
+    public final Set<DexString> varHandleCompareAndSetMethodNames =
         createStrings(
             compareAndSetString,
             "weakCompareAndSet",
@@ -2308,7 +2308,7 @@
           result = createMethod(varHandleType, signature, invokeProto.name);
         } else if (varHandleSetMethods.contains(invokeProto.name)) {
           result = createMethod(varHandleType, setSignature, invokeProto.name);
-        } else if (varHandleCompareAndSetMethods.contains(invokeProto.name)) {
+        } else if (varHandleCompareAndSetMethodNames.contains(invokeProto.name)) {
           result = createMethod(varHandleType, compareAndSetSignature, invokeProto.name);
         }
       }
@@ -2333,7 +2333,7 @@
       if (invokeProto.holder == varHandleType) {
         return varHandleMethods.contains(invokeProto.name)
             || varHandleSetMethods.contains(invokeProto.name)
-            || varHandleCompareAndSetMethods.contains(invokeProto.name);
+            || varHandleCompareAndSetMethodNames.contains(invokeProto.name);
       }
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 3abc2e4..d98d0ea 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -175,7 +175,7 @@
   }
 
   @Override
-  public Code getCodeAsInlining(DexMethod caller, DexMethod callee, DexItemFactory factory) {
+  public Code getCodeAsInlining(DexMethod caller, DexEncodedMethod callee, DexItemFactory factory) {
     return asCfCode().getCodeAsInlining(caller, callee, factory);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
index 653e20e..5e88574 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -46,10 +46,6 @@
     return false;
   }
 
-  public boolean isAdditionalMappingInfoPosition() {
-    return false;
-  }
-
   public boolean isRemoveInnerFramesIfThrowingNpe() {
     return removeInnerFramesIfThrowingNpe;
   }
@@ -201,7 +197,8 @@
     return HashCodeVisitor.run(this);
   }
 
-  private String toString(boolean forceMethod) {
+  @Override
+  public String toString() {
     if (isNone()) {
       return "--";
     }
@@ -210,7 +207,7 @@
       builder.append(getFile()).append(":");
     }
     builder.append("#").append(line);
-    if (method != null && (forceMethod || callerPosition != null)) {
+    if (method != null && callerPosition != null) {
       builder.append(":").append(method.name);
     }
     if (callerPosition != null) {
@@ -220,14 +217,18 @@
         caller = caller.callerPosition;
       }
     }
+    if (isOutline()) {
+      builder.append(", isOutline = true");
+    }
+    if (getOutlineCallee() != null) {
+      builder.append(", outlineCallee = ").append(getOutlineCallee());
+    }
+    if (getOutlinePositions() != null) {
+      builder.append(", outlineCallerPositions = ").append(getOutlinePositions());
+    }
     return builder.toString();
   }
 
-  @Override
-  public String toString() {
-    return toString(false);
-  }
-
   public abstract PositionBuilder<?, ?> builderWithCopy();
 
   public abstract static class PositionBuilder<
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 3fbcdc8..ea0f655 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -259,10 +259,15 @@
 
   private Position getCanonicalPositionAppendCaller(DexDebugEntry entry) {
     // If this instruction has already been inlined then this.method must be the outermost caller.
-    assert entry.callerPosition == null
-        || entry.callerPosition.getOutermostCaller().getMethod() == originalMethod;
+    Position position = entry.getPosition();
+    assert !position.hasCallerPosition()
+        || position.getOutermostCaller().getMethod() == originalMethod;
     return canonicalPositions.getCanonical(
-        entry.toPosition(canonicalPositions::canonicalizeCallerPosition));
+        position
+            .builderWithCopy()
+            .setCallerPosition(
+                canonicalPositions.canonicalizeCallerPosition(position.getCallerPosition()))
+            .build());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
index 8d2c8c6..adf23fa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -210,7 +210,7 @@
         definition
             .getCode()
             .getCodeAsInlining(
-                companion.getReference(), method.getReference(), appView.dexItemFactory());
+                companion.getReference(), method.getDefinition(), appView.dexItemFactory());
     if (!definition.isStatic()) {
       DexEncodedMethod.setDebugInfoWithFakeThisParameter(
           code, companion.getReference().getArity(), appView);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java
index 9ed7493..ae232bd 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java
@@ -379,7 +379,7 @@
   }
 
   private DexType objectOrPrimitiveReturnType(DexType type) {
-    return type.isPrimitiveType() || type.isVoidType() ? type : factory.objectType;
+    return isPrimitiveThatIsNotBoxed(type) || type.isVoidType() ? type : factory.objectType;
   }
 
   private DexType objectOrPrimitiveParameterType(DexType type) {
@@ -456,13 +456,23 @@
       }
     }
     assert newParameters.size() == proto.parameters.size();
-    // TODO(b/247076137): Also convert return type if reference type and not Object?.
+    DexString name = invoke.getMethod().getName();
     DexProto newProto =
-        factory.createProto(objectOrPrimitiveReturnType(proto.returnType), newParameters);
-    DexMethod newMethod =
-        factory.createMethod(factory.desugarVarHandleType, newProto, invoke.getMethod().getName());
+        factory.createProto(
+            factory.polymorphicMethods.varHandleCompareAndSetMethodNames.contains(name)
+                ? proto.returnType
+                : objectOrPrimitiveReturnType(proto.returnType),
+            newParameters);
+    DexMethod newMethod = factory.createMethod(factory.desugarVarHandleType, newProto, name);
     builder.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, newMethod, false));
-    if (proto.returnType.isClassType()
+    if (proto.returnType.isPrimitiveType() && !newProto.returnType.isPrimitiveType()) {
+      assert proto.returnType.isPrimitiveType();
+      assert newProto.returnType == factory.objectType;
+      builder.add(new CfCheckCast(factory.getBoxedForPrimitiveType(proto.returnType)));
+      builder.add(
+          new CfInvoke(
+              Opcodes.INVOKEVIRTUAL, factory.getUnboxPrimitiveMethod(proto.returnType), false));
+    } else if (proto.returnType.isClassType()
         && proto.returnType != factory.objectType
         && proto.returnType != factory.voidType) {
       builder.add(new CfCheckCast(proto.returnType));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java
index e32fa57..112f246 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java
@@ -52,6 +52,7 @@
   public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
     factory.createSynthesizedType("Lcom/android/tools/r8/DesugarVarHandle;");
     factory.createSynthesizedType("Ljava/lang/Byte;");
+    factory.createSynthesizedType("Ljava/lang/ClassCastException;");
     factory.createSynthesizedType("Ljava/lang/Integer;");
     factory.createSynthesizedType("Ljava/lang/Long;");
     factory.createSynthesizedType("Ljava/lang/RuntimeException;");
@@ -98,6 +99,11 @@
                 .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
                 .disableAndroidApiLevelCheck()
                 .build()));
+    DexMethod toIntIfPossible =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(factory.intType, factory.objectType, factory.booleanType),
+            factory.createString("toIntIfPossible"));
     DexMethod get =
         factory.createMethod(
             builder.getType(),
@@ -120,11 +126,6 @@
             builder.getType(),
             factory.createProto(factory.voidType, factory.objectType, factory.intType),
             factory.createString("set"));
-    DexMethod toLongIfPossible =
-        factory.createMethod(
-            builder.getType(),
-            factory.createProto(factory.longType, factory.objectType),
-            factory.createString("toLongIfPossible"));
     DexMethod constructor_3 =
         factory.createMethod(
             builder.getType(),
@@ -145,6 +146,11 @@
             factory.createProto(
                 factory.booleanType, factory.objectType, factory.longType, factory.longType),
             factory.createString("compareAndSet"));
+    DexMethod toLongIfPossible =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(factory.longType, factory.objectType, factory.booleanType),
+            factory.createString("toLongIfPossible"));
     DexMethod set =
         factory.createMethod(
             builder.getType(),
@@ -167,11 +173,6 @@
             builder.getType(),
             factory.createProto(factory.intType, factory.objectType),
             factory.createString("get"));
-    DexMethod toIntIfPossible =
-        factory.createMethod(
-            builder.getType(),
-            factory.createProto(factory.intType, factory.objectType),
-            factory.createString("toIntIfPossible"));
     DexMethod getLong =
         factory.createMethod(
             builder.getType(),
@@ -198,6 +199,14 @@
     builder.setVirtualMethods(
         ImmutableList.of(
             DexEncodedMethod.syntheticBuilder()
+                .setMethod(toIntIfPossible)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_toIntIfPossible(factory, toIntIfPossible))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
                 .setMethod(get)
                 .setAccessFlags(
                     MethodAccessFlags.fromSharedAccessFlags(
@@ -222,14 +231,6 @@
                 .disableAndroidApiLevelCheck()
                 .build(),
             DexEncodedMethod.syntheticBuilder()
-                .setMethod(toLongIfPossible)
-                .setAccessFlags(
-                    MethodAccessFlags.fromSharedAccessFlags(
-                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
-                .setCode(DesugarVarHandle_toLongIfPossible(factory, toLongIfPossible))
-                .disableAndroidApiLevelCheck()
-                .build(),
-            DexEncodedMethod.syntheticBuilder()
                 .setMethod(setLong)
                 .setAccessFlags(
                     MethodAccessFlags.fromSharedAccessFlags(
@@ -246,6 +247,14 @@
                 .disableAndroidApiLevelCheck()
                 .build(),
             DexEncodedMethod.syntheticBuilder()
+                .setMethod(toLongIfPossible)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_toLongIfPossible(factory, toLongIfPossible))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
                 .setMethod(set)
                 .setAccessFlags(
                     MethodAccessFlags.fromSharedAccessFlags(
@@ -280,14 +289,6 @@
                 .disableAndroidApiLevelCheck()
                 .build(),
             DexEncodedMethod.syntheticBuilder()
-                .setMethod(toIntIfPossible)
-                .setAccessFlags(
-                    MethodAccessFlags.fromSharedAccessFlags(
-                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
-                .setCode(DesugarVarHandle_toIntIfPossible(factory, toIntIfPossible))
-                .disableAndroidApiLevelCheck()
-                .build(),
-            DexEncodedMethod.syntheticBuilder()
                 .setMethod(getLong)
                 .setAccessFlags(
                     MethodAccessFlags.fromSharedAccessFlags(
@@ -659,7 +660,7 @@
     CfLabel label9 = new CfLabel();
     return new CfCode(
         method.holder,
-        8,
+        9,
         4,
         ImmutableList.of(
             label0,
@@ -691,21 +692,23 @@
                     factory.createString("offset"))),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.OBJECT, 2),
+            new CfConstNumber(0, ValueType.INT),
             label2,
             new CfInvoke(
                 182,
                 factory.createMethod(
                     factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
-                    factory.createProto(factory.intType, factory.objectType),
+                    factory.createProto(factory.intType, factory.objectType, factory.booleanType),
                     factory.createString("toIntIfPossible")),
                 false),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.OBJECT, 3),
+            new CfConstNumber(0, ValueType.INT),
             new CfInvoke(
                 182,
                 factory.createMethod(
                     factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
-                    factory.createProto(factory.intType, factory.objectType),
+                    factory.createProto(factory.intType, factory.objectType, factory.booleanType),
                     factory.createString("toIntIfPossible")),
                 false),
             label3,
@@ -761,21 +764,23 @@
                     factory.createString("offset"))),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.OBJECT, 2),
+            new CfConstNumber(0, ValueType.INT),
             label6,
             new CfInvoke(
                 182,
                 factory.createMethod(
                     factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
-                    factory.createProto(factory.longType, factory.objectType),
+                    factory.createProto(factory.longType, factory.objectType, factory.booleanType),
                     factory.createString("toLongIfPossible")),
                 false),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.OBJECT, 3),
+            new CfConstNumber(0, ValueType.INT),
             new CfInvoke(
                 182,
                 factory.createMethod(
                     factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
-                    factory.createProto(factory.longType, factory.objectType),
+                    factory.createProto(factory.longType, factory.objectType, factory.booleanType),
                     factory.createString("toLongIfPossible")),
                 false),
             label7,
@@ -1259,9 +1264,13 @@
   public static CfCode DesugarVarHandle_getInt(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
     return new CfCode(
         method.holder,
-        4,
+        5,
         2,
         ImmutableList.of(
             label0,
@@ -1269,6 +1278,19 @@
             new CfInstanceFieldRead(
                 factory.createField(
                     factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Ljava/lang/Integer;"),
+                    factory.classType,
+                    factory.createString("TYPE"))),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label2),
+            label1,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
                     factory.createType("Lsun/misc/Unsafe;"),
                     factory.createString("U"))),
             new CfLoad(ValueType.OBJECT, 1),
@@ -1286,7 +1308,90 @@
                     factory.createString("getInt")),
                 false),
             new CfReturn(ValueType.INT),
-            label1),
+            label2,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Ljava/lang/Long;"),
+                    factory.classType,
+                    factory.createString("TYPE"))),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label4),
+            label3,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(factory.longType, factory.objectType, factory.longType),
+                    factory.createString("getLong")),
+                false),
+            new CfNumberConversion(NumericType.LONG, NumericType.INT),
+            new CfReturn(ValueType.INT),
+            label4,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(factory.objectType, factory.objectType, factory.longType),
+                    factory.createString("getObject")),
+                false),
+            new CfConstNumber(1, ValueType.INT),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createProto(factory.intType, factory.objectType, factory.booleanType),
+                    factory.createString("toIntIfPossible")),
+                false),
+            new CfReturn(ValueType.INT),
+            label5),
         ImmutableList.of(),
         ImmutableList.of());
   }
@@ -1294,9 +1399,13 @@
   public static CfCode DesugarVarHandle_getLong(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
     return new CfCode(
         method.holder,
-        4,
+        5,
         2,
         ImmutableList.of(
             label0,
@@ -1304,6 +1413,19 @@
             new CfInstanceFieldRead(
                 factory.createField(
                     factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Ljava/lang/Long;"),
+                    factory.classType,
+                    factory.createString("TYPE"))),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label2),
+            label1,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
                     factory.createType("Lsun/misc/Unsafe;"),
                     factory.createString("U"))),
             new CfLoad(ValueType.OBJECT, 1),
@@ -1321,7 +1443,90 @@
                     factory.createString("getLong")),
                 false),
             new CfReturn(ValueType.LONG),
-            label1),
+            label2,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Ljava/lang/Integer;"),
+                    factory.classType,
+                    factory.createString("TYPE"))),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label4),
+            label3,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(factory.intType, factory.objectType, factory.longType),
+                    factory.createString("getInt")),
+                false),
+            new CfNumberConversion(NumericType.INT, NumericType.LONG),
+            new CfReturn(ValueType.LONG),
+            label4,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(factory.objectType, factory.objectType, factory.longType),
+                    factory.createString("getObject")),
+                false),
+            new CfConstNumber(1, ValueType.INT),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createProto(factory.longType, factory.objectType, factory.booleanType),
+                    factory.createString("toLongIfPossible")),
+                false),
+            new CfReturn(ValueType.LONG),
+            label5),
         ImmutableList.of(),
         ImmutableList.of());
   }
@@ -1357,11 +1562,12 @@
             new CfLoad(ValueType.OBJECT, 1),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.OBJECT, 2),
+            new CfConstNumber(0, ValueType.INT),
             new CfInvoke(
                 182,
                 factory.createMethod(
                     factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
-                    factory.createProto(factory.intType, factory.objectType),
+                    factory.createProto(factory.intType, factory.objectType, factory.booleanType),
                     factory.createString("toIntIfPossible")),
                 false),
             new CfInvoke(
@@ -1399,11 +1605,12 @@
             new CfLoad(ValueType.OBJECT, 1),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.OBJECT, 2),
+            new CfConstNumber(0, ValueType.INT),
             new CfInvoke(
                 182,
                 factory.createMethod(
                     factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
-                    factory.createProto(factory.longType, factory.objectType),
+                    factory.createProto(factory.longType, factory.objectType, factory.booleanType),
                     factory.createString("toLongIfPossible")),
                 false),
             new CfInvoke(
@@ -1608,6 +1815,8 @@
     CfLabel label2 = new CfLabel();
     CfLabel label3 = new CfLabel();
     CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    CfLabel label6 = new CfLabel();
     return new CfCode(
         method.holder,
         6,
@@ -1649,7 +1858,7 @@
                         factory.voidType, factory.objectType, factory.longType, factory.longType),
                     factory.createString("putLong")),
                 false),
-            new CfGoto(label3),
+            new CfGoto(label5),
             label2,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
@@ -1662,6 +1871,19 @@
                       FrameType.longHighType()
                     })),
             new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Ljava/lang/Integer;"),
+                    factory.classType,
+                    factory.createString("TYPE"))),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label4),
+            label3,
+            new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 182,
                 factory.createMethod(
@@ -1670,7 +1892,47 @@
                     factory.createString("desugarWrongMethodTypeException")),
                 false),
             new CfThrow(),
-            label3,
+            label4,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.longType(),
+                      FrameType.longHighType()
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfLoad(ValueType.LONG, 2),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Long;"),
+                    factory.createProto(factory.createType("Ljava/lang/Long;"), factory.longType),
+                    factory.createString("valueOf")),
+                false),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(
+                        factory.voidType, factory.objectType, factory.longType, factory.objectType),
+                    factory.createString("putObject")),
+                false),
+            label5,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
@@ -1682,7 +1944,7 @@
                       FrameType.longHighType()
                     })),
             new CfReturnVoid(),
-            label4),
+            label6),
         ImmutableList.of(),
         ImmutableList.of());
   }
@@ -1698,10 +1960,12 @@
     CfLabel label7 = new CfLabel();
     CfLabel label8 = new CfLabel();
     CfLabel label9 = new CfLabel();
+    CfLabel label10 = new CfLabel();
+    CfLabel label11 = new CfLabel();
     return new CfCode(
         method.holder,
-        1,
         2,
+        3,
         ImmutableList.of(
             label0,
             new CfLoad(ValueType.OBJECT, 1),
@@ -1721,11 +1985,12 @@
             label2,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
-                    new int[] {0, 1},
+                    new int[] {0, 1, 2},
                     new FrameType[] {
                       FrameType.initializedNonNullReference(
                           factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
-                      FrameType.initializedNonNullReference(factory.objectType)
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.intType()
                     })),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInstanceOf(factory.createType("Ljava/lang/Byte;")),
@@ -1744,11 +2009,12 @@
             label4,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
-                    new int[] {0, 1},
+                    new int[] {0, 1, 2},
                     new FrameType[] {
                       FrameType.initializedNonNullReference(
                           factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
-                      FrameType.initializedNonNullReference(factory.objectType)
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.intType()
                     })),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInstanceOf(factory.boxedCharType),
@@ -1767,11 +2033,12 @@
             label6,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
-                    new int[] {0, 1},
+                    new int[] {0, 1, 2},
                     new FrameType[] {
                       FrameType.initializedNonNullReference(
                           factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
-                      FrameType.initializedNonNullReference(factory.objectType)
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.intType()
                     })),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInstanceOf(factory.createType("Ljava/lang/Short;")),
@@ -1790,11 +2057,35 @@
             label8,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
-                    new int[] {0, 1},
+                    new int[] {0, 1, 2},
                     new FrameType[] {
                       FrameType.initializedNonNullReference(
                           factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
-                      FrameType.initializedNonNullReference(factory.objectType)
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.intType()
+                    })),
+            new CfLoad(ValueType.INT, 2),
+            new CfIf(If.Type.EQ, ValueType.INT, label10),
+            label9,
+            new CfNew(factory.createType("Ljava/lang/ClassCastException;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/ClassCastException;"),
+                    factory.createProto(factory.voidType),
+                    factory.createString("<init>")),
+                false),
+            new CfThrow(),
+            label10,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.intType()
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
@@ -1805,7 +2096,7 @@
                     factory.createString("desugarWrongMethodTypeException")),
                 false),
             new CfThrow(),
-            label9),
+            label11),
         ImmutableList.of(),
         ImmutableList.of());
   }
@@ -1817,8 +2108,8 @@
     CfLabel label3 = new CfLabel();
     return new CfCode(
         method.holder,
-        2,
-        2,
+        3,
+        3,
         ImmutableList.of(
             label0,
             new CfLoad(ValueType.OBJECT, 1),
@@ -1838,19 +2129,21 @@
             label2,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
-                    new int[] {0, 1},
+                    new int[] {0, 1, 2},
                     new FrameType[] {
                       FrameType.initializedNonNullReference(
                           factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
-                      FrameType.initializedNonNullReference(factory.objectType)
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.intType()
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.INT, 2),
             new CfInvoke(
                 182,
                 factory.createMethod(
                     factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
-                    factory.createProto(factory.intType, factory.objectType),
+                    factory.createProto(factory.intType, factory.objectType, factory.booleanType),
                     factory.createString("toIntIfPossible")),
                 false),
             new CfNumberConversion(NumericType.INT, NumericType.LONG),
diff --git a/src/main/java/com/android/tools/r8/utils/IntBox.java b/src/main/java/com/android/tools/r8/utils/IntBox.java
index 79684cd..47f2093 100644
--- a/src/main/java/com/android/tools/r8/utils/IntBox.java
+++ b/src/main/java/com/android/tools/r8/utils/IntBox.java
@@ -48,6 +48,12 @@
     increment(1);
   }
 
+  public void incrementIf(boolean value) {
+    if (value) {
+      increment();
+    }
+  }
+
   public void increment(int i) {
     assert i >= 0;
     value += i;
diff --git a/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java
index dd60a3e..7360a6c 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java
@@ -116,27 +116,7 @@
               positionRemapper.createRemappedPosition(currentPosition);
           Position oldPosition = remappedPosition.getFirst();
           Position newPosition = remappedPosition.getSecond();
-          if (isFirstEntry) {
-            mappedPositions.add(
-                new MappedPosition(
-                    oldPosition.getMethod(),
-                    oldPosition.getLine(),
-                    oldPosition.getCallerPosition(),
-                    newPosition.getLine(),
-                    oldPosition.isOutline(),
-                    oldPosition.getOutlineCallee(),
-                    oldPosition.getOutlinePositions()));
-          } else {
-            mappedPositions.add(
-                new MappedPosition(
-                    oldPosition.getMethod(),
-                    oldPosition.getLine(),
-                    oldPosition.getCallerPosition(),
-                    newPosition.getLine(),
-                    false,
-                    null,
-                    null));
-          }
+          mappedPositions.add(new MappedPosition(oldPosition, newPosition.getLine()));
           CfPosition position = new CfPosition(new CfLabel(), newPosition);
           newInstructions.add(position);
           newInstructions.add(position.getLabel());
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
index 6e540b8..a660a37 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.graph.DexDebugEvent.RestartLocal;
 import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin;
 import com.android.tools.r8.graph.DexDebugEvent.SetFile;
-import com.android.tools.r8.graph.DexDebugEvent.SetPositionFrame;
 import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
 import com.android.tools.r8.graph.DexDebugEventBuilder;
@@ -26,12 +25,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.Position.OutlineCallerPosition;
-import com.android.tools.r8.ir.code.Position.OutlineCallerPosition.OutlineCallerPositionBuilder;
-import com.android.tools.r8.ir.code.Position.OutlinePosition;
-import com.android.tools.r8.ir.code.Position.PositionBuilder;
 import com.android.tools.r8.ir.code.Position.SourcePosition;
-import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import java.util.ArrayList;
 import java.util.List;
@@ -87,6 +81,122 @@
     }
   }
 
+  private static class DexDebugPositionStateVisitor extends DexDebugPositionState {
+
+    private final PositionEventEmitter positionEventEmitter;
+    private final List<MappedPosition> mappedPositions;
+    private final PositionRemapper positionRemapper;
+    private final List<DexDebugEvent> processedEvents;
+    private final DexItemFactory factory;
+
+    private final DexMethod startMethod;
+
+    // Keep track of what PC has been emitted.
+    private int emittedPc = 0;
+
+    private boolean inlinedOriginalPosition;
+
+    public DexDebugPositionStateVisitor(
+        PositionEventEmitter positionEventEmitter,
+        List<MappedPosition> mappedPositions,
+        PositionRemapper positionRemapper,
+        List<DexDebugEvent> processedEvents,
+        DexItemFactory factory,
+        int startLine,
+        DexMethod method) {
+      super(startLine, method);
+      this.positionEventEmitter = positionEventEmitter;
+      this.mappedPositions = mappedPositions;
+      this.positionRemapper = positionRemapper;
+      this.processedEvents = processedEvents;
+      this.factory = factory;
+      this.startMethod = method;
+    }
+
+    // Force the current PC to emitted.
+    private void flushPc() {
+      if (emittedPc != getCurrentPc()) {
+        positionEventEmitter.emitAdvancePc(getCurrentPc());
+        emittedPc = getCurrentPc();
+      }
+    }
+
+    // A default event denotes a line table entry and must always be emitted. Remap its line.
+    @Override
+    public void visit(Default defaultEvent) {
+      if (hasPreamblePosition(defaultEvent)) {
+        emitPreamblePosition();
+      }
+      super.visit(defaultEvent);
+      assert getCurrentLine() >= 0;
+      Position position = getPosition();
+      Position mappedPosition =
+          PositionUtils.remapAndAdd(position, positionRemapper, mappedPositions);
+      positionEventEmitter.emitPositionEvents(getCurrentPc(), mappedPosition);
+      if (mappedPosition != position) {
+        inlinedOriginalPosition = true;
+      }
+      emittedPc = getCurrentPc();
+    }
+
+    private boolean hasPreamblePosition(Default defaultEvent) {
+      return getCurrentPc() == 0
+          && defaultEvent.getPCDelta() > 0
+          && currentPosition != null
+          && currentPosition.getLine() != getCurrentLine();
+    }
+
+    // Non-materializing events use super, ie, AdvancePC, AdvanceLine and SetInlineFrame.
+
+    // Materializing events are just amended to the stream.
+
+    @Override
+    public void visit(SetFile setFile) {
+      processedEvents.add(setFile);
+    }
+
+    @Override
+    public void visit(SetPrologueEnd setPrologueEnd) {
+      processedEvents.add(setPrologueEnd);
+    }
+
+    @Override
+    public void visit(SetEpilogueBegin setEpilogueBegin) {
+      processedEvents.add(setEpilogueBegin);
+    }
+
+    // Local changes must force flush the PC ensuing they pertain to the correct point.
+
+    @Override
+    public void visit(StartLocal startLocal) {
+      flushPc();
+      processedEvents.add(startLocal);
+    }
+
+    @Override
+    public void visit(EndLocal endLocal) {
+      flushPc();
+      processedEvents.add(endLocal);
+    }
+
+    @Override
+    public void visit(RestartLocal restartLocal) {
+      flushPc();
+      processedEvents.add(restartLocal);
+    }
+
+    public void emitPreamblePosition() {
+      if (currentPosition == null || positionEventEmitter.didEmitLineEvents()) {
+        return;
+      }
+      Position mappedPosition =
+          PositionUtils.remapAndAdd(currentPosition, positionRemapper, mappedPositions);
+      processedEvents.add(factory.createPositionFrame(mappedPosition));
+      currentPosition = null;
+      currentMethod = startMethod;
+    }
+  }
+
   private final AppView<?> appView;
   private final boolean isIdentityMapping;
 
@@ -112,95 +222,24 @@
             appView.graphLens().getOriginalMethodSignature(method.getReference()),
             processedEvents);
 
-    Box<Boolean> inlinedOriginalPosition = new Box<>(false);
-
-    // Debug event visitor to map line numbers.
-    DexDebugPositionState visitor =
-        new DexDebugPositionState(
+    DexDebugPositionStateVisitor visitor =
+        new DexDebugPositionStateVisitor(
+            positionEventEmitter,
+            mappedPositions,
+            positionRemapper,
+            processedEvents,
+            appView.dexItemFactory(),
             debugInfo.startLine,
-            appView.graphLens().getOriginalMethodSignature(method.getReference())) {
-
-          // Keep track of what PC has been emitted.
-          private int emittedPc = 0;
-
-          // Force the current PC to emitted.
-          private void flushPc() {
-            if (emittedPc != getCurrentPc()) {
-              positionEventEmitter.emitAdvancePc(getCurrentPc());
-              emittedPc = getCurrentPc();
-            }
-          }
-
-          // A default event denotes a line table entry and must always be emitted. Remap its line.
-          @Override
-          public void visit(Default defaultEvent) {
-            super.visit(defaultEvent);
-            assert getCurrentLine() >= 0;
-            Position position = getPositionFromPositionState(this);
-            Position currentPosition =
-                PositionUtils.remapAndAdd(position, positionRemapper, mappedPositions);
-            positionEventEmitter.emitPositionEvents(getCurrentPc(), currentPosition);
-            if (currentPosition != position) {
-              inlinedOriginalPosition.set(true);
-            }
-            emittedPc = getCurrentPc();
-            resetOutlineInformation();
-          }
-
-          // Non-materializing events use super, ie, AdvancePC, AdvanceLine and SetInlineFrame.
-
-          // Materializing events are just amended to the stream.
-
-          @Override
-          public void visit(SetFile setFile) {
-            processedEvents.add(setFile);
-          }
-
-          @Override
-          public void visit(SetPrologueEnd setPrologueEnd) {
-            processedEvents.add(setPrologueEnd);
-          }
-
-          @Override
-          public void visit(SetEpilogueBegin setEpilogueBegin) {
-            processedEvents.add(setEpilogueBegin);
-          }
-
-          // Local changes must force flush the PC ensuing they pertain to the correct point.
-
-          @Override
-          public void visit(StartLocal startLocal) {
-            flushPc();
-            processedEvents.add(startLocal);
-          }
-
-          @Override
-          public void visit(EndLocal endLocal) {
-            flushPc();
-            processedEvents.add(endLocal);
-          }
-
-          @Override
-          public void visit(RestartLocal restartLocal) {
-            flushPc();
-            processedEvents.add(restartLocal);
-          }
-        };
+            appView.graphLens().getOriginalMethodSignature(method.getReference()));
 
     DexDebugEvent[] events = debugInfo.events;
-    if (events.length > 0) {
-      SetPositionFrame preambleFrame = getAsPreambleFrame(events[0]);
-      if (preambleFrame != null) {
-        // The preamble is specially identified here as it is active at method entry and thus not
-        // part of the instruction stream events.
-        Position position = preambleFrame.getPosition();
-        Position newPosition =
-            PositionUtils.remapAndAdd(position, positionRemapper, mappedPositions);
-        processedEvents.add(appView.dexItemFactory().createPositionFrame(newPosition));
-      }
-      for (int i = (preambleFrame == null) ? 0 : 1; i < events.length; i++) {
-        events[i].accept(visitor);
-      }
+    for (DexDebugEvent event : events) {
+      event.accept(visitor);
+    }
+
+    // We still need to emit a preamble if we did not materialize any other instructions.
+    if (mappedPositions.isEmpty()) {
+      visitor.emitPreamblePosition();
     }
 
     EventBasedDebugInfo optimizedDebugInfo =
@@ -210,23 +249,13 @@
             processedEvents.toArray(DexDebugEvent.EMPTY_ARRAY));
 
     assert !isIdentityMapping
-        || inlinedOriginalPosition.get()
+        || visitor.inlinedOriginalPosition
         || verifyIdentityMapping(debugInfo, optimizedDebugInfo);
 
     dexCode.setDebugInfo(optimizedDebugInfo);
     return mappedPositions;
   }
 
-  private SetPositionFrame getAsPreambleFrame(DexDebugEvent event) {
-    SetPositionFrame positionFrame = event.asSetPositionFrame();
-    if (positionFrame != null
-        && positionFrame.getPosition().isSyntheticPosition()
-        && positionFrame.getPosition().getLine() == 0) {
-      return positionFrame;
-    }
-    return null;
-  }
-
   // This conversion *always* creates an event based debug info encoding as any non-info will
   // be created as an implicit PC encoding.
   private static EventBasedDebugInfo getEventBasedDebugInfo(
@@ -243,27 +272,6 @@
     return debugInfo;
   }
 
-  private static Position getPositionFromPositionState(DexDebugPositionState state) {
-    PositionBuilder<?, ?> positionBuilder;
-    if (state.getOutlineCallee() != null) {
-      OutlineCallerPositionBuilder outlineCallerPositionBuilder =
-          OutlineCallerPosition.builder()
-              .setOutlineCallee(state.getOutlineCallee())
-              .setIsOutline(state.isOutline());
-      state.getOutlineCallerPositions().forEach(outlineCallerPositionBuilder::addOutlinePosition);
-      positionBuilder = outlineCallerPositionBuilder;
-    } else if (state.isOutline()) {
-      positionBuilder = OutlinePosition.builder();
-    } else {
-      positionBuilder = SourcePosition.builder().setFile(state.getCurrentFile());
-    }
-    return positionBuilder
-        .setLine(state.getCurrentLine())
-        .setMethod(state.getCurrentMethod())
-        .setCallerPosition(state.getCurrentCallerPosition())
-        .build();
-  }
-
   private static boolean verifyIdentityMapping(
       EventBasedDebugInfo originalDebugInfo, EventBasedDebugInfo optimizedDebugInfo) {
     assert optimizedDebugInfo.startLine == originalDebugInfo.startLine;
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
index 890cb80..40f80b4 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
@@ -16,11 +16,6 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.Position.OutlineCallerPosition;
-import com.android.tools.r8.ir.code.Position.OutlineCallerPosition.OutlineCallerPositionBuilder;
-import com.android.tools.r8.ir.code.Position.OutlinePosition;
-import com.android.tools.r8.ir.code.Position.PositionBuilder;
-import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.positions.PositionToMappedRangeMapper.PcBasedDebugInfoRecorder;
@@ -58,7 +53,7 @@
             if (firstDefaultEventPc.get() < 0) {
               firstDefaultEventPc.set(getCurrentPc());
             }
-            Position currentPosition = getPositionFromPositionState(this);
+            Position currentPosition = getPosition();
             if (lastPosition.getSecond() != null) {
               remapAndAddForPc(
                   pcBasedDebugInfo,
@@ -70,7 +65,6 @@
             }
             lastPosition.setFirst(getCurrentPc());
             lastPosition.setSecond(currentPosition);
-            resetOutlineInformation();
           }
         };
 
@@ -104,18 +98,8 @@
     Pair<Position, Position> remappedPosition = remapper.createRemappedPosition(position);
     Position oldPosition = remappedPosition.getFirst();
     for (int currentPc = startPc; currentPc < endPc; currentPc++) {
-      boolean firstEntry = currentPc == startPc;
       mappedPositions.add(
-          new MappedPosition(
-              oldPosition.getMethod(),
-              oldPosition.getLine(),
-              oldPosition.getCallerPosition(),
-              debugInfoProvider.getPcEncoding(currentPc),
-              // Outline info is placed exactly on the positions that relate to it so we should
-              // only emit it for the first entry.
-              firstEntry && oldPosition.isOutline(),
-              firstEntry ? oldPosition.getOutlineCallee() : null,
-              firstEntry ? oldPosition.getOutlinePositions() : null));
+          new MappedPosition(oldPosition, debugInfoProvider.getPcEncoding(currentPc)));
     }
   }
 
@@ -135,27 +119,6 @@
     return debugInfo;
   }
 
-  private static Position getPositionFromPositionState(DexDebugPositionState state) {
-    PositionBuilder<?, ?> positionBuilder;
-    if (state.getOutlineCallee() != null) {
-      OutlineCallerPositionBuilder outlineCallerPositionBuilder =
-          OutlineCallerPosition.builder()
-              .setOutlineCallee(state.getOutlineCallee())
-              .setIsOutline(state.isOutline());
-      state.getOutlineCallerPositions().forEach(outlineCallerPositionBuilder::addOutlinePosition);
-      positionBuilder = outlineCallerPositionBuilder;
-    } else if (state.isOutline()) {
-      positionBuilder = OutlinePosition.builder();
-    } else {
-      positionBuilder = SourcePosition.builder().setFile(state.getCurrentFile());
-    }
-    return positionBuilder
-        .setLine(state.getCurrentLine())
-        .setMethod(state.getCurrentMethod())
-        .setCallerPosition(state.getCurrentCallerPosition())
-        .build();
-  }
-
   private static boolean verifyIdentityMapping(
       EventBasedDebugInfo originalDebugInfo, EventBasedDebugInfo optimizedDebugInfo) {
     assert optimizedDebugInfo.startLine == originalDebugInfo.startLine;
diff --git a/src/main/java/com/android/tools/r8/utils/positions/MappedPosition.java b/src/main/java/com/android/tools/r8/utils/positions/MappedPosition.java
index a156c80..3b29392 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/MappedPosition.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/MappedPosition.java
@@ -4,66 +4,23 @@
 
 package com.android.tools.r8.utils.positions;
 
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.utils.Int2StructuralItemArrayMap;
 
 public class MappedPosition {
 
-  private final DexMethod method;
-  private final int originalLine;
-  private final Position caller;
   private final int obfuscatedLine;
-  private final boolean isOutline;
-  private final DexMethod outlineCallee;
-  private final Int2StructuralItemArrayMap<Position> outlinePositions;
+  private final Position position;
 
-  public MappedPosition(
-      DexMethod method,
-      int originalLine,
-      Position caller,
-      int obfuscatedLine,
-      boolean isOutline,
-      DexMethod outlineCallee,
-      Int2StructuralItemArrayMap<Position> outlinePositions) {
-    this.method = method;
-    this.originalLine = originalLine;
-    this.caller = caller;
+  public MappedPosition(Position position, int obfuscatedLine) {
+    this.position = position;
     this.obfuscatedLine = obfuscatedLine;
-    this.isOutline = isOutline;
-    this.outlineCallee = outlineCallee;
-    this.outlinePositions = outlinePositions;
-  }
-
-  public DexMethod getMethod() {
-    return method;
-  }
-
-  public int getOriginalLine() {
-    return originalLine;
-  }
-
-  public Position getCaller() {
-    return caller;
   }
 
   public int getObfuscatedLine() {
     return obfuscatedLine;
   }
 
-  public boolean isOutline() {
-    return isOutline;
-  }
-
-  public DexMethod getOutlineCallee() {
-    return outlineCallee;
-  }
-
-  public Int2StructuralItemArrayMap<Position> getOutlinePositions() {
-    return outlinePositions;
-  }
-
-  public boolean isOutlineCaller() {
-    return outlineCallee != null;
+  public Position getPosition() {
+    return position;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
index 13f013e..2915d14 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
@@ -248,7 +248,7 @@
                   m, key -> MethodSignature.fromDexMethod(m, m.holder != clazz.getType()));
 
       // Check if mapped position is an outline
-      DexMethod outlineMethod = getOutlineMethod(mappedPositions.get(0));
+      DexMethod outlineMethod = getOutlineMethod(mappedPositions.get(0).getPosition());
       if (outlineMethod != null) {
         outlinesToFix
             .computeIfAbsent(
@@ -261,38 +261,48 @@
       // Update memberNaming with the collected positions, merging multiple positions into a
       // single region whenever possible.
       for (int i = 0; i < mappedPositions.size(); /* updated in body */ ) {
-        MappedPosition firstPosition = mappedPositions.get(i);
+        MappedPosition firstMappedPosition = mappedPositions.get(i);
         int j = i + 1;
-        MappedPosition lastPosition = firstPosition;
+        MappedPosition lastMappedPosition = firstMappedPosition;
         MappedPositionRange mappedPositionRange = MappedPositionRange.SINGLE_LINE;
         for (; j < mappedPositions.size(); j++) {
           // Break if this position cannot be merged with lastPosition.
-          MappedPosition currentPosition = mappedPositions.get(j);
+          MappedPosition currentMappedPosition = mappedPositions.get(j);
           mappedPositionRange =
-              mappedPositionRange.canAddNextMappingToRange(lastPosition, currentPosition);
+              mappedPositionRange.canAddNextMappingToRange(
+                  lastMappedPosition, currentMappedPosition);
           // Note that currentPosition.caller and lastPosition.class must be deep-compared since
           // multiple inlining passes lose the canonical property of the positions.
+          Position currentPosition = currentMappedPosition.getPosition();
+          Position lastPosition = lastMappedPosition.getPosition();
           if (currentPosition.getMethod() != lastPosition.getMethod()
               || mappedPositionRange.isOutOfRange()
-              || !Objects.equals(currentPosition.getCaller(), lastPosition.getCaller())
+              || !Objects.equals(
+                  currentPosition.getCallerPosition(), lastPosition.getCallerPosition())
               // Break when we see a mapped outline
               || currentPosition.getOutlineCallee() != null
               // Ensure that we break when we start iterating with an outline caller again.
-              || firstPosition.getOutlineCallee() != null) {
+              || firstMappedPosition.getPosition().getOutlineCallee() != null) {
             break;
           }
           // The mapped positions are not guaranteed to be in order, so maintain first and last
           // position.
-          if (firstPosition.getObfuscatedLine() > currentPosition.getObfuscatedLine()) {
-            firstPosition = currentPosition;
+          if (firstMappedPosition.getObfuscatedLine() > currentMappedPosition.getObfuscatedLine()) {
+            firstMappedPosition = currentMappedPosition;
           }
-          if (lastPosition.getObfuscatedLine() < currentPosition.getObfuscatedLine()) {
-            lastPosition = currentPosition;
+          if (lastMappedPosition.getObfuscatedLine() < currentMappedPosition.getObfuscatedLine()) {
+            lastMappedPosition = currentMappedPosition;
           }
         }
         Range obfuscatedRange =
             nonCardinalRangeCache.get(
-                firstPosition.getObfuscatedLine(), lastPosition.getObfuscatedLine());
+                firstMappedPosition.getObfuscatedLine(), lastMappedPosition.getObfuscatedLine());
+
+        Position firstPosition = firstMappedPosition.getPosition();
+        Position lastPosition = lastMappedPosition.getPosition();
+
+        Range originalRange =
+            nonCardinalRangeCache.get(firstPosition.getLine(), lastPosition.getLine());
 
         MappedRange lastMappedRange =
             getMappedRangesForPosition(
@@ -302,9 +312,8 @@
                 firstPosition.getMethod(),
                 residualSignature,
                 obfuscatedRange,
-                nonCardinalRangeCache.get(
-                    firstPosition.getOriginalLine(), lastPosition.getOriginalLine()),
-                firstPosition.getCaller(),
+                originalRange,
+                firstPosition.getCallerPosition(),
                 prunedInlinedClasses,
                 cardinalRangeCache);
         methodSpecificMappingInformation.consume(
@@ -407,11 +416,11 @@
       return lastMappedRange;
     }
 
-    private DexMethod getOutlineMethod(MappedPosition mappedPosition) {
+    private DexMethod getOutlineMethod(Position mappedPosition) {
       if (mappedPosition.isOutline()) {
         return mappedPosition.getMethod();
       }
-      Position caller = mappedPosition.getCaller();
+      Position caller = mappedPosition.getCallerPosition();
       if (caller == null) {
         return null;
       }
@@ -500,8 +509,9 @@
       // is OK since retrace(a(:7)) = 42, however, the following is not OK:
       //   1:10:void foo():42:43 -> a
       // since retrace(a(:7)) = 49, which is not correct.
-      boolean hasSameRightHandSide =
-          lastPosition.getOriginalLine() == currentPosition.getOriginalLine();
+      int currentOriginalLine = currentPosition.getPosition().getLine();
+      int lastOriginalLine = lastPosition.getPosition().getLine();
+      boolean hasSameRightHandSide = lastOriginalLine == currentOriginalLine;
       if (hasSameRightHandSide) {
         if (isSameDelta()) {
           return OUT_OF_RANGE;
@@ -515,7 +525,7 @@
         return OUT_OF_RANGE;
       }
       boolean sameDelta =
-          currentPosition.getOriginalLine() - lastPosition.getOriginalLine()
+          currentOriginalLine - lastOriginalLine
               == currentPosition.getObfuscatedLine() - lastPosition.getObfuscatedLine();
       return sameDelta ? SAME_DELTA : OUT_OF_RANGE;
     }
@@ -569,7 +579,7 @@
     private int getMinifiedLinePosition(
         int originalPosition, List<MappedPosition> mappedPositions) {
       for (MappedPosition mappedPosition : mappedPositions) {
-        if (mappedPosition.getOriginalLine() == originalPosition) {
+        if (mappedPosition.getPosition().getLine() == originalPosition) {
           return mappedPosition.getObfuscatedLine();
         }
       }
diff --git a/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java b/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java
index b7247e7..0b0b20a 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java
@@ -24,15 +24,7 @@
     Pair<Position, Position> remappedPosition = remapper.createRemappedPosition(position);
     Position oldPosition = remappedPosition.getFirst();
     Position newPosition = remappedPosition.getSecond();
-    mappedPositions.add(
-        new MappedPosition(
-            oldPosition.getMethod(),
-            oldPosition.getLine(),
-            oldPosition.getCallerPosition(),
-            newPosition.getLine(),
-            oldPosition.isOutline(),
-            oldPosition.getOutlineCallee(),
-            oldPosition.getOutlinePositions()));
+    mappedPositions.add(new MappedPosition(oldPosition, newPosition.getLine()));
     return newPosition;
   }
 
diff --git a/src/test/examplesJava9/varhandle/InstanceObjectField.java b/src/test/examplesJava9/varhandle/InstanceObjectField.java
index e7b4970..1b4a4c9 100644
--- a/src/test/examplesJava9/varhandle/InstanceObjectField.java
+++ b/src/test/examplesJava9/varhandle/InstanceObjectField.java
@@ -5,19 +5,160 @@
 
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.VarHandle;
+import java.lang.invoke.WrongMethodTypeException;
 
 public class InstanceObjectField {
 
   private Object field;
 
+  public static class A {
+
+    private final int i;
+
+    public A(int i) {
+      this.i = i;
+    }
+
+    public String toString() {
+      return new StringBuilder().append("A(").append(i).append(")").toString();
+    }
+  }
+
   public static void testSet(VarHandle varHandle) {
-    System.out.println("testGet");
+    System.out.println("testSet");
 
     InstanceObjectField instance = new InstanceObjectField();
 
     System.out.println(varHandle.get(instance));
-    varHandle.set(instance, 1);
+    A a1 = new A(1);
+    varHandle.set(instance, a1);
     System.out.println(varHandle.get(instance));
+    System.out.println(varHandle.get(instance) == a1);
+    A a2 = new A(2);
+    varHandle.set(instance, a2);
+    System.out.println(varHandle.get(instance));
+    System.out.println(varHandle.get(instance) == a2);
+
+    Object o;
+    {
+      int i;
+      varHandle.set(instance, 1);
+      System.out.println(varHandle.get(instance));
+      System.out.println((int) varHandle.get(instance));
+      o = varHandle.get(instance);
+      System.out.println(o instanceof Integer);
+      i = (int) varHandle.get(instance);
+      System.out.println(i == 1);
+      varHandle.set(instance, Integer.valueOf(2));
+      System.out.println(varHandle.get(instance));
+      System.out.println((int) varHandle.get(instance));
+      o = varHandle.get(instance);
+      System.out.println(o instanceof Integer);
+      i = (int) varHandle.get(instance);
+      System.out.println(i == 2);
+    }
+    {
+      long l;
+      varHandle.set(instance, 3L);
+      System.out.println(varHandle.get(instance));
+      System.out.println((long) varHandle.get(instance));
+      o = varHandle.get(instance);
+      System.out.println(o instanceof Long);
+      l = (long) varHandle.get(instance);
+      System.out.println(l == 3L);
+      varHandle.set(instance, Long.valueOf(4L));
+      System.out.println(varHandle.get(instance));
+      System.out.println((long) varHandle.get(instance));
+      o = varHandle.get(instance);
+      System.out.println(o instanceof Long);
+      l = (long) varHandle.get(instance);
+      System.out.println(l == 4L);
+    }
+    {
+      byte b;
+      varHandle.set(instance, (byte) 5);
+      System.out.println(varHandle.get(instance));
+      System.out.println((byte) varHandle.get(instance));
+      o = varHandle.get(instance);
+      System.out.println(o instanceof Byte);
+      b = (byte) varHandle.get(instance);
+      System.out.println(b == (byte) 5);
+      varHandle.set(instance, Byte.valueOf((byte) 6));
+      System.out.println(varHandle.get(instance));
+      System.out.println((byte) varHandle.get(instance));
+      o = varHandle.get(instance);
+      System.out.println(o instanceof Byte);
+      b = (byte) varHandle.get(instance);
+      System.out.println(b == 6);
+    }
+    {
+      short s;
+      varHandle.set(instance, (short) 7);
+      System.out.println(varHandle.get(instance));
+      System.out.println((short) varHandle.get(instance));
+      o = varHandle.get(instance);
+      System.out.println(o instanceof Short);
+      s = (short) varHandle.get(instance);
+      System.out.println(s == (short) 7);
+      varHandle.set(instance, Short.valueOf((short) 8));
+      System.out.println(varHandle.get(instance));
+      System.out.println((short) varHandle.get(instance));
+      o = varHandle.get(instance);
+      System.out.println(o instanceof Short);
+      s = (short) varHandle.get(instance);
+      System.out.println(s == 8);
+    }
+    {
+      float f;
+      varHandle.set(instance, (float) 9.0f);
+      System.out.println(varHandle.get(instance));
+      System.out.println((float) varHandle.get(instance));
+      o = varHandle.get(instance);
+      System.out.println(o instanceof Float);
+      f = (float) varHandle.get(instance);
+      System.out.println(f == (float) 9.0f);
+      varHandle.set(instance, Float.valueOf(10.0f));
+      System.out.println(varHandle.get(instance));
+      System.out.println((float) varHandle.get(instance));
+      o = varHandle.get(instance);
+      System.out.println(o instanceof Float);
+      f = (float) varHandle.get(instance);
+      System.out.println(f == 10.0f);
+    }
+    {
+      double d;
+      varHandle.set(instance, (double) 11.0);
+      System.out.println(varHandle.get(instance));
+      System.out.println((double) varHandle.get(instance));
+      o = varHandle.get(instance);
+      System.out.println(o instanceof Double);
+      d = (double) varHandle.get(instance);
+      System.out.println(d == (double) 11.0);
+      varHandle.set(instance, Double.valueOf(12.0));
+      System.out.println(varHandle.get(instance));
+      System.out.println((double) varHandle.get(instance));
+      o = varHandle.get(instance);
+      System.out.println(o instanceof Double);
+      d = (double) varHandle.get(instance);
+      System.out.println(d == 12.0);
+    }
+    {
+      char c;
+      varHandle.set(instance, 'A');
+      System.out.println(varHandle.get(instance));
+      System.out.println((char) varHandle.get(instance));
+      o = varHandle.get(instance);
+      System.out.println(o instanceof Character);
+      c = (char) varHandle.get(instance);
+      System.out.println(c == 'A');
+      varHandle.set(instance, Character.valueOf('B'));
+      System.out.println(varHandle.get(instance));
+      System.out.println((char) varHandle.get(instance));
+      o = varHandle.get(instance);
+      System.out.println(o instanceof Character);
+      c = (char) varHandle.get(instance);
+      System.out.println(c == 'B');
+    }
   }
 
   public static void testCompareAndSet(VarHandle varHandle) {
@@ -25,10 +166,106 @@
 
     InstanceObjectField instance = new InstanceObjectField();
 
-    varHandle.compareAndSet(instance, 0, 1);
+    A a1 = new A(1);
+    varHandle.compareAndSet(instance, 0, a1);
     System.out.println(varHandle.get(instance));
-    varHandle.compareAndSet(instance, null, 1);
+    varHandle.compareAndSet(instance, null, a1);
     System.out.println(varHandle.get(instance));
+    System.out.println(varHandle.get(instance) == a1);
+    A a2 = new A(2);
+    varHandle.compareAndSet(instance, a1, a2);
+    System.out.println(varHandle.get(instance));
+    System.out.println(varHandle.get(instance) == a2);
+
+    varHandle.compareAndSet(instance, a2, 1);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, Integer.valueOf(1), 2);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, Integer.valueOf(2), Integer.valueOf(3));
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, Integer.valueOf(3), 4);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, 4L, 5);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, (byte) 4, 5);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, (short) 4, 5);
+    System.out.println(varHandle.get(instance));
+
+    varHandle.compareAndSet(instance, 4, 5L);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, Long.valueOf(5), 6L);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, Long.valueOf(6), Long.valueOf(7));
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, Long.valueOf(7), 8L);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, 8, 9L);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, (byte) 8, 9);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, (short) 8, 9);
+    System.out.println(varHandle.get(instance));
+    System.out.println(varHandle.get(instance) == a1);
+    varHandle.compareAndSet(instance, a1, a2);
+    System.out.println(varHandle.get(instance));
+    System.out.println(varHandle.get(instance) == a2);
+
+    varHandle.compareAndSet(instance, a2, 1);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, Integer.valueOf(1), 2);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, Integer.valueOf(2), Integer.valueOf(3));
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, Integer.valueOf(3), 4);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, 4L, 5);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, (byte) 4, 5);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, (short) 4, 5);
+    System.out.println(varHandle.get(instance));
+
+    varHandle.compareAndSet(instance, 4, 5L);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, Long.valueOf(5), 6L);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, Long.valueOf(6), Long.valueOf(7));
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, Long.valueOf(7), 8L);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, 8, 9L);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, (byte) 8, 9);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, (short) 8, 9);
+    System.out.println(varHandle.get(instance));
+  }
+
+  public static void testReturnValueClassCastException(VarHandle varHandle) {
+    System.out.println("testReturnValueClassCastException");
+
+    InstanceObjectField instance = new InstanceObjectField();
+    A a = new A(1);
+
+    varHandle.set(instance, a);
+    try {
+      System.out.println((int) varHandle.get(instance));
+      System.out.println("Expected ClassCastException");
+    } catch (ClassCastException e) {
+      System.out.println("Reference implementation");
+    } catch (WrongMethodTypeException e) {
+      System.out.println("Art implementation");
+    }
+    varHandle.set(instance, a);
+    try {
+      System.out.println((int) (Integer) (int) varHandle.get(instance));
+      System.out.println("Expected ClassCastException");
+    } catch (ClassCastException e) {
+      System.out.println("Reference implementation");
+    } catch (WrongMethodTypeException e) {
+      System.out.println("Art implementation");
+    }
   }
 
   public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
@@ -36,5 +273,6 @@
         MethodHandles.lookup().findVarHandle(InstanceObjectField.class, "field", Object.class);
     testSet(varHandle);
     testCompareAndSet(varHandle);
+    testReturnValueClassCastException(varHandle);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ClassValueTest.java b/src/test/java/com/android/tools/r8/apimodel/ClassValueTest.java
new file mode 100644
index 0000000..ad9ea40
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ClassValueTest.java
@@ -0,0 +1,224 @@
+// 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.apimodel;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+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 ClassValueTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines(TestClass.class.getTypeName());
+
+  private void computeValuePresent(CodeInspector inspector) {
+    assertThat(
+        inspector.clazz(ClassValueSub.class).uniqueMethodWithOriginalName("computeValue"),
+        isPresent());
+  }
+
+  private void computeValueAbsent(CodeInspector inspector) {
+    assertThat(
+        inspector.clazz(ClassValueSub.class).uniqueMethodWithOriginalName("computeValue"),
+        isAbsent());
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForRuntime(parameters)
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::computeValuePresent)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .addDontWarn(ClassValue.class)
+        .compile()
+        .inspect(this::computeValueAbsent)
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            parameters.isCfRuntime(),
+            r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  @Test
+  public void testProguard() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForProguard(ProguardVersion.V7_0_0)
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        // ProGuard warns about the inner class attributes referring to this outer class.
+        .addDontWarn(this.getClass().getTypeName())
+        // ProGuard also warns about ClassValueSub not having method get.
+        .addDontWarn(ClassValueSub.class.getTypeName())
+        .compile()
+        .inspect(this::computeValueAbsent)
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            parameters.isCfRuntime(),
+            r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  @Test
+  public void testR8KeepExtendsMissingType() throws Exception {
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .addDontWarn(ClassValue.class)
+        // Try to keep computeValue on classes extending unknown type.
+        .addKeepRules("-keep class * extends " + ClassValue.class.getTypeName() + " { *; }")
+        .allowUnusedProguardConfigurationRules()
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics.assertInfoThatMatches(
+                    diagnosticMessage(
+                        containsString(
+                            "Proguard configuration rule does not match anything: "
+                                + "`-keep class * extends "
+                                + ClassValue.class.getTypeName()
+                                + " {"))))
+        .inspect(this::computeValueAbsent)
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            parameters.isCfRuntime(),
+            r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  @Test
+  public void testProguardKeepExtendsMissingTypeProguard() throws Exception {
+    assumeTrue(parameters.isCfRuntime() && parameters.asCfRuntime().getVm() == CfVm.JDK11);
+    testForProguard(ProguardVersion.V7_0_0)
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        // ProGuard warns about the inner class attributes referring to this outer class.
+        .addDontWarn(this.getClass().getTypeName())
+        // Just -dontwarn on ClassValue is not sufficient. ProGuard also warns about ClassValueSub
+        // not having method get.
+        .addDontWarn(ClassValueSub.class.getTypeName())
+        // Try to keep computeValue on classes extending unknown type.
+        .addKeepRules("-keep class * extends " + ClassValue.class.getTypeName() + " { *; }")
+        // TODO(b/261971620): Support extends matching names of missing classes.
+        .compile()
+        .inspect(this::computeValueAbsent)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(AbstractMethodError.class);
+  }
+
+  @Test
+  public void testR8KeepConcreteMethod() throws Exception {
+    for (String dontWarn :
+        ImmutableList.of(ClassValue.class.getTypeName(), ClassValueSub.class.getTypeName())) {
+      testForR8(parameters.getBackend())
+          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+          .addInnerClasses(getClass())
+          .addKeepMainRule(TestClass.class)
+          .setMinApi(parameters.getApiLevel())
+          .addDontWarn(dontWarn)
+          .addKeepRules(
+              "-keep class "
+                  + ClassValueSub.class.getTypeName()
+                  + " { ** computeValue(java.lang.Class); }")
+          .compile()
+          .inspect(this::computeValuePresent)
+          .run(parameters.getRuntime(), TestClass.class)
+          .applyIf(
+              parameters.isCfRuntime(),
+              r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+              r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+    }
+  }
+
+  @Test
+  public void testProguardKeepConcreteMethod() throws Exception {
+    assumeTrue(parameters.isCfRuntime() && parameters.asCfRuntime().getVm() == CfVm.JDK11);
+    testForProguard(ProguardVersion.V7_0_0)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        // ProGuard warns about the inner class attributes referring to this outer class.
+        .addDontWarn(this.getClass().getTypeName())
+        // Just -dontwarn on ClassValue is not sufficient. ProGuard also warns about ClassValueSub
+        // not having method get.
+        .addDontWarn(ClassValueSub.class.getTypeName())
+        .addKeepRules(
+            "-keep class "
+                + ClassValueSub.class.getTypeName()
+                + " { ** computeValue(java.lang.Class); }")
+        .compile()
+        .inspect(this::computeValuePresent)
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            parameters.isCfRuntime(),
+            r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  static class ClassValueSub extends ClassValue<Object> {
+    @Override
+    protected Object computeValue(Class<?> clazz) {
+      return clazz.getTypeName();
+    }
+  }
+
+  static class TestClass {
+    static ClassValueSub classValueSub = new ClassValueSub();
+
+    public static void main(String[] args) {
+      System.out.println(classValueSub.get(TestClass.class));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UndefinedTypesAssignmentTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UndefinedTypesAssignmentTest.java
new file mode 100644
index 0000000..9e8c086e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UndefinedTypesAssignmentTest.java
@@ -0,0 +1,84 @@
+// 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.stackmap;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/** Regression test for b/b/261967650 */
+@RunWith(Parameterized.class)
+public class UndefinedTypesAssignmentTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDefaultRuntimes()
+        .withApiLevel(AndroidApiLevel.B)
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  public UndefinedTypesAssignmentTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathClasses(I.class, A.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addDontWarn(I.class, A.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .allowDiagnosticWarningMessages()
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics
+                    .assertOnlyWarnings()
+                    .assertWarningsMatch(diagnosticType(UnverifiableCfCodeDiagnostic.class)))
+        .addRunClasspathClasses(I.class, A.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  interface I {}
+
+  static class A implements I {}
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      I i = null;
+      try {
+        i = new A();
+        System.out.println("Hello, world");
+      } catch (Exception e) {
+        System.out.println(i);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfIntTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfIntTest.java
index af62516..d7ca20f 100644
--- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfIntTest.java
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfIntTest.java
@@ -6,6 +6,8 @@
 
 import com.android.tools.r8.examples.jdk9.VarHandle;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
@@ -23,12 +25,12 @@
   }
 
   @Override
-  protected String getJarEntry() {
-    return JAR_ENTRY;
+  protected List<String> getJarEntries() {
+    return ImmutableList.of(JAR_ENTRY);
   }
 
   @Override
-  protected String getExpectedOutput() {
+  protected String getExpectedOutputForReferenceImplementation() {
     return EXPECTED_OUTPUT;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfLongTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfLongTest.java
index 46e130f..c3c81da 100644
--- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfLongTest.java
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfLongTest.java
@@ -6,6 +6,8 @@
 
 import com.android.tools.r8.examples.jdk9.VarHandle;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
@@ -23,12 +25,12 @@
   }
 
   @Override
-  protected String getJarEntry() {
-    return JAR_ENTRY;
+  protected List<String> getJarEntries() {
+    return ImmutableList.of(JAR_ENTRY);
   }
 
   @Override
-  protected String getExpectedOutput() {
+  protected String getExpectedOutputForReferenceImplementation() {
     return EXPECTED_OUTPUT;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceIntFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceIntFieldTest.java
index 9ae8ecd..7cfc0e5 100644
--- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceIntFieldTest.java
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceIntFieldTest.java
@@ -6,6 +6,8 @@
 
 import com.android.tools.r8.examples.jdk9.VarHandle;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
@@ -76,12 +78,12 @@
   }
 
   @Override
-  protected String getJarEntry() {
-    return JAR_ENTRY;
+  protected List<String> getJarEntries() {
+    return ImmutableList.of(JAR_ENTRY);
   }
 
   @Override
-  protected String getExpectedOutput() {
+  protected String getExpectedOutputForReferenceImplementation() {
     return EXPECTED_OUTPUT;
   }
 
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceLongFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceLongFieldTest.java
index c50db54..227cc76 100644
--- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceLongFieldTest.java
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceLongFieldTest.java
@@ -6,6 +6,8 @@
 
 import com.android.tools.r8.examples.jdk9.VarHandle;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
@@ -75,12 +77,12 @@
   }
 
   @Override
-  protected String getJarEntry() {
-    return JAR_ENTRY;
+  protected List<String> getJarEntries() {
+    return ImmutableList.of(JAR_ENTRY);
   }
 
   @Override
-  protected String getExpectedOutput() {
+  protected String getExpectedOutputForReferenceImplementation() {
     return EXPECTED_OUTPUT;
   }
 
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceObjectFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceObjectFieldTest.java
index b9b2b8e..ac43573 100644
--- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceObjectFieldTest.java
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceObjectFieldTest.java
@@ -6,6 +6,8 @@
 
 import com.android.tools.r8.examples.jdk9.VarHandle;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
@@ -13,9 +15,111 @@
 public class VarHandleDesugaringInstanceObjectFieldTest extends VarHandleDesugaringTestBase {
 
   private static final String EXPECTED_OUTPUT =
-      StringUtils.lines("testGet", "null", "1", "testCompareAndSet", "null", "1");
+      StringUtils.lines(
+          "testSet",
+          "null",
+          "A(1)",
+          "true",
+          "A(2)",
+          "true",
+          "1",
+          "1",
+          "true",
+          "true",
+          "2",
+          "2",
+          "true",
+          "true",
+          "3",
+          "3",
+          "true",
+          "true",
+          "4",
+          "4",
+          "true",
+          "true",
+          "5",
+          "5",
+          "true",
+          "true",
+          "6",
+          "6",
+          "true",
+          "true",
+          "7",
+          "7",
+          "true",
+          "true",
+          "8",
+          "8",
+          "true",
+          "true",
+          "9.0",
+          "9.0",
+          "true",
+          "true",
+          "10.0",
+          "10.0",
+          "true",
+          "true",
+          "11.0",
+          "11.0",
+          "true",
+          "true",
+          "12.0",
+          "12.0",
+          "true",
+          "true",
+          "A",
+          "A",
+          "true",
+          "true",
+          "B",
+          "B",
+          "true",
+          "true",
+          "testCompareAndSet",
+          "null",
+          "A(1)",
+          "true",
+          "A(2)",
+          "true",
+          "1",
+          "2",
+          "3",
+          "4",
+          "4",
+          "4",
+          "4",
+          "5",
+          "6",
+          "7",
+          "8",
+          "8",
+          "8",
+          "8",
+          "false",
+          "8",
+          "false",
+          "8",
+          "8",
+          "8",
+          "8",
+          "8",
+          "8",
+          "8",
+          "8",
+          "8",
+          "8",
+          "8",
+          "8",
+          "8",
+          "8",
+          "testReturnValueClassCastException");
   private static final String MAIN_CLASS = VarHandle.InstanceObjectField.typeName();
-  private static final String JAR_ENTRY = "varhandle/InstanceObjectField.class";
+  private static final List<String> JAR_ENTRIES =
+      ImmutableList.of(
+          "varhandle/InstanceObjectField.class", "varhandle/InstanceObjectField$A.class");
 
   @Override
   protected String getMainClass() {
@@ -28,12 +132,24 @@
   }
 
   @Override
-  protected String getJarEntry() {
-    return JAR_ENTRY;
+  protected List<String> getJarEntries() {
+    return JAR_ENTRIES;
   }
 
   @Override
-  protected String getExpectedOutput() {
-    return EXPECTED_OUTPUT;
+  protected String getExpectedOutputForReferenceImplementation() {
+    return StringUtils.lines(
+        EXPECTED_OUTPUT.trim(), "Reference implementation", "Reference implementation");
+  }
+
+  @Override
+  protected String getExpectedOutputForArtImplementation() {
+    assert parameters.isDexRuntime();
+    return StringUtils.lines(EXPECTED_OUTPUT.trim(), "Art implementation", "Art implementation");
+  }
+
+  @Override
+  protected boolean getTestWithDesugaring() {
+    return true;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceStringFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceStringFieldTest.java
index dfd32cf..f33f52c 100644
--- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceStringFieldTest.java
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceStringFieldTest.java
@@ -6,6 +6,8 @@
 
 import com.android.tools.r8.examples.jdk9.VarHandle;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
@@ -28,12 +30,12 @@
   }
 
   @Override
-  protected String getJarEntry() {
-    return JAR_ENTRY;
+  protected List<String> getJarEntries() {
+    return ImmutableList.of(JAR_ENTRY);
   }
 
   @Override
-  protected String getExpectedOutput() {
+  protected String getExpectedOutputForReferenceImplementation() {
     return EXPECTED_OUTPUT;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java
index 6a6c9d5..6a73d66 100644
--- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java
@@ -4,8 +4,12 @@
 
 package com.android.tools.r8.cf.varhandle;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -14,8 +18,20 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.examples.jdk9.VarHandle;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
+import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -45,9 +61,13 @@
     return "";
   }
 
-  protected abstract String getJarEntry();
+  protected abstract List<String> getJarEntries();
 
-  protected abstract String getExpectedOutput();
+  protected abstract String getExpectedOutputForReferenceImplementation();
+
+  protected String getExpectedOutputForArtImplementation() {
+    return getExpectedOutputForReferenceImplementation();
+  }
 
   // TODO(b/247076137): Remove this when all tests can run with desugaring.
   protected boolean getTestWithDesugaring() {
@@ -60,29 +80,129 @@
     testForJvm()
         .addProgramFiles(VarHandle.jar())
         .run(parameters.getRuntime(), getMainClass())
-        .assertSuccessWithOutput(getExpectedOutput());
+        .assertSuccessWithOutput(getExpectedOutputForReferenceImplementation());
   }
 
+  private List<byte[]> getProgramClassFileData() {
+    return getJarEntries().stream()
+        .map(
+            entry -> {
+              try {
+                return ZipUtils.readSingleEntry(VarHandle.jar(), entry);
+              } catch (IOException e) {
+                throw new RuntimeException(e);
+              }
+            })
+        .collect(Collectors.toList());
+  }
+
+  private boolean willDesugarVarHandle() {
+    return parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.T);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    IntBox unsafeCompareAndSwapInt = new IntBox();
+    IntBox unsafeCompareAndSwapLong = new IntBox();
+    IntBox unsafeCompareAndSwapObject = new IntBox();
+    DexString compareAndSwapInt = inspector.getFactory().createString("compareAndSwapInt");
+    DexString compareAndSwapLong = inspector.getFactory().createString("compareAndSwapLong");
+    DexString compareAndSwapObject = inspector.getFactory().createString("compareAndSwapObject");
+    // Right now we only expect one backport coming out of DesugarVarHandle - the backport with
+    // forwarding of Unsafe.compareAndSwapObject.
+    MethodReference firstBackportFromDesugarVarHandle =
+        SyntheticItemsTestUtils.syntheticBackportWithForwardingMethod(
+            Reference.classFromDescriptor(DexItemFactory.desugarVarHandleDescriptorString),
+            0,
+            Reference.method(
+                Reference.classFromDescriptor("Lsun/misc/Unsafe;"),
+                "compareAndSwapObject",
+                ImmutableList.of(
+                    Reference.typeFromDescriptor("Ljava/lang/Object;"),
+                    Reference.LONG,
+                    Reference.typeFromDescriptor("Ljava/lang/Object;"),
+                    Reference.typeFromDescriptor("Ljava/lang/Object;")),
+                Reference.BOOL));
+    inspector.forAllClasses(
+        clazz -> {
+          clazz.forAllMethods(
+              method -> {
+                method
+                    .instructions()
+                    .forEach(
+                        instruction -> {
+                          if (instruction.isInvoke()) {
+                            DexMethod target = instruction.getMethod();
+                            if (target.getHolderType() == inspector.getFactory().unsafeType) {
+                              if (target.getName() == compareAndSwapInt
+                                  || target.getName() == compareAndSwapLong) {
+                                // All compareAndSwapInt and compareAndSwapLong stay on
+                                // DesugarVarHandle.
+                                assertSame(
+                                    clazz.getDexProgramClass().getType(),
+                                    inspector.getFactory().desugarVarHandleType);
+                                unsafeCompareAndSwapInt.incrementIf(
+                                    target.getName() == compareAndSwapInt);
+                                unsafeCompareAndSwapLong.incrementIf(
+                                    target.getName() == compareAndSwapLong);
+                              } else if (target.getName() == compareAndSwapObject) {
+                                // compareAndSwapObject is not on DesugarVarHandle - it must be
+                                // backported.
+                                assertNotSame(
+                                    clazz.getDexProgramClass().getType(),
+                                    inspector.getFactory().desugarVarHandleType);
+                                assertEquals(
+                                    clazz.getFinalReference(),
+                                    firstBackportFromDesugarVarHandle.getHolderClass());
+                                assertEquals(1, clazz.allMethods().size());
+                                assertEquals(
+                                    firstBackportFromDesugarVarHandle,
+                                    clazz.allMethods().iterator().next().getFinalReference());
+                                unsafeCompareAndSwapObject.increment();
+                              }
+                            }
+                          }
+                        });
+              });
+        });
+    if (willDesugarVarHandle()) {
+      assertEquals(2, unsafeCompareAndSwapInt.get());
+      assertEquals(3, unsafeCompareAndSwapLong.get());
+      assertEquals(1, unsafeCompareAndSwapObject.get());
+    } else {
+      assertEquals(0, unsafeCompareAndSwapInt.get());
+      assertEquals(0, unsafeCompareAndSwapLong.get());
+      assertEquals(0, unsafeCompareAndSwapObject.get());
+    }
+  }
+
+  // TODO(b/247076137: Also turn on VarHandle desugaring for R8 tests.
   @Test
   public void testD8() throws Throwable {
     assumeTrue(parameters.isDexRuntime());
     if (getTestWithDesugaring()) {
       testForD8(parameters.getBackend())
-          .addProgramClassFileData(ZipUtils.readSingleEntry(VarHandle.jar(), getJarEntry()))
+          .addProgramClassFileData(getProgramClassFileData())
           .setMinApi(parameters.getApiLevel())
           .addOptionsModification(options -> options.enableVarHandleDesugaring = true)
           .run(parameters.getRuntime(), getMainClass())
           .applyIf(
               parameters.isDexRuntime()
                   && parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V4_4_4),
-              // TODO(sgjesse): Running on 4.0.4 and 4.4.4 needs to be checked. Output seems
-              // correct,
-              //  but at the same time there are VFY errors on stderr.
+              // TODO(b/247076137): Running on 4.0.4 and 4.4.4 needs to be checked. Output seems
+              // correct, but at the same time there are VFY errors on stderr.
               r -> r.assertFailureWithErrorThatThrows(NoSuchFieldException.class),
-              r -> r.assertSuccessWithOutput(getExpectedOutput()));
+              r ->
+                  r.assertSuccessWithOutput(
+                      parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.T)
+                              && parameters
+                                  .getDexRuntimeVersion()
+                                  .isNewerThanOrEqual(Version.V13_0_0)
+                          ? getExpectedOutputForArtImplementation()
+                          : getExpectedOutputForReferenceImplementation()))
+          .inspect(this::inspect);
     } else {
       testForD8(parameters.getBackend())
-          .addProgramClassFileData(ZipUtils.readSingleEntry(VarHandle.jar(), getJarEntry()))
+          .addProgramClassFileData(getProgramClassFileData())
           .setMinApi(parameters.getApiLevel())
           .run(parameters.getRuntime(), getMainClass())
           .applyIf(
@@ -93,7 +213,7 @@
               parameters.getApiLevel().isLessThan(AndroidApiLevel.P)
                   || parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V8_1_0),
               r -> r.assertFailure(),
-              r -> r.assertSuccessWithOutput(getExpectedOutput()));
+              r -> r.assertSuccessWithOutput(getExpectedOutputForArtImplementation()));
     }
   }
 
@@ -101,10 +221,13 @@
   @Test
   public void testR8() throws Throwable {
     testForR8(parameters.getBackend())
-        // Use android.jar from Android T to get the VarHandle type.
-        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
-        .addProgramClassFileData(ZipUtils.readSingleEntry(VarHandle.jar(), getJarEntry()))
-        .addLibraryFiles()
+        .applyIf(
+            parameters.isDexRuntime(),
+            // Use android.jar from Android T to get the VarHandle type.
+            b -> b.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)),
+            // Use system JDK to have references types including StringConcatFactory.
+            b -> b.addLibraryProvider(JdkClassFileProvider.fromSystemJdk()))
+        .addProgramClassFileData(getProgramClassFileData())
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(getMainClass())
         .addKeepRules(getKeepRules())
@@ -121,6 +244,10 @@
                 && (parameters.getApiLevel().isLessThan(AndroidApiLevel.P)
                     || parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V8_1_0)),
             r -> r.assertFailure(),
-            r -> r.assertSuccessWithOutput(getExpectedOutput()));
+            r ->
+                r.assertSuccessWithOutput(
+                    parameters.isCfRuntime()
+                        ? getExpectedOutputForReferenceImplementation()
+                        : getExpectedOutputForArtImplementation()));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java b/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java
index 1f41abc..1d4a205 100644
--- a/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java
@@ -122,7 +122,7 @@
     int target = cond.getTargets()[0] + cond.getOffset();
     int linePC = -1;
     for (DexDebugEntry entry : info.getEntries()) {
-      if (entry.line == 4) {
+      if (entry.getPosition().getLine() == 4) {
         linePC = entry.address;
         break;
       }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
index 605865f..00409e1 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
@@ -125,7 +125,7 @@
     for (int i = 0; i < entries.size(); i++) {
       DexDebugEntry entry = entries.get(i);
       // Matches each entry at 'line' that is not a zero-line increment.
-      if (entry.line == line && (i == 0 || entries.get(i - 1).line != line)) {
+      if (entry.getLine() == line && (i == 0 || entries.get(i - 1).getLine() != line)) {
         found++;
         check.accept(entry);
       }
@@ -139,7 +139,11 @@
     for (DebugLocalInfo local : entry.locals.values()) {
       if (local.name.toString().equals(name)) {
         if (found != null) {
-          fail("Line " + entry.line + ". Local defined multiple times for name: " + name);
+          fail(
+              "Line "
+                  + entry.getPosition().getLine()
+                  + ". Local defined multiple times for name: "
+                  + name);
         }
         assertEquals(type, local.type.toString());
         if (typeParameters.length > 0) {
@@ -155,7 +159,7 @@
         found = local;
       }
     }
-    assertNotNull("Line " + entry.line + ". Failed to find local with name: " + name, found);
+    assertNotNull("Line " + entry.getLine() + ". Failed to find local with name: " + name, found);
     return found;
   }
 
@@ -176,9 +180,14 @@
         remaining.remove(local);
       }
     }
-    assertEquals("Line " + entry.line + ". Found unexpected locals: " +
-            String.join(",", remaining.stream().map(Object::toString).collect(Collectors.toList())),
-        expected, expected + remaining.size());
+    assertEquals(
+        "Line "
+            + entry.getLine()
+            + ". Found unexpected locals: "
+            + String.join(
+                ",", remaining.stream().map(Object::toString).collect(Collectors.toList())),
+        expected,
+        expected + remaining.size());
   }
 
   public DexEncodedMethod getMethod() {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
index c6f0994..9d8cf281 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
@@ -146,7 +146,8 @@
     MethodSubject main = clazz.uniqueMethodWithOriginalName("main");
     List<DexDebugEntry> entries =
         new DexDebugEntryBuilder(main.getMethod(), inspector.getFactory()).build();
-    Set<Integer> lines = entries.stream().map(e -> e.line).collect(Collectors.toSet());
+    Set<Integer> lines =
+        entries.stream().map(e -> e.getPosition().getLine()).collect(Collectors.toSet());
     assertFalse(lines.isEmpty());
   }
 
diff --git a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
index 6af4440..944b163 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
@@ -97,7 +97,8 @@
               assertEquals(TEST_CLASS, line.className);
               assertEquals("main", line.methodName);
               // Expected line number could be PC based or increments.
-              // The test need not check what it is, just that all methods have been fully inlined.
+              // The test need not check what it is, just that all methods have been fully
+              // inlined.
               assertEquals(2, rawStackTrace.size());
             })
         .inspectStackTrace(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesWatchEventTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesWatchEventTest.java
new file mode 100644
index 0000000..6f3b986
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesWatchEventTest.java
@@ -0,0 +1,124 @@
+// 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.desugar.desugaredlibrary.jdk11;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FilesWatchEventTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines("true", "true", "true", "ENTRY_CREATE", "foo", "true");
+  private static final String EXPECTED_RESULT_DESUGARING =
+      StringUtils.lines("class java.lang.UnsupportedOperationException :: null");
+
+  private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        // Skip Android 4.4.4 due to missing libjavacrypto.
+        getTestParameters()
+            .withCfRuntime(CfVm.JDK11)
+            .withDexRuntime(Version.V4_0_4)
+            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+            .withAllApiLevels()
+            .build(),
+        ImmutableList.of(JDK11_PATH),
+        DEFAULT_SPECIFICATIONS);
+  }
+
+  public FilesWatchEventTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
+  }
+
+  @Test
+  public void test() throws Throwable {
+    if (parameters.isCfRuntime() && !ToolHelper.isWindows()) {
+      // Reference runtime, we use Jdk 11 since this is Jdk 11 desugared library, not that Jdk 8
+      // behaves differently on this test.
+      Assume.assumeTrue(parameters.isCfRuntime(CfVm.JDK11) && !ToolHelper.isWindows());
+      testForJvm()
+          .addInnerClasses(getClass())
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+      return;
+    }
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .compile()
+        .withArt6Plus64BitsLib()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(
+            libraryDesugaringSpecification.usesPlatformFileSystem(parameters)
+                ? EXPECTED_RESULT
+                : EXPECTED_RESULT_DESUGARING);
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) throws IOException, InterruptedException {
+      Path dir = Files.createTempDirectory("tmpDictWatch");
+      FileSystem fs = FileSystems.getDefault();
+
+      try (WatchService watcher = fs.newWatchService()) {
+        WatchKey myKey = dir.register(watcher, ENTRY_CREATE);
+        System.out.println(myKey.isValid());
+        System.out.println(myKey.watchable().equals(dir));
+
+        Path file = dir.resolve("foo");
+        Files.createFile(file);
+
+        WatchKey key = watcher.take();
+        System.out.println(key.equals(myKey));
+
+        WatchEvent<?> event = myKey.pollEvents().iterator().next();
+        System.out.println(event.kind());
+        System.out.println(event.context());
+
+        System.out.println(myKey.reset());
+        Files.delete(file);
+      } catch (UnsupportedOperationException e) {
+        System.out.println(e.getClass() + " :: " + e.getMessage());
+      }
+
+      Files.delete(dir);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11NioFileTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11NioFileTests.java
index 8274628..9f3a07b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11NioFileTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11NioFileTests.java
@@ -12,6 +12,7 @@
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8SHRINK;
 import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.D8TestCompileResult;
@@ -27,8 +28,10 @@
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -39,6 +42,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 import java.util.stream.Collectors;
 import org.jetbrains.annotations.NotNull;
 import org.junit.Assume;
@@ -74,7 +78,8 @@
         // TODO(134732760): Support Dalvik VMs, currently fails because libjavacrypto is required
         // and present only in ART runtimes.
         getTestParameters()
-            .withDexRuntimesStartingFromIncluding(Version.V10_0_0)
+            .withDexRuntime(Version.V4_0_4)
+            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
             .withAllApiLevels()
             .build(),
         specs,
@@ -106,6 +111,16 @@
             "module-info.java"
           });
 
+  private static final Set<String> EXPECTED_FAILING_CLASSES_DESUGARING_26 =
+      ImmutableSet.of(
+          // SecureDirectoryStream is not supported with desugared library, which leads to issues.
+          "DirectoryStreamSecureDS",
+          // Watch services are supported on high apis with desugared library, however, only
+          // equality
+          // and not identity is preserved which fails the tests.
+          "WatchServiceBasic",
+          "WatchServiceSensitivityModifier");
+
   // We distinguish 2 kinds of tests:
   // - Main tests, which are run by running the main method, and succeed if no error is raised.
   // - TestNG tests, which are run using testNG.
@@ -272,12 +287,12 @@
             .compile()
             .withArt6Plus64BitsLib();
     int success = 0;
-    int failures = 0;
+    List<String> failingClasses = new ArrayList<>();
     for (String mainTestClass : SUCCESSFUL_MAIN_TESTS) {
       SingleTestRunResult<?> run = compileResult.run(parameters.getRuntime(), mainTestClass);
       if (run.getExitCode() != 0) {
         System.out.println("Main Fail " + mainTestClass);
-        failures++;
+        failingClasses.add(mainTestClass);
       } else {
         success++;
       }
@@ -288,17 +303,31 @@
               parameters.getRuntime(), "TestNGMainRunner", verbosity, testNGTestClass);
       if (!result.getStdOut().contains(StringUtils.lines(testNGTestClass + ": SUCCESS"))) {
         System.out.println("TestNG Fail " + testNGTestClass);
-        failures++;
+        failingClasses.add(testNGTestClass);
       } else {
         success++;
       }
     }
-    // TODO(b/234689867): Understand and fix these issues.
-    // Most issues seem to come from the missing secure.properties file. This file is not accessed
-    // in all tests on all API levels, hence a different number of failures on each level.
-    System.out.println("Successes :" + success + "; failures " + failures);
-    assertTrue(success >= 11);
-    assertTrue(failures <= 20);
+    System.out.println("Successes:" + success + "; failures:" + failingClasses.size());
+    if (!failingClasses.isEmpty()) {
+      System.out.println("Failing classes: " + failingClasses);
+    }
+    if (parameters.getDexRuntimeVersion().isOlderThan(Version.V8_1_0)) {
+      // TODO(b/234689867): DesugaredFileSystemProvider is exercised, fix or understand remaining
+      // failures.
+      int sevenOffset = parameters.getDexRuntimeVersion() == Version.V7_0_0 ? -1 : 0;
+      int shrinkOffset = compilationSpecification.isL8Shrink() ? -1 : 0;
+      assertTrue(success >= 18 + sevenOffset + shrinkOffset);
+    } else if (parameters.getApiLevel().isLessThan(AndroidApiLevel.O)) {
+      // Desugaring high api level.
+      assertEquals(26, success);
+      assertEquals(3, failingClasses.size());
+      assertTrue(failingClasses.containsAll(EXPECTED_FAILING_CLASSES_DESUGARING_26));
+    } else {
+      // No desugaring or partial desugaring, high api level.
+      assertEquals(29, success);
+      assertEquals(0, failingClasses.size());
+    }
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java
index af2bb5b..30b3240 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java
@@ -96,7 +96,7 @@
     return new RuntimeException("java.lang.invoke.WrongMethodTypeException");
   }
 
-  int toIntIfPossible(Object value) {
+  int toIntIfPossible(Object value, boolean forReturnType) {
     if (value instanceof Integer) {
       return (Integer) value;
     }
@@ -109,14 +109,18 @@
     if (value instanceof Short) {
       return (Short) value;
     }
-    throw desugarWrongMethodTypeException();
+    if (forReturnType) {
+      throw new ClassCastException();
+    } else {
+      throw desugarWrongMethodTypeException();
+    }
   }
 
-  long toLongIfPossible(Object value) {
+  long toLongIfPossible(Object value, boolean forReturnType) {
     if (value instanceof Long) {
       return (Long) value;
     }
-    return toIntIfPossible(value);
+    return toIntIfPossible(value, forReturnType);
   }
 
   // get variants.
@@ -131,19 +135,31 @@
   }
 
   int getInt(Object ct1) {
-    return U.getInt(ct1, offset);
+    if (type == int.class) {
+      return U.getInt(ct1, offset);
+    } else if (type == long.class) {
+      return (int) U.getLong(ct1, offset);
+    } else {
+      return toIntIfPossible(U.getObject(ct1, offset), true);
+    }
   }
 
   long getLong(Object ct1) {
-    return U.getLong(ct1, offset);
+    if (type == long.class) {
+      return U.getLong(ct1, offset);
+    } else if (type == int.class) {
+      return U.getInt(ct1, offset);
+    } else {
+      return toLongIfPossible(U.getObject(ct1, offset), true);
+    }
   }
 
   // set variants.
   void set(Object ct1, Object newValue) {
     if (type == int.class) {
-      setInt(ct1, toIntIfPossible(newValue));
+      setInt(ct1, toIntIfPossible(newValue, false));
     } else if (type == long.class) {
-      setLong(ct1, toLongIfPossible(newValue));
+      setLong(ct1, toLongIfPossible(newValue, false));
     } else {
       U.putObject(ct1, offset, newValue);
     }
@@ -162,37 +178,39 @@
   void setLong(Object ct1, long newValue) {
     if (type == long.class) {
       U.putLong(ct1, offset, newValue);
-    } else {
+    } else if (type == int.class) {
       throw desugarWrongMethodTypeException();
+    } else {
+      U.putObject(ct1, offset, Long.valueOf(newValue));
     }
   }
 
-  boolean compareAndSet(Object ct1, Object expetedValue, Object newValue) {
+  boolean compareAndSet(Object ct1, Object expectedValue, Object newValue) {
     if (type == int.class) {
       return U.compareAndSwapInt(
-          ct1, offset, toIntIfPossible(expetedValue), toIntIfPossible((newValue)));
+          ct1, offset, toIntIfPossible(expectedValue, false), toIntIfPossible(newValue, false));
     }
     if (type == long.class) {
       return U.compareAndSwapLong(
-          ct1, offset, toLongIfPossible(expetedValue), toLongIfPossible((newValue)));
+          ct1, offset, toLongIfPossible(expectedValue, false), toLongIfPossible(newValue, false));
     }
-    return U.compareAndSwapObject(ct1, offset, expetedValue, newValue);
+    return U.compareAndSwapObject(ct1, offset, expectedValue, newValue);
   }
 
-  boolean compareAndSetInt(Object ct1, int expetedValue, int newValue) {
+  boolean compareAndSetInt(Object ct1, int expectedValue, int newValue) {
     if (type == int.class) {
-      return U.compareAndSwapInt(ct1, offset, expetedValue, newValue);
+      return U.compareAndSwapInt(ct1, offset, expectedValue, newValue);
     } else if (type == long.class) {
-      return U.compareAndSwapLong(ct1, offset, expetedValue, newValue);
+      return U.compareAndSwapLong(ct1, offset, expectedValue, newValue);
     } else {
-      return compareAndSet(ct1, expetedValue, newValue);
+      return compareAndSet(ct1, expectedValue, newValue);
     }
   }
 
-  boolean compareAndSetLong(Object ct1, long expetedValue, long newValue) {
+  boolean compareAndSetLong(Object ct1, long expectedValue, long newValue) {
     if (type == long.class) {
-      return U.compareAndSwapLong(ct1, offset, expetedValue, newValue);
+      return U.compareAndSwapLong(ct1, offset, expectedValue, newValue);
     }
-    return compareAndSet(ct1, expetedValue, newValue);
+    return compareAndSet(ct1, expectedValue, newValue);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java
index 9bee408..f9d98e3 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java
@@ -3,6 +3,7 @@
 // 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;
 
@@ -53,13 +54,14 @@
         .addKeepRules(rules)
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters.getApiLevel())
+        .allowUnusedProguardConfigurationRules()
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED)
         .inspect(this::checkOutput);
   }
 
   public List<Class<?>> getInputClasses() {
-    return ImmutableList.of(TestClass.class, A.class, B.class);
+    return ImmutableList.of(TestClass.class, A.class, B.class, C.class);
   }
 
   public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
@@ -83,7 +85,9 @@
   private void checkOutput(CodeInspector inspector) {
     assertThat(inspector.clazz(A.class), isPresent());
     assertThat(inspector.clazz(B.class), isPresent());
-    assertThat(inspector.clazz(B.class).uniqueMethodWithOriginalName("bar"), isPresent());
+    assertThat(inspector.clazz(C.class), isAbsent());
+    assertThat(inspector.clazz(B.class).method("void", "bar"), isPresent());
+    assertThat(inspector.clazz(B.class).method("void", "bar", "int"), isAbsent());
   }
 
   static class A {
@@ -94,18 +98,36 @@
       // Ensure that the class B remains as we are looking it up by reflected name.
       @KeepTarget(classConstant = B.class),
       // Ensure the method 'bar' remains as we are invoking it by reflected name.
-      @KeepTarget(classConstant = B.class, methodName = "bar")
+      @KeepTarget(
+          classConstant = B.class,
+          methodName = "bar",
+          methodParameters = {},
+          methodReturnType = "void")
     })
     public void foo() throws Exception {
       Class<?> clazz = Class.forName(A.class.getTypeName().replace("$A", "$B"));
       clazz.getDeclaredMethod("bar").invoke(clazz);
     }
+
+    // This annotation is not active as its implicit precondition "void A.foo(int)" is not used.
+    @UsesReflection({@KeepTarget(classConstant = C.class)})
+    public void foo(int unused) {
+      // Unused.
+    }
   }
 
   static class B {
     public static void bar() {
       System.out.println("Hello, world");
     }
+
+    public static void bar(int ignore) {
+      throw new RuntimeException("UNUSED");
+    }
+  }
+
+  static class C {
+    // Unused.
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java
new file mode 100644
index 0000000..b6cc560
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java
@@ -0,0 +1,131 @@
+// 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 com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepUsesReflectionFieldAnnotationTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+  }
+
+  public KeepUsesReflectionFieldAnnotationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getInputClasses())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testWithRuleExtraction() throws Exception {
+    List<String> rules = getExtractedKeepRules();
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getInputClassesWithoutAnnotations())
+        .addKeepRules(rules)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkOutput);
+  }
+
+  public List<Class<?>> getInputClasses() {
+    return ImmutableList.of(TestClass.class, A.class, B.class);
+  }
+
+  public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
+    List<Class<?>> classes = getInputClasses();
+    List<byte[]> transformed = new ArrayList<>(classes.size());
+    for (Class<?> clazz : classes) {
+      transformed.add(transformer(clazz).removeAllAnnotations().transform());
+    }
+    return transformed;
+  }
+
+  public List<String> getExtractedKeepRules() throws Exception {
+    List<Class<?>> classes = getInputClasses();
+    List<String> rules = new ArrayList<>();
+    for (Class<?> clazz : classes) {
+      rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz));
+    }
+    return rules;
+  }
+
+  private void checkOutput(CodeInspector inspector) {
+    assertThat(inspector.clazz(A.class), isPresent());
+    assertThat(inspector.clazz(A.class).uniqueFieldWithOriginalName("classNameForB"), isPresent());
+    assertThat(inspector.clazz(B.class), isPresent());
+    assertThat(inspector.clazz(B.class).init(), isPresent());
+    assertThat(inspector.clazz(B.class).init("int"), isAbsent());
+  }
+
+  static class A {
+
+    @UsesReflection({
+      @KeepTarget(classConstant = B.class),
+      @KeepTarget(
+          classConstant = B.class,
+          methodName = "<init>",
+          methodParameters = {}),
+    })
+    public final String classNameForB =
+        System.nanoTime() == 0
+            ? null
+            : "com.android.tools.r8.keepanno.KeepUsesReflectionFieldAnnotationTest$B";
+
+    public B foo() throws Exception {
+      return (B) Class.forName(classNameForB).getDeclaredConstructor().newInstance();
+    }
+  }
+
+  static class B {
+    B() {
+      // Used.
+    }
+
+    B(int unused) {
+      // Unused.
+    }
+
+    public void bar() {
+      System.out.println("Hello, world");
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      new A().foo().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java
new file mode 100644
index 0000000..6739144
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java
@@ -0,0 +1,113 @@
+// 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 com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepUsesReflectionOnFieldTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+  }
+
+  public KeepUsesReflectionOnFieldTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getInputClasses())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testWithRuleExtraction() throws Exception {
+    List<String> rules = getExtractedKeepRules();
+    System.out.println(rules);
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getInputClassesWithoutAnnotations())
+        .addKeepRules(rules)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkOutput);
+  }
+
+  public List<Class<?>> getInputClasses() {
+    return ImmutableList.of(TestClass.class, A.class);
+  }
+
+  public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
+    List<Class<?>> classes = getInputClasses();
+    List<byte[]> transformed = new ArrayList<>(classes.size());
+    for (Class<?> clazz : classes) {
+      transformed.add(transformer(clazz).removeAllAnnotations().transform());
+    }
+    return transformed;
+  }
+
+  public List<String> getExtractedKeepRules() throws Exception {
+    List<Class<?>> classes = getInputClasses();
+    List<String> rules = new ArrayList<>();
+    for (Class<?> clazz : classes) {
+      rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz));
+    }
+    return rules;
+  }
+
+  private void checkOutput(CodeInspector inspector) {
+    assertThat(inspector.clazz(A.class), isPresent());
+    assertThat(inspector.clazz(A.class).uniqueFieldWithOriginalName("fieldA"), isPresent());
+    assertThat(inspector.clazz(A.class).uniqueFieldWithOriginalName("fieldB"), isAbsent());
+  }
+
+  static class A {
+
+    public String fieldA = "Hello, world";
+    public Integer fieldB = 42;
+
+    @UsesReflection({@KeepTarget(classConstant = A.class, fieldType = "java.lang.String")})
+    public void foo() throws Exception {
+      for (Field field : getClass().getDeclaredFields()) {
+        if (field.getType().equals(String.class)) {
+          System.out.println(field.get(this));
+        }
+      }
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index ff769a2..1b67cfc 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -10,8 +10,10 @@
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.synthesis.SyntheticNaming.Phase;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.google.common.collect.ImmutableList;
 import java.lang.reflect.Method;
 import org.hamcrest.Matcher;
 
@@ -51,6 +53,24 @@
         originalMethod.getMethodDescriptor());
   }
 
+  public static MethodReference syntheticBackportWithForwardingMethod(
+      ClassReference clazz, int id, MethodReference method) {
+    // For backports with forwarding the backported method is not static, so the original method
+    // signature has the receiver type pre-pended.
+    ImmutableList.Builder<TypeReference> builder = ImmutableList.builder();
+    builder.add(method.getHolderClass()).addAll(method.getFormalTypes());
+    MethodReference methodWithReceiverForForwarding =
+        Reference.method(
+            method.getHolderClass(),
+            method.getMethodName(),
+            builder.build(),
+            method.getReturnType());
+    return Reference.methodFromDescriptor(
+        syntheticBackportWithForwardingClass(clazz, id),
+        syntheticMethodName(),
+        methodWithReceiverForForwarding.getMethodDescriptor());
+  }
+
   public static ClassReference syntheticOutlineClass(Class<?> clazz, int id) {
     return syntheticClass(clazz, naming.OUTLINE, id);
   }
@@ -85,6 +105,11 @@
     return syntheticClass(classReference, naming.BACKPORT, id);
   }
 
+  public static ClassReference syntheticBackportWithForwardingClass(
+      ClassReference classReference, int id) {
+    return syntheticClass(classReference, naming.BACKPORT_WITH_FORWARDING, id);
+  }
+
   public static ClassReference syntheticTwrCloseResourceClass(Class<?> clazz, int id) {
     return syntheticClass(clazz, naming.TWR_CLOSE_RESOURCE, id);
   }
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 bcd5360..1974bcd 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -1494,7 +1494,7 @@
           @Override
           public FieldVisitor visitField(
               int access, String name, String descriptor, String signature, Object value) {
-            FieldVisitor fv = visitField(access, name, descriptor, signature, value);
+            FieldVisitor fv = super.visitField(access, name, descriptor, signature, value);
             return new FieldVisitor(ASM_VERSION, fv) {
               @Override
               public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
diff --git a/tools/r8_release.py b/tools/r8_release.py
index fe921e2..78fd031 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -193,8 +193,7 @@
       # Don't upload if requested not to, or if changes are not committed due
       # to --use-existing-work-branch
       if not options.no_upload and not options.use_existing_work_branch:
-        # TODO(b/260680525): Consider removing "-o banned-words~skip" if b/260680525 is resolved.
-        process = subprocess.Popen(['repo', 'upload', '.', '--verify', '-o' 'banned-words~skip'],
+        process = subprocess.Popen(['repo', 'upload', '.', '--verify'],
                                    stdin=subprocess.PIPE)
         return process.communicate(input=b'y\n')[0]