[Retrace] Extend the RetraceApi with FrameResult

This change updates the retrace api such than one can structurally
disambiguate inline frames - that are not ambiguous - with ambiguous
results.

Bug: 170491519
Change-Id: I6b7e17aba74b16b04c6fae65413f51c952dab3e8
diff --git a/src/main/java/com/android/tools/r8/retrace/Definition.java b/src/main/java/com/android/tools/r8/retrace/Definition.java
new file mode 100644
index 0000000..f955ac0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/Definition.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2020, 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.retrace;
+
+import com.android.tools.r8.references.ClassReference;
+
+interface Definition {
+
+  String getName();
+
+  ClassReference getHolderClass();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/FieldDefinition.java b/src/main/java/com/android/tools/r8/retrace/FieldDefinition.java
new file mode 100644
index 0000000..3dcf1c9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/FieldDefinition.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2020, 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.retrace;
+
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
+import java.util.Objects;
+
+/** Internal encoding of a field that allows for having either basic info or full info. */
+abstract class FieldDefinition implements Definition {
+
+  static FieldDefinition create(ClassReference obfuscatedReference, String fieldName) {
+    return new BaseFieldDefinition(obfuscatedReference, fieldName);
+  }
+
+  public static FieldDefinition create(FieldReference field) {
+    return new FullFieldDefinition(field);
+  }
+
+  abstract FieldDefinition substituteHolder(ClassReference newHolder);
+
+  static class BaseFieldDefinition extends FieldDefinition {
+    private final ClassReference classReference;
+    private final String name;
+
+    private BaseFieldDefinition(ClassReference classReference, String name) {
+      this.classReference = classReference;
+      this.name = name;
+    }
+
+    @Override
+    public String getName() {
+      return name;
+    }
+
+    @Override
+    public ClassReference getHolderClass() {
+      return classReference;
+    }
+
+    @Override
+    FieldDefinition substituteHolder(ClassReference newHolder) {
+      return FieldDefinition.create(classReference, name);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      BaseFieldDefinition that = (BaseFieldDefinition) o;
+      return classReference.equals(that.classReference) && name.equals(that.name);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(classReference, name);
+    }
+  }
+
+  static class FullFieldDefinition extends FieldDefinition {
+
+    private final FieldReference fieldReference;
+
+    private FullFieldDefinition(FieldReference fieldReference) {
+      this.fieldReference = fieldReference;
+    }
+
+    @Override
+    public String getName() {
+      return fieldReference.getFieldName();
+    }
+
+    @Override
+    public ClassReference getHolderClass() {
+      return fieldReference.getHolderClass();
+    }
+
+    @Override
+    FieldDefinition substituteHolder(ClassReference newHolder) {
+      return create(
+          Reference.field(newHolder, fieldReference.getFieldName(), fieldReference.getFieldType()));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      FullFieldDefinition that = (FullFieldDefinition) o;
+      return fieldReference.equals(that.fieldReference);
+    }
+
+    @Override
+    public int hashCode() {
+      return fieldReference.hashCode();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/MethodDefinition.java b/src/main/java/com/android/tools/r8/retrace/MethodDefinition.java
new file mode 100644
index 0000000..8641f13
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/MethodDefinition.java
@@ -0,0 +1,135 @@
+// Copyright (c) 2020, 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.retrace;
+
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import java.util.Objects;
+
+/** Internal encoding of a method that allows for having either basic info or full info. */
+abstract class MethodDefinition implements Definition {
+
+  static MethodDefinition create(ClassReference classReference, String methodName) {
+    return new BaseMethodDefinition(classReference, methodName);
+  }
+
+  static MethodDefinition create(MethodReference methodReference) {
+    return new FullMethodDefinition(methodReference);
+  }
+
+  boolean isFullMethodDefinition() {
+    return false;
+  }
+
+  FullMethodDefinition asFullMethodDefinition() {
+    return null;
+  }
+
+  abstract MethodDefinition substituteHolder(ClassReference newHolder);
+
+  static class BaseMethodDefinition extends MethodDefinition {
+
+    private final ClassReference classReference;
+    private final String name;
+
+    private BaseMethodDefinition(ClassReference classReference, String name) {
+      this.classReference = classReference;
+      this.name = name;
+    }
+
+    @Override
+    public ClassReference getHolderClass() {
+      return classReference;
+    }
+
+    @Override
+    public String getName() {
+      return name;
+    }
+
+    @Override
+    MethodDefinition substituteHolder(ClassReference newHolder) {
+      return MethodDefinition.create(newHolder, name);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      BaseMethodDefinition that = (BaseMethodDefinition) o;
+      return classReference.equals(that.classReference) && name.equals(that.name);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(classReference, name);
+    }
+  }
+
+  static class FullMethodDefinition extends MethodDefinition {
+
+    private final MethodReference methodReference;
+
+    private FullMethodDefinition(MethodReference methodReference) {
+      this.methodReference = methodReference;
+    }
+
+    @Override
+    public ClassReference getHolderClass() {
+      return methodReference.getHolderClass();
+    }
+
+    @Override
+    public String getName() {
+      return methodReference.getMethodName();
+    }
+
+    @Override
+    boolean isFullMethodDefinition() {
+      return true;
+    }
+
+    @Override
+    FullMethodDefinition asFullMethodDefinition() {
+      return this;
+    }
+
+    @Override
+    MethodDefinition substituteHolder(ClassReference newHolder) {
+      return MethodDefinition.create(
+          Reference.method(
+              newHolder,
+              methodReference.getMethodName(),
+              methodReference.getFormalTypes(),
+              methodReference.getReturnType()));
+    }
+
+    MethodReference getMethodReference() {
+      return methodReference;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      FullMethodDefinition that = (FullMethodDefinition) o;
+      return methodReference.equals(that.methodReference);
+    }
+
+    @Override
+    public int hashCode() {
+      return methodReference.hashCode();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceApi.java b/src/main/java/com/android/tools/r8/retrace/RetraceApi.java
index d3bd6d3..7a1eb4e 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceApi.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceApi.java
@@ -14,11 +14,15 @@
 @Keep
 public interface RetraceApi {
 
+  // TODO(b/170711681): Rename these to not have overloads.
+
   RetraceClassResult retrace(ClassReference classReference);
 
   RetraceMethodResult retrace(MethodReference methodReference);
 
   RetraceFieldResult retrace(FieldReference fieldReference);
 
+  RetraceFrameResult retrace(MethodReference methodReference, int position);
+
   RetraceTypeResult retrace(TypeReference typeReference);
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassMemberElement.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassMemberElement.java
new file mode 100644
index 0000000..c21a100
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassMemberElement.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2020, 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.retrace;
+
+import java.util.function.BiConsumer;
+
+public interface RetraceClassMemberElement<T extends RetracedClassMember> {
+
+  boolean isUnknown();
+
+  default boolean isFrameElement() {
+    return false;
+  }
+
+  default RetraceFrameResult.Element asFrameElement() {
+    return null;
+  }
+
+  RetraceClassResult.Element getClassElement();
+
+  T getMember();
+
+  void visitFrames(BiConsumer<T, Integer> consumer);
+
+  RetraceSourceFileResult retraceSourceFile(RetracedClassMember frame, String sourceFile);
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
index 9ea9614..795311e 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.retrace.RetraceClassResult.Element;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.ImmutableList;
@@ -43,8 +44,28 @@
   }
 
   public RetraceFieldResult lookupField(String fieldName) {
+    return lookupField(FieldDefinition.create(obfuscatedReference, fieldName));
+  }
+
+  public RetraceFieldResult lookupField(String fieldName, TypeReference fieldType) {
+    return lookupField(
+        FieldDefinition.create(Reference.field(obfuscatedReference, fieldName, fieldType)));
+  }
+
+  public RetraceMethodResult lookupMethod(String methodName) {
+    return lookupMethod(MethodDefinition.create(obfuscatedReference, methodName));
+  }
+
+  public RetraceMethodResult lookupMethod(
+      String methodName, List<TypeReference> formalTypes, TypeReference returnType) {
+    return lookupMethod(
+        MethodDefinition.create(
+            Reference.method(obfuscatedReference, methodName, formalTypes, returnType)));
+  }
+
+  private RetraceFieldResult lookupField(FieldDefinition fieldDefinition) {
     return lookup(
-        fieldName,
+        fieldDefinition,
         (mapper, name) -> {
           List<MemberNaming> memberNamings = mapper.mappedFieldNamingsByName.get(name);
           if (memberNamings == null || memberNamings.isEmpty()) {
@@ -55,9 +76,9 @@
         RetraceFieldResult::new);
   }
 
-  public RetraceMethodResult lookupMethod(String methodName) {
+  private RetraceMethodResult lookupMethod(MethodDefinition methodDefinition) {
     return lookup(
-        methodName,
+        methodDefinition,
         (mapper, name) -> {
           MappedRangesOfName mappedRanges = mapper.mappedRangesByRenamedName.get(name);
           if (mappedRanges == null || mappedRanges.getMappedRanges().isEmpty()) {
@@ -68,16 +89,16 @@
         RetraceMethodResult::new);
   }
 
-  private <T, R> R lookup(
-      String name,
+  private <T, R, D extends Definition> R lookup(
+      D definition,
       BiFunction<ClassNamingForNameMapper, String, T> lookupFunction,
-      ResultConstructor<T, R> constructor) {
+      ResultConstructor<T, R, D> constructor) {
     List<Pair<Element, T>> mappings = new ArrayList<>();
     forEach(
         element -> {
           if (mapper != null) {
             assert element.mapper != null;
-            T mappedElements = lookupFunction.apply(element.mapper, name);
+            T mappedElements = lookupFunction.apply(element.mapper, definition.getName());
             if (mappedElements != null) {
               mappings.add(new Pair<>(element, mappedElements));
               return;
@@ -85,7 +106,7 @@
           }
           mappings.add(new Pair<>(element, null));
         });
-    return constructor.create(this, mappings, name, retracer);
+    return constructor.create(this, mappings, definition, retracer);
   }
 
   boolean hasRetraceResult() {
@@ -110,11 +131,11 @@
     return this;
   }
 
-  private interface ResultConstructor<T, R> {
+  private interface ResultConstructor<T, R, D> {
     R create(
         RetraceClassResult classResult,
         List<Pair<Element, T>> mappings,
-        String obfuscatedName,
+        D definition,
         RetraceApi retraceApi);
   }
 
@@ -170,8 +191,12 @@
     }
 
     public RetraceFieldResult lookupField(String fieldName) {
+      return lookupField(FieldDefinition.create(classReference.getClassReference(), fieldName));
+    }
+
+    private RetraceFieldResult lookupField(FieldDefinition fieldDefinition) {
       return lookup(
-          fieldName,
+          fieldDefinition,
           (mapper, name) -> {
             List<MemberNaming> memberNamings = mapper.mappedFieldNamingsByName.get(name);
             if (memberNamings == null || memberNamings.isEmpty()) {
@@ -183,8 +208,12 @@
     }
 
     public RetraceMethodResult lookupMethod(String methodName) {
+      return lookupMethod(MethodDefinition.create(classReference.getClassReference(), methodName));
+    }
+
+    private RetraceMethodResult lookupMethod(MethodDefinition methodDefinition) {
       return lookup(
-          methodName,
+          methodDefinition,
           (mapper, name) -> {
             MappedRangesOfName mappedRanges = mapper.mappedRangesByRenamedName.get(name);
             if (mappedRanges == null || mappedRanges.getMappedRanges().isEmpty()) {
@@ -195,13 +224,13 @@
           RetraceMethodResult::new);
     }
 
-    private <T, R> R lookup(
-        String name,
+    private <T, R, D extends Definition> R lookup(
+        D definition,
         BiFunction<ClassNamingForNameMapper, String, T> lookupFunction,
-        ResultConstructor<T, R> constructor) {
+        ResultConstructor<T, R, D> constructor) {
       List<Pair<Element, T>> mappings = ImmutableList.of();
       if (mapper != null) {
-        T result = lookupFunction.apply(mapper, name);
+        T result = lookupFunction.apply(mapper, definition.getName());
         if (result != null) {
           mappings = ImmutableList.of(new Pair<>(this, result));
         }
@@ -209,7 +238,7 @@
       if (mappings.isEmpty()) {
         mappings = ImmutableList.of(new Pair<>(this, null));
       }
-      return constructor.create(classResult, mappings, name, classResult.retracer);
+      return constructor.create(classResult, mappings, definition, classResult.retracer);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
index 114f193..46f247d 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
@@ -19,17 +19,17 @@
 
   private final RetraceClassResult classResult;
   private final List<Pair<RetraceClassResult.Element, List<MemberNaming>>> memberNamings;
-  private final String obfuscatedName;
+  private final FieldDefinition fieldDefinition;
   private final RetraceApi retracer;
 
   RetraceFieldResult(
       RetraceClassResult classResult,
       List<Pair<RetraceClassResult.Element, List<MemberNaming>>> memberNamings,
-      String obfuscatedName,
+      FieldDefinition fieldDefinition,
       RetraceApi retracer) {
     this.classResult = classResult;
     this.memberNamings = memberNamings;
-    this.obfuscatedName = obfuscatedName;
+    this.fieldDefinition = fieldDefinition;
     this.retracer = retracer;
     assert classResult != null;
     assert !memberNamings.isEmpty();
@@ -59,8 +59,9 @@
                     new RetraceFieldResult.Element(
                         this,
                         classElement,
-                        RetracedField.createUnknown(
-                            classElement.getRetracedClass(), obfuscatedName)));
+                        RetracedField.create(
+                            fieldDefinition.substituteHolder(
+                                classElement.getRetracedClass().getClassReference()))));
               }
               return memberNamings.stream()
                   .map(
@@ -78,7 +79,6 @@
                             this,
                             classElement,
                             RetracedField.create(
-                                holder,
                                 Reference.field(
                                     holder.getClassReference(),
                                     fieldSignature.isQualified()
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceFrameResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceFrameResult.java
new file mode 100644
index 0000000..95dc9d5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFrameResult.java
@@ -0,0 +1,164 @@
+// Copyright (c) 2020, 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.retrace;
+
+import static com.android.tools.r8.retrace.RetraceUtils.methodReferenceFromMappedRange;
+
+import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+public class RetraceFrameResult extends Result<RetraceFrameResult.Element, RetraceFrameResult> {
+
+  private final RetraceClassResult classResult;
+  private final MethodDefinition methodDefinition;
+  private final int obfuscatedPosition;
+  private final List<Pair<RetraceClassResult.Element, List<MappedRange>>> mappedRanges;
+  private final RetraceApi retracer;
+
+  public RetraceFrameResult(
+      RetraceClassResult classResult,
+      List<Pair<RetraceClassResult.Element, List<MappedRange>>> mappedRanges,
+      MethodDefinition methodDefinition,
+      int obfuscatedPosition,
+      RetraceApi retracer) {
+    this.classResult = classResult;
+    this.methodDefinition = methodDefinition;
+    this.obfuscatedPosition = obfuscatedPosition;
+    this.mappedRanges = mappedRanges;
+    this.retracer = retracer;
+  }
+
+  @Override
+  public boolean isAmbiguous() {
+    return mappedRanges.size() > 1;
+  }
+
+  @Override
+  public Stream<Element> stream() {
+    return mappedRanges.stream()
+        .map(
+            mappedRangePair -> {
+              RetraceClassResult.Element classElement = mappedRangePair.getFirst();
+              List<MappedRange> mappedRanges = mappedRangePair.getSecond();
+              if (mappedRanges == null || mappedRanges.isEmpty()) {
+                return new Element(
+                    this,
+                    classElement,
+                    RetracedMethod.create(
+                        methodDefinition.substituteHolder(
+                            classElement.getRetracedClass().getClassReference())),
+                    ImmutableList.of(),
+                    obfuscatedPosition);
+              }
+              MappedRange mappedRange = mappedRanges.get(0);
+              MethodReference methodReference =
+                  methodReferenceFromMappedRange(
+                      mappedRange, classElement.getRetracedClass().getClassReference());
+              RetracedMethod retracedMethod =
+                  RetracedMethod.create(
+                      methodReference, mappedRange.getOriginalLineNumber(obfuscatedPosition));
+              return new Element(
+                  this, classElement, retracedMethod, mappedRanges, obfuscatedPosition);
+            });
+  }
+
+  @Override
+  public RetraceFrameResult forEach(Consumer<Element> resultConsumer) {
+    stream().forEach(resultConsumer);
+    return this;
+  }
+
+  public static class Element implements RetraceClassMemberElement<RetracedMethod> {
+
+    private final RetracedMethod methodReference;
+    private final RetraceFrameResult retraceFrameResult;
+    private final RetraceClassResult.Element classElement;
+    private final List<MappedRange> mappedRanges;
+    private final int obfuscatedPosition;
+
+    public Element(
+        RetraceFrameResult retraceFrameResult,
+        RetraceClassResult.Element classElement,
+        RetracedMethod methodReference,
+        List<MappedRange> mappedRanges,
+        int obfuscatedPosition) {
+      this.methodReference = methodReference;
+      this.retraceFrameResult = retraceFrameResult;
+      this.classElement = classElement;
+      this.mappedRanges = mappedRanges;
+      this.obfuscatedPosition = obfuscatedPosition;
+    }
+
+    @Override
+    public boolean isUnknown() {
+      return methodReference.isUnknown();
+    }
+
+    @Override
+    public boolean isFrameElement() {
+      return true;
+    }
+
+    @Override
+    public Element asFrameElement() {
+      return this;
+    }
+
+    @Override
+    public RetracedMethod getMember() {
+      return methodReference;
+    }
+
+    public RetracedMethod getTopFrame() {
+      return methodReference;
+    }
+
+    @Override
+    public RetraceClassResult.Element getClassElement() {
+      return classElement;
+    }
+
+    @Override
+    public void visitFrames(BiConsumer<RetracedMethod, Integer> consumer) {
+      int counter = 0;
+      consumer.accept(methodReference, counter++);
+      for (RetracedMethod outerFrame : getOuterFrames()) {
+        consumer.accept(outerFrame, counter++);
+      }
+    }
+
+    @Override
+    public RetraceSourceFileResult retraceSourceFile(RetracedClassMember frame, String sourceFile) {
+      return RetraceUtils.getSourceFile(
+          classElement, frame.getHolderClass(), sourceFile, retraceFrameResult.retracer);
+    }
+
+    public List<RetracedMethod> getOuterFrames() {
+      if (mappedRanges == null) {
+        return Collections.emptyList();
+      }
+      List<RetracedMethod> outerFrames = new ArrayList<>();
+      for (int i = 1; i < mappedRanges.size(); i++) {
+        MappedRange mappedRange = mappedRanges.get(i);
+        MethodReference methodReference =
+            methodReferenceFromMappedRange(
+                mappedRange, classElement.getRetracedClass().getClassReference());
+        outerFrames.add(
+            RetracedMethod.create(
+                MethodDefinition.create(methodReference),
+                mappedRange.getOriginalLineNumber(obfuscatedPosition)));
+      }
+      return outerFrames;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
index 7a41f74..0906420 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
@@ -7,21 +7,19 @@
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 
 @Keep
 public class RetraceMethodResult extends Result<RetraceMethodResult.Element, RetraceMethodResult> {
 
-  private final String obfuscatedName;
+  private final MethodDefinition methodDefinition;
   private final RetraceClassResult classResult;
   private final List<Pair<RetraceClassResult.Element, List<MappedRange>>> mappedRanges;
   private final RetraceApi retracer;
@@ -29,15 +27,14 @@
   RetraceMethodResult(
       RetraceClassResult classResult,
       List<Pair<RetraceClassResult.Element, List<MappedRange>>> mappedRanges,
-      String obfuscatedName,
+      MethodDefinition methodDefinition,
       RetraceApi retracer) {
     this.classResult = classResult;
     this.mappedRanges = mappedRanges;
-    this.obfuscatedName = obfuscatedName;
+    this.methodDefinition = methodDefinition;
     this.retracer = retracer;
     assert classResult != null;
-    // TODO(mkroghj): Enable this when we have frame results.
-    // assert !mappedRanges.isEmpty();
+    assert !mappedRanges.isEmpty();
   }
 
   @Override
@@ -60,7 +57,7 @@
     return false;
   }
 
-  public RetraceMethodResult narrowByLine(int linePosition) {
+  public RetraceFrameResult narrowByPosition(int position) {
     List<Pair<RetraceClassResult.Element, List<MappedRange>>> narrowedRanges = new ArrayList<>();
     List<Pair<RetraceClassResult.Element, List<MappedRange>>> noMappingRanges = new ArrayList<>();
     for (Pair<RetraceClassResult.Element, List<MappedRange>> mappedRange : mappedRanges) {
@@ -69,7 +66,7 @@
         continue;
       }
       List<MappedRange> ranges =
-          new MappedRangesOfName(mappedRange.getSecond()).allRangesForLine(linePosition, false);
+          new MappedRangesOfName(mappedRange.getSecond()).allRangesForLine(position, false);
       boolean hasAddedRanges = false;
       if (!ranges.isEmpty()) {
         narrowedRanges.add(new Pair<>(mappedRange.getFirst(), ranges));
@@ -87,10 +84,11 @@
         narrowedRanges.add(new Pair<>(mappedRange.getFirst(), null));
       }
     }
-    return new RetraceMethodResult(
+    return new RetraceFrameResult(
         classResult,
         narrowedRanges.isEmpty() ? noMappingRanges : narrowedRanges,
-        obfuscatedName,
+        methodDefinition,
+        position,
         retracer);
   }
 
@@ -101,45 +99,23 @@
             mappedRangePair -> {
               RetraceClassResult.Element classElement = mappedRangePair.getFirst();
               List<MappedRange> mappedRanges = mappedRangePair.getSecond();
-              if (mappedRanges == null) {
+              if (mappedRanges == null || mappedRanges.isEmpty()) {
                 return Stream.of(
                     new Element(
                         this,
                         classElement,
-                        RetracedMethod.createUnknown(
-                            classElement.getRetracedClass(), obfuscatedName),
-                        null));
+                        RetracedMethod.create(
+                            methodDefinition.substituteHolder(
+                                classElement.getRetracedClass().getClassReference()))));
               }
               return mappedRanges.stream()
                   .map(
                       mappedRange -> {
-                        MethodSignature signature = mappedRange.signature;
-                        RetracedClass holder =
-                            mappedRange.signature.isQualified()
-                                ? RetracedClass.create(
-                                    Reference.classFromDescriptor(
-                                        DescriptorUtils.javaTypeToDescriptor(
-                                            mappedRange.signature.toHolderFromQualified())))
-                                : classElement.getRetracedClass();
-                        List<TypeReference> formalTypes =
-                            new ArrayList<>(signature.parameters.length);
-                        for (String parameter : signature.parameters) {
-                          formalTypes.add(Reference.typeFromTypeName(parameter));
-                        }
-                        TypeReference returnType =
-                            Reference.returnTypeFromDescriptor(
-                                DescriptorUtils.javaTypeToDescriptor(signature.type));
-                        RetracedMethod retracedMethod =
-                            RetracedMethod.create(
-                                holder,
-                                Reference.method(
-                                    holder.getClassReference(),
-                                    signature.isQualified()
-                                        ? signature.toUnqualifiedName()
-                                        : signature.name,
-                                    formalTypes,
-                                    returnType));
-                        return new Element(this, classElement, retracedMethod, mappedRange);
+                        MethodReference methodReference =
+                            RetraceUtils.methodReferenceFromMappedRange(
+                                mappedRange, classElement.getRetracedClass().getClassReference());
+                        return new Element(
+                            this, classElement, RetracedMethod.create(methodReference));
                       });
             });
   }
@@ -150,41 +126,28 @@
     return this;
   }
 
-  public static class Element {
+  public static class Element implements RetraceClassMemberElement<RetracedMethod> {
 
     private final RetracedMethod methodReference;
     private final RetraceMethodResult retraceMethodResult;
     private final RetraceClassResult.Element classElement;
-    private final MappedRange mappedRange;
 
     private Element(
         RetraceMethodResult retraceMethodResult,
         RetraceClassResult.Element classElement,
-        RetracedMethod methodReference,
-        MappedRange mappedRange) {
+        RetracedMethod methodReference) {
       this.classElement = classElement;
       this.retraceMethodResult = retraceMethodResult;
       this.methodReference = methodReference;
-      this.mappedRange = mappedRange;
     }
 
+    @Override
     public boolean isUnknown() {
       return methodReference.isUnknown();
     }
 
-    public boolean hasNoLineNumberRange() {
-      return mappedRange == null || mappedRange.minifiedRange == null;
-    }
-
-    public boolean containsMinifiedLineNumber(int linePosition) {
-      if (hasNoLineNumberRange()) {
-        return false;
-      }
-      return mappedRange.minifiedRange.from <= linePosition
-          && linePosition <= mappedRange.minifiedRange.to;
-    }
-
-    public RetracedMethod getMethod() {
+    @Override
+    public RetracedMethod getMember() {
       return methodReference;
     }
 
@@ -192,22 +155,18 @@
       return retraceMethodResult;
     }
 
+    @Override
     public RetraceClassResult.Element getClassElement() {
       return classElement;
     }
 
-    public int getOriginalLineNumber(int linePosition) {
-      return mappedRange != null ? mappedRange.getOriginalLineNumber(linePosition) : linePosition;
+    @Override
+    public void visitFrames(BiConsumer<RetracedMethod, Integer> consumer) {
+      consumer.accept(methodReference, 0);
     }
 
-    public int getFirstLineNumberOfOriginalRange() {
-      if (hasNoLineNumberRange()) {
-        return 0;
-      }
-      return mappedRange.getFirstLineNumberOfOriginalRange();
-    }
-
-    public RetraceSourceFileResult retraceSourceFile(String sourceFile) {
+    @Override
+    public RetraceSourceFileResult retraceSourceFile(RetracedClassMember frame, String sourceFile) {
       return RetraceUtils.getSourceFile(
           classElement, methodReference.getHolderClass(), sourceFile, retraceMethodResult.retracer);
     }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
index bccb9dd..910db1a 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.retrace;
 
+import static com.android.tools.r8.retrace.RetraceUtils.methodDescriptionFromRetraceMethod;
+
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.references.ClassReference;
@@ -14,7 +16,6 @@
 import com.android.tools.r8.retrace.RetracedField.KnownRetracedField;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.Lists;
 import java.util.ArrayList;
@@ -133,7 +134,7 @@
               case CLASS:
                 return line.getClassContext().getRetracedClass().getTypeName();
               case METHOD:
-                return line.getMethodContext().getMethod().getMethodName();
+                return line.getMethodContext().getMember().getMethodName();
               case SOURCE:
                 return line.getSource();
               case LINE:
@@ -241,9 +242,10 @@
 
   static class RetraceStringContext {
     private final Element classContext;
-    private final RetracedClass qualifiedContext;
+    private final RetraceClassMemberElement<? extends RetracedMethod> methodContext;
+    private final RetracedClass qualifiedClassContext;
+    private final RetracedMethod qualifiedMethodContext;
     private final String methodName;
-    private final RetraceMethodResult.Element methodContext;
     private final int minifiedLineNumber;
     private final int originalLineNumber;
     private final String source;
@@ -251,17 +253,19 @@
 
     private RetraceStringContext(
         Element classContext,
-        RetracedClass qualifiedContext,
+        RetraceClassMemberElement<? extends RetracedMethod> methodContext,
+        RetracedClass qualifiedClassContext,
+        RetracedMethod qualifiedMethodContext,
         String methodName,
-        RetraceMethodResult.Element methodContext,
         int minifiedLineNumber,
         int originalLineNumber,
         String source,
         boolean isAmbiguous) {
       this.classContext = classContext;
-      this.qualifiedContext = qualifiedContext;
-      this.methodName = methodName;
       this.methodContext = methodContext;
+      this.qualifiedClassContext = qualifiedClassContext;
+      this.qualifiedMethodContext = qualifiedMethodContext;
+      this.methodName = methodName;
       this.minifiedLineNumber = minifiedLineNumber;
       this.originalLineNumber = originalLineNumber;
       this.source = source;
@@ -269,16 +273,18 @@
     }
 
     private static RetraceStringContext empty() {
-      return new RetraceStringContext(null, null, null, null, NO_MATCH, NO_MATCH, null, false);
+      return new RetraceStringContext(
+          null, null, null, null, null, NO_MATCH, NO_MATCH, null, false);
     }
 
     private RetraceStringContext withClassContext(
         Element classContext, RetracedClass qualifiedContext) {
       return new RetraceStringContext(
           classContext,
-          qualifiedContext,
-          methodName,
           methodContext,
+          qualifiedContext,
+          qualifiedMethodContext,
+          methodName,
           minifiedLineNumber,
           originalLineNumber,
           source,
@@ -288,9 +294,10 @@
     private RetraceStringContext withMethodName(String methodName) {
       return new RetraceStringContext(
           classContext,
-          qualifiedContext,
-          methodName,
           methodContext,
+          qualifiedClassContext,
+          qualifiedMethodContext,
+          methodName,
           minifiedLineNumber,
           originalLineNumber,
           source,
@@ -298,26 +305,39 @@
     }
 
     private RetraceStringContext withMethodContext(
-        RetraceMethodResult.Element methodContext,
-        RetracedClass qualifiedContext,
-        boolean isAmbiguous) {
+        RetraceClassMemberElement<? extends RetracedMethod> methodContext, boolean isAmbiguous) {
       return new RetraceStringContext(
           classContext,
-          qualifiedContext,
-          methodName,
           methodContext,
+          qualifiedClassContext,
+          qualifiedMethodContext,
+          methodName,
           minifiedLineNumber,
           originalLineNumber,
           source,
           isAmbiguous);
     }
 
-    private RetraceStringContext withQualifiedContext(RetracedClass qualifiedContext) {
+    private RetraceStringContext withQualifiedClassContext(RetracedClass qualifiedContext) {
       return new RetraceStringContext(
           classContext,
+          methodContext,
+          qualifiedContext,
+          qualifiedMethodContext,
+          methodName,
+          minifiedLineNumber,
+          originalLineNumber,
+          source,
+          isAmbiguous);
+    }
+
+    private RetraceStringContext withQualifiedMethodContext(RetracedMethod qualifiedContext) {
+      return new RetraceStringContext(
+          classContext,
+          methodContext,
+          qualifiedContext.getHolderClass(),
           qualifiedContext,
           methodName,
-          methodContext,
           minifiedLineNumber,
           originalLineNumber,
           source,
@@ -327,9 +347,10 @@
     public RetraceStringContext withSource(String source) {
       return new RetraceStringContext(
           classContext,
-          qualifiedContext,
-          methodName,
           methodContext,
+          qualifiedClassContext,
+          qualifiedMethodContext,
+          methodName,
           minifiedLineNumber,
           originalLineNumber,
           source,
@@ -339,9 +360,10 @@
     public RetraceStringContext withLineNumbers(int minifiedLineNumber, int originalLineNumber) {
       return new RetraceStringContext(
           classContext,
-          qualifiedContext,
-          methodName,
           methodContext,
+          qualifiedClassContext,
+          qualifiedMethodContext,
+          methodName,
           minifiedLineNumber,
           originalLineNumber,
           source,
@@ -385,7 +407,7 @@
       return context.classContext;
     }
 
-    private RetraceMethodResult.Element getMethodContext() {
+    private RetraceClassMemberElement<? extends RetracedMethod> getMethodContext() {
       return context.methodContext;
     }
 
@@ -657,7 +679,7 @@
             if (classNameGroupHandler != null) {
               for (RetraceString string : strings) {
                 classNameGroupHandler.commitClassName(
-                    original, string, string.context.qualifiedContext, matcher);
+                    original, string, string.context.qualifiedClassContext, matcher);
               }
             }
             return strings;
@@ -669,20 +691,23 @@
                 retraceString,
                 methodName,
                 (element, newContext) -> {
-                  final RetraceString newRetraceString = retraceString.duplicate(newContext);
-                  if (classNameGroupHandler != null) {
-                    classNameGroupHandler.commitClassName(
-                        original, newRetraceString, element.getMethod().getHolderClass(), matcher);
-                  }
-                  retracedStrings.add(
-                      newRetraceString.appendRetracedString(
-                          original,
-                          printVerbose
-                              ? RetraceUtils.methodDescriptionFromMethodReference(
-                                  element.getMethod(), false, true)
-                              : element.getMethod().getMethodName(),
-                          startOfGroup,
-                          matcher.end(captureGroup)));
+                  element.visitFrames(
+                      (method, ignoredPosition) -> {
+                        RetraceString newRetraceString =
+                            retraceString.duplicate(newContext.withQualifiedMethodContext(method));
+                        if (classNameGroupHandler != null) {
+                          classNameGroupHandler.commitClassName(
+                              original, newRetraceString, method.getHolderClass(), matcher);
+                        }
+                        retracedStrings.add(
+                            newRetraceString.appendRetracedString(
+                                original,
+                                printVerbose
+                                    ? methodDescriptionFromRetraceMethod(method, false, true)
+                                    : method.getMethodName(),
+                                startOfGroup,
+                                matcher.end(captureGroup)));
+                      });
                 });
           }
           return retracedStrings;
@@ -703,24 +728,25 @@
     private static void retraceMethodForString(
         RetraceString retraceString,
         String methodName,
-        BiConsumer<RetraceMethodResult.Element, RetraceStringContext> process) {
+        BiConsumer<RetraceClassMemberElement<? extends RetracedMethod>, RetraceStringContext>
+            process) {
       if (retraceString.context.classContext == null) {
         return;
       }
       RetraceMethodResult retraceMethodResult =
           retraceString.getClassContext().lookupMethod(methodName);
+      Result<? extends RetraceClassMemberElement<RetracedMethod>, ?> retraceResult;
       if (retraceString.context.minifiedLineNumber > NO_MATCH) {
-        retraceMethodResult =
-            retraceMethodResult.narrowByLine(retraceString.context.minifiedLineNumber);
+        retraceResult =
+            retraceMethodResult.narrowByPosition(retraceString.context.minifiedLineNumber);
+      } else {
+        retraceResult = retraceMethodResult;
       }
-      retraceMethodResult.forEach(
+      retraceResult.forEach(
           element ->
               process.accept(
                   element,
-                  retraceString.context.withMethodContext(
-                      element,
-                      element.getMethod().getHolderClass(),
-                      element.getRetraceMethodResult().isAmbiguous())));
+                  retraceString.context.withMethodContext(element, retraceResult.isAmbiguous())));
     }
   }
 
@@ -756,7 +782,7 @@
             if (classNameGroupHandler != null) {
               for (RetraceString string : strings) {
                 classNameGroupHandler.commitClassName(
-                    original, string, string.context.qualifiedContext, matcher);
+                    original, string, string.context.qualifiedClassContext, matcher);
               }
             }
             return strings;
@@ -781,7 +807,8 @@
                       retraceString
                           .updateContext(
                               context ->
-                                  context.withQualifiedContext(element.getField().getHolderClass()))
+                                  context.withQualifiedClassContext(
+                                      element.getField().getHolderClass()))
                           .appendRetracedString(
                               original,
                               getFieldString(element.getField()),
@@ -829,10 +856,12 @@
           }
           RetraceSourceFileResult sourceFileResult =
               retraceString.getMethodContext() != null
-                  ? retraceString.getMethodContext().retraceSourceFile(fileName)
+                  ? retraceString
+                      .getMethodContext()
+                      .retraceSourceFile(retraceString.context.qualifiedMethodContext, fileName)
                   : RetraceUtils.getSourceFile(
                       retraceString.getClassContext(),
-                      retraceString.context.qualifiedContext,
+                      retraceString.context.qualifiedClassContext,
                       fileName,
                       retracer);
           retracedStrings.add(
@@ -873,7 +902,8 @@
           int lineNumber = Integer.parseInt(lineNumberAsString);
           List<RetraceString> retracedStrings = new ArrayList<>();
           for (RetraceString retraceString : strings) {
-            RetraceMethodResult.Element methodContext = retraceString.context.methodContext;
+            RetraceClassMemberElement<? extends RetracedMethod> methodContext =
+                retraceString.context.methodContext;
             if (methodContext == null) {
               if (retraceString.context.classContext == null
                   || retraceString.context.methodName == null) {
@@ -889,34 +919,33 @@
                   (element, newContext) -> {
                     // The same method can be represented multiple times if it has multiple
                     // mappings.
-                    if (element.hasNoLineNumberRange()
-                        || !element.containsMinifiedLineNumber(lineNumber)) {
-                      diagnosticsHandler.info(
-                          new StringDiagnostic(
-                              "Pruning "
-                                  + retraceString.builder.retracedString.toString()
-                                  + " from result because method is not in range on line number "
-                                  + lineNumber));
-                    }
-                    final int originalLineNumber = element.getOriginalLineNumber(lineNumber);
-                    retracedStrings.add(
-                        retraceString
-                            .updateContext(
-                                context -> context.withLineNumbers(lineNumber, originalLineNumber))
-                            .appendRetracedString(
-                                original,
-                                originalLineNumber + "",
-                                startOfGroup,
-                                matcher.end(captureGroup)));
+                    element.visitFrames(
+                        (method, ignoredPosition) -> {
+                          int originalPosition = method.getOriginalPositionOrDefault(lineNumber);
+                          retracedStrings.add(
+                              retraceString
+                                  .duplicate(
+                                      retraceString
+                                          .context
+                                          .withQualifiedMethodContext(method)
+                                          .withLineNumbers(lineNumber, originalPosition))
+                                  .appendRetracedString(
+                                      original,
+                                      originalPosition + "",
+                                      startOfGroup,
+                                      matcher.end(captureGroup)));
+                        });
                   });
               continue;
             }
             // If the method context is unknown, do nothing.
-            if (methodContext.isUnknown() || methodContext.hasNoLineNumberRange()) {
+            if (methodContext.isUnknown()) {
               retracedStrings.add(retraceString);
               continue;
             }
-            int originalLineNumber = methodContext.getOriginalLineNumber(lineNumber);
+            int originalLineNumber =
+                retraceString.context.qualifiedMethodContext.getOriginalPositionOrDefault(
+                    lineNumber);
             retracedStrings.add(
                 retraceString
                     .updateContext(
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
index e2e3616..024f761 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.retrace;
 
-import static com.android.tools.r8.retrace.RetraceUtils.methodDescriptionFromMethodReference;
+import static com.android.tools.r8.retrace.RetraceUtils.methodDescriptionFromRetraceMethod;
 import static com.google.common.base.Predicates.not;
 
 import com.android.tools.r8.DiagnosticsHandler;
@@ -418,28 +418,31 @@
         RetraceApi retracer, boolean verbose, List<StackTraceLine> lines, String classLoaderName) {
       ClassReference classReference = Reference.classFromTypeName(clazz);
       RetraceClassResult classResult = retracer.retrace(classReference);
-      RetraceMethodResult retraceResult = classResult.lookupMethod(method);
+      RetraceMethodResult retraceMethodResult = classResult.lookupMethod(method);
+      Result<? extends RetraceClassMemberElement<RetracedMethod>, ?> retraceResult;
       if (linePosition != NO_POSITION && linePosition != INVALID_POSITION) {
-        retraceResult = retraceResult.narrowByLine(linePosition);
+        retraceResult = retraceMethodResult.narrowByPosition(linePosition);
+      } else {
+        retraceResult = retraceMethodResult;
       }
       retraceResult.forEach(
-          methodElement -> {
-            RetracedMethod methodReference = methodElement.getMethod();
-            lines.add(
-                new AtLine(
-                    startingWhitespace,
-                    at,
-                    classLoaderName,
-                    moduleName,
-                    methodReference.getHolderClass().getTypeName(),
-                    methodReference.getMethodName(),
-                    methodDescriptionFromMethodReference(methodReference, true, verbose),
-                    methodElement.retraceSourceFile(fileName).getFilename(),
-                    hasLinePosition()
-                        ? methodElement.getOriginalLineNumber(linePosition)
-                        : linePosition,
-                    methodElement.getRetraceMethodResult().isAmbiguous()));
-          });
+          methodElement ->
+              methodElement.visitFrames(
+                  (methodReference, ignoredPosition) ->
+                      lines.add(
+                          new AtLine(
+                              startingWhitespace,
+                              at,
+                              classLoaderName,
+                              moduleName,
+                              methodReference.getHolderClass().getTypeName(),
+                              methodReference.getMethodName(),
+                              methodDescriptionFromRetraceMethod(methodReference, true, verbose),
+                              methodElement
+                                  .retraceSourceFile(methodReference, fileName)
+                                  .getFilename(),
+                              methodReference.getOriginalPositionOrDefault(linePosition),
+                              retraceResult.isAmbiguous()))));
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
index 335059e..e402a80 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
@@ -4,11 +4,19 @@
 
 package com.android.tools.r8.retrace;
 
+import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+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.retrace.RetracedMethod.KnownRetracedMethod;
 import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.Sets;
 import com.google.common.io.Files;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 
 public class RetraceUtils {
@@ -16,7 +24,7 @@
   private static final Set<String> UNKNOWN_SOURCEFILE_NAMES =
       Sets.newHashSet("", "SourceFile", "Unknown", "Unknown Source", "PG");
 
-  public static String methodDescriptionFromMethodReference(
+  public static String methodDescriptionFromRetraceMethod(
       RetracedMethod methodReference, boolean appendHolder, boolean verbose) {
     StringBuilder sb = new StringBuilder();
     if (appendHolder) {
@@ -115,4 +123,25 @@
     String newFileName = getClassSimpleName(retracedClassName);
     return newFileName + "." + extension;
   }
+
+  static MethodReference methodReferenceFromMappedRange(
+      MappedRange mappedRange, ClassReference classReference) {
+    MethodSignature signature = mappedRange.signature;
+    ClassReference holder =
+        signature.isQualified()
+            ? Reference.classFromDescriptor(
+                DescriptorUtils.javaTypeToDescriptor(signature.toHolderFromQualified()))
+            : classReference;
+    List<TypeReference> formalTypes = new ArrayList<>(signature.parameters.length);
+    for (String parameter : signature.parameters) {
+      formalTypes.add(Reference.typeFromTypeName(parameter));
+    }
+    TypeReference returnType =
+        Reference.returnTypeFromDescriptor(DescriptorUtils.javaTypeToDescriptor(signature.type));
+    return Reference.method(
+        holder,
+        signature.isQualified() ? signature.toUnqualifiedName() : signature.name,
+        formalTypes,
+        returnType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedClassMember.java b/src/main/java/com/android/tools/r8/retrace/RetracedClassMember.java
new file mode 100644
index 0000000..e05be47
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedClassMember.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2020, 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.retrace;
+
+public interface RetracedClassMember {
+
+  RetracedClass getHolderClass();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedField.java b/src/main/java/com/android/tools/r8/retrace/RetracedField.java
index b809cab..132d0c7 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetracedField.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedField.java
@@ -10,7 +10,7 @@
 import java.util.Objects;
 
 @Keep
-public abstract class RetracedField {
+public abstract class RetracedField implements RetracedClassMember {
 
   private RetracedField() {}
 
@@ -26,17 +26,13 @@
     return null;
   }
 
-  public abstract RetracedClass getHolderClass();
-
   public abstract String getFieldName();
 
   public static final class KnownRetracedField extends RetracedField {
 
-    private final RetracedClass classReference;
     private final FieldReference fieldReference;
 
-    private KnownRetracedField(RetracedClass classReference, FieldReference fieldReference) {
-      this.classReference = classReference;
+    private KnownRetracedField(FieldReference fieldReference) {
       this.fieldReference = fieldReference;
     }
 
@@ -52,7 +48,7 @@
 
     @Override
     public RetracedClass getHolderClass() {
-      return classReference;
+      return RetracedClass.create(fieldReference.getHolderClass());
     }
 
     @Override
@@ -77,43 +73,56 @@
         return false;
       }
       KnownRetracedField that = (KnownRetracedField) o;
-      assert !fieldReference.equals(that.fieldReference)
-          || classReference.equals(that.classReference);
       return fieldReference.equals(that.fieldReference);
     }
 
     @Override
     public int hashCode() {
-      return Objects.hash(classReference, fieldReference);
+      return Objects.hash(fieldReference);
     }
   }
 
   public static final class UnknownRetracedField extends RetracedField {
 
-    private final RetracedClass classReference;
-    private final String name;
+    private final FieldDefinition fieldDefinition;
 
-    private UnknownRetracedField(RetracedClass classReference, String name) {
-      this.classReference = classReference;
-      this.name = name;
+    private UnknownRetracedField(FieldDefinition fieldDefinition) {
+      this.fieldDefinition = fieldDefinition;
     }
 
     @Override
     public RetracedClass getHolderClass() {
-      return classReference;
+      return RetracedClass.create(fieldDefinition.getHolderClass());
     }
 
     @Override
     public String getFieldName() {
-      return name;
+      return fieldDefinition.getName();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      UnknownRetracedField that = (UnknownRetracedField) o;
+      return fieldDefinition.equals(that.fieldDefinition);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(fieldDefinition);
     }
   }
 
-  static RetracedField create(RetracedClass classReference, FieldReference fieldReference) {
-    return new KnownRetracedField(classReference, fieldReference);
+  static RetracedField create(FieldReference fieldReference) {
+    return new KnownRetracedField(fieldReference);
   }
 
-  static RetracedField createUnknown(RetracedClass classReference, String name) {
-    return new UnknownRetracedField(classReference, name);
+  static RetracedField create(FieldDefinition fieldDefinition) {
+    return new UnknownRetracedField(fieldDefinition);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java b/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java
index ee3b792..937ded1 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java
@@ -9,9 +9,12 @@
 import com.android.tools.r8.references.TypeReference;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 
 @Keep
-public abstract class RetracedMethod {
+public abstract class RetracedMethod implements RetracedClassMember {
+
+  private static final int NO_POSITION = -1;
 
   private RetracedMethod() {}
 
@@ -27,19 +30,21 @@
     return null;
   }
 
-  public abstract RetracedClass getHolderClass();
-
   public abstract String getMethodName();
 
+  public abstract boolean hasPosition();
+
+  public abstract int getOriginalPositionOrDefault(int defaultPosition);
+
   public static final class KnownRetracedMethod extends RetracedMethod {
 
     private final MethodReference methodReference;
-    private final RetracedClass classReference;
+    private final int position;
 
-    private KnownRetracedMethod(RetracedClass classReference, MethodReference methodReference) {
+    private KnownRetracedMethod(MethodReference methodReference, int position) {
       assert methodReference != null;
-      this.classReference = classReference;
       this.methodReference = methodReference;
+      this.position = position;
     }
 
     @Override
@@ -58,7 +63,7 @@
 
     @Override
     public RetracedClass getHolderClass() {
-      return classReference;
+      return RetracedClass.create(methodReference.getHolderClass());
     }
 
     @Override
@@ -66,6 +71,16 @@
       return methodReference.getMethodName();
     }
 
+    @Override
+    public boolean hasPosition() {
+      return position != NO_POSITION;
+    }
+
+    @Override
+    public int getOriginalPositionOrDefault(int defaultPosition) {
+      return hasPosition() ? position : defaultPosition;
+    }
+
     public TypeReference getReturnType() {
       assert !isVoid();
       return methodReference.getReturnType();
@@ -75,14 +90,10 @@
       return methodReference.getFormalTypes();
     }
 
-    public MethodReference getClassReference() {
+    public MethodReference getMethodReference() {
       return methodReference;
     }
 
-    public boolean equalsMethodReference(MethodReference reference) {
-      return methodReference.equals(reference);
-    }
-
     @Override
     public boolean equals(Object o) {
       if (this == o) {
@@ -92,43 +103,70 @@
         return false;
       }
       KnownRetracedMethod that = (KnownRetracedMethod) o;
-      assert !methodReference.equals(that.methodReference)
-          || classReference.equals(that.classReference);
-      return methodReference.equals(that.methodReference);
+      return position == that.position && methodReference.equals(that.methodReference);
     }
 
     @Override
     public int hashCode() {
-      return Objects.hash(methodReference, classReference);
+      return Objects.hash(methodReference, position);
     }
   }
 
   public static final class UnknownRetracedMethod extends RetracedMethod {
 
-    private final RetracedClass classReference;
-    private final String name;
+    private final MethodDefinition methodDefinition;
+    private final int position;
 
-    private UnknownRetracedMethod(RetracedClass classReference, String name) {
-      this.classReference = classReference;
-      this.name = name;
+    private UnknownRetracedMethod(MethodDefinition methodDefinition, int position) {
+      this.methodDefinition = methodDefinition;
+      this.position = position;
     }
 
     @Override
     public RetracedClass getHolderClass() {
-      return classReference;
+      return RetracedClass.create(methodDefinition.getHolderClass());
     }
 
     @Override
     public String getMethodName() {
-      return name;
+      return methodDefinition.getName();
+    }
+
+    @Override
+    public boolean hasPosition() {
+      return position != NO_POSITION;
+    }
+
+    @Override
+    public int getOriginalPositionOrDefault(int defaultPosition) {
+      return hasPosition() ? position : defaultPosition;
+    }
+
+    public Optional<MethodReference> getMethodReference() {
+      if (!methodDefinition.isFullMethodDefinition()) {
+        return Optional.empty();
+      }
+      return Optional.of(methodDefinition.asFullMethodDefinition().getMethodReference());
     }
   }
 
-  static RetracedMethod create(RetracedClass classReference, MethodReference methodReference) {
-    return new KnownRetracedMethod(classReference, methodReference);
+  static RetracedMethod create(MethodDefinition methodDefinition) {
+    return create(methodDefinition, NO_POSITION);
   }
 
-  static RetracedMethod createUnknown(RetracedClass classReference, String name) {
-    return new UnknownRetracedMethod(classReference, name);
+  static RetracedMethod create(MethodDefinition methodDefinition, int position) {
+    if (methodDefinition.isFullMethodDefinition()) {
+      return new KnownRetracedMethod(
+          methodDefinition.asFullMethodDefinition().getMethodReference(), position);
+    }
+    return new UnknownRetracedMethod(methodDefinition, position);
+  }
+
+  static RetracedMethod create(MethodReference methodReference) {
+    return create(methodReference, NO_POSITION);
+  }
+
+  static RetracedMethod create(MethodReference methodReference, int position) {
+    return new KnownRetracedMethod(methodReference, position);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/Retracer.java b/src/main/java/com/android/tools/r8/retrace/Retracer.java
index d38e203..279253c 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retracer.java
@@ -50,6 +50,13 @@
   }
 
   @Override
+  public RetraceFrameResult retrace(MethodReference methodReference, int position) {
+    return retrace(methodReference.getHolderClass())
+        .lookupMethod(methodReference.getMethodName())
+        .narrowByPosition(position);
+  }
+
+  @Override
   public RetraceClassResult retrace(ClassReference classReference) {
     return RetraceClassResult.create(
         classReference, classNameMapper.getClassNaming(classReference.getTypeName()), this);
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
index 11dff7b..18913cc 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
@@ -22,7 +22,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.retrace.RetraceFrameResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -103,7 +103,7 @@
                           MINIFIED_LINE_POSITION,
                           20,
                           FILENAME_INLINE));
-              RetraceMethodResult retraceResult =
+              RetraceFrameResult retraceResult =
                   throwingSubject
                       .streamInstructions()
                       .filter(InstructionSubject::isThrow)
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 ae533a4..8c2ef82 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
@@ -18,7 +18,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.retrace.RetraceFrameResult;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -93,7 +93,7 @@
                           INLINED_DEX_PC,
                           23,
                           FILENAME_MAIN));
-              RetraceMethodResult retraceResult =
+              RetraceFrameResult retraceResult =
                   mainSubject
                       .streamInstructions()
                       .filter(InstructionSubject::isThrow)
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
index 1fa811a..0beee59 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
@@ -120,7 +120,7 @@
       MethodSubject mainSubject,
       LinePosition inlineStack) {
     assertThat(mainSubject, isPresent());
-    RetraceMethodResult retraceResult =
+    RetraceFrameResult retraceResult =
         mainSubject
             .streamInstructions()
             .filter(InstructionSubject::isThrow)
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index 05b028a..3b0538d 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -225,7 +225,7 @@
       MethodSubject mainSubject,
       LinePosition inlineStack) {
     assertThat(mainSubject, isPresent());
-    RetraceMethodResult retraceResult =
+    RetraceFrameResult retraceResult =
         mainSubject
             .streamInstructions()
             .filter(InstructionSubject::isThrow)
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 513de02..6f6bbf8 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.retrace.RetraceApi;
+import com.android.tools.r8.retrace.RetraceFrameResult;
 import com.android.tools.r8.retrace.RetraceMethodResult;
 
 public interface InstructionSubject {
@@ -137,11 +138,11 @@
     return retracer.retrace(methodSubject.asFoundMethodSubject().asMethodReference());
   }
 
-  default RetraceMethodResult retraceLinePosition(RetraceApi retracer) {
-    return retrace(retracer).narrowByLine(getLineNumber());
+  default RetraceFrameResult retraceLinePosition(RetraceApi retracer) {
+    return retrace(retracer).narrowByPosition(getLineNumber());
   }
 
-  default RetraceMethodResult retracePcPosition(RetraceApi retracer, MethodSubject methodSubject) {
-    return retrace(retracer).narrowByLine(getOffset(methodSubject).offset);
+  default RetraceFrameResult retracePcPosition(RetraceApi retracer, MethodSubject methodSubject) {
+    return retrace(retracer).narrowByPosition(getOffset(methodSubject).offset);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 017a637..6f11640 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -6,6 +6,7 @@
 
 import static org.hamcrest.CoreMatchers.not;
 
+import com.android.tools.r8.Collectors;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.DexClass;
@@ -14,14 +15,11 @@
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
 import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.retrace.RetraceMethodResult;
-import com.android.tools.r8.retrace.RetraceMethodResult.Element;
-import com.android.tools.r8.retrace.RetracedMethod;
+import com.android.tools.r8.retrace.RetraceFrameResult;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.Visibility;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
-import java.util.stream.Collectors;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
@@ -483,11 +481,12 @@
     };
   }
 
-  public static Matcher<RetraceMethodResult> isInlineFrame() {
-    return new TypeSafeMatcher<RetraceMethodResult>() {
+  public static Matcher<RetraceFrameResult> isInlineFrame() {
+    return new TypeSafeMatcher<RetraceFrameResult>() {
       @Override
-      protected boolean matchesSafely(RetraceMethodResult item) {
-        return !item.isAmbiguous() && item.stream().count() > 1;
+      protected boolean matchesSafely(RetraceFrameResult item) {
+        return !item.isAmbiguous()
+            && item.stream().mapToLong(method -> method.getOuterFrames().size()).sum() > 0;
       }
 
       @Override
@@ -497,28 +496,29 @@
     };
   }
 
-  public static Matcher<RetraceMethodResult> isInlineStack(LinePosition startPosition) {
-    return new TypeSafeMatcher<RetraceMethodResult>() {
+  public static Matcher<RetraceFrameResult> isInlineStack(LinePosition startPosition) {
+    return new TypeSafeMatcher<RetraceFrameResult>() {
       @Override
-      protected boolean matchesSafely(RetraceMethodResult item) {
+      protected boolean matchesSafely(RetraceFrameResult item) {
+        RetraceFrameResult.Element single = item.stream().collect(Collectors.toSingle());
         Box<LinePosition> currentPosition = new Box<>(startPosition);
         Box<Boolean> returnValue = new Box<>();
-        item.forEach(
-            element -> {
+        single.visitFrames(
+            (method, __) -> {
               boolean sameMethod;
               LinePosition currentInline = currentPosition.get();
               if (currentInline == null) {
                 returnValue.set(false);
                 return;
               }
+              if (method.isUnknown()) {
+                returnValue.set(false);
+                return;
+              }
               sameMethod =
-                  element.getMethod().isKnown()
-                      && element
-                          .getMethod()
-                          .asKnown()
-                          .equalsMethodReference(currentInline.methodReference);
+                  method.asKnown().getMethodReference().equals(currentInline.methodReference);
               boolean samePosition =
-                  element.getOriginalLineNumber(currentInline.minifiedPosition)
+                  method.getOriginalPositionOrDefault(currentInline.minifiedPosition)
                       == currentInline.originalPosition;
               if (!returnValue.isSet() || returnValue.get()) {
                 returnValue.set(sameMethod & samePosition);
@@ -535,28 +535,24 @@
     };
   }
 
-  public static Matcher<RetraceMethodResult> isTopOfStackTrace(
+  public static Matcher<RetraceFrameResult> isTopOfStackTrace(
       StackTrace stackTrace, List<Integer> minifiedPositions) {
-    return new TypeSafeMatcher<RetraceMethodResult>() {
+    return new TypeSafeMatcher<RetraceFrameResult>() {
       @Override
-      protected boolean matchesSafely(RetraceMethodResult item) {
-        List<Element> retraceElements = item.stream().collect(Collectors.toList());
-        if (retraceElements.size() > stackTrace.size()
-            || retraceElements.size() != minifiedPositions.size()) {
-          return false;
-        }
-        for (int i = 0; i < retraceElements.size(); i++) {
-          Element retraceElement = retraceElements.get(i);
-          StackTraceLine stackTraceLine = stackTrace.get(i);
-          RetracedMethod methodReference = retraceElement.getMethod();
-          if (!stackTraceLine.methodName.equals(methodReference.getMethodName())
-              || !stackTraceLine.className.equals(methodReference.getHolderClass().getTypeName())
-              || stackTraceLine.lineNumber
-                  != retraceElement.getOriginalLineNumber(minifiedPositions.get(i))) {
-            return false;
-          }
-        }
-        return true;
+      protected boolean matchesSafely(RetraceFrameResult item) {
+        RetraceFrameResult.Element single = item.stream().collect(Collectors.toSingle());
+        Box<Boolean> matches = new Box<>(true);
+        single.visitFrames(
+            (method, index) -> {
+              StackTraceLine stackTraceLine = stackTrace.get(index);
+              if (!stackTraceLine.methodName.equals(method.getMethodName())
+                  || !stackTraceLine.className.equals(method.getHolderClass().getTypeName())
+                  || stackTraceLine.lineNumber
+                      != method.getOriginalPositionOrDefault(minifiedPositions.get(index))) {
+                matches.set(false);
+              }
+            });
+        return matches.get();
       }
 
       @Override