Reorder parameter annotations during proto normalization

Bug: 195112263
Change-Id: I7c8d075a3694f6ecb572300f9a9ce0035e2f1f5e
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index c326dc0..c33d18d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -44,6 +44,7 @@
 import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
@@ -85,7 +86,6 @@
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
-import java.util.function.IntPredicate;
 import org.objectweb.asm.Opcodes;
 
 public class DexEncodedMethod extends DexEncodedMember<DexEncodedMethod, DexMethod>
@@ -1470,21 +1470,31 @@
       return this;
     }
 
-    public Builder removeParameterAnnotations(IntPredicate predicate) {
+    public Builder rewriteParameterAnnotations(
+        DexEncodedMethod method, ArgumentInfoCollection argumentInfoCollection) {
       if (parameterAnnotations.isEmpty()) {
         // Nothing to do.
         return this;
       }
+      if (!argumentInfoCollection.hasArgumentPermutation()
+          && !argumentInfoCollection.hasRemovedArguments()) {
+        // Nothing to do.
+        return this;
+      }
 
-      List<DexAnnotationSet> newParameterAnnotations = new ArrayList<>();
+      List<DexAnnotationSet> newParameterAnnotations =
+          new ArrayList<>(parameterAnnotations.countNonMissing());
       int newNumberOfMissingParameterAnnotations = 0;
 
-      for (int oldIndex = 0; oldIndex < parameterAnnotations.size(); oldIndex++) {
-        if (!predicate.test(oldIndex)) {
-          if (parameterAnnotations.isMissing(oldIndex)) {
+      for (int parameterIndex = 0;
+          parameterIndex < method.getParameters().size();
+          parameterIndex++) {
+        int argumentIndex = parameterIndex + method.getFirstNonReceiverArgumentIndex();
+        if (!argumentInfoCollection.isArgumentRemoved(argumentIndex)) {
+          if (parameterAnnotations.isMissing(parameterIndex)) {
             newNumberOfMissingParameterAnnotations++;
           } else {
-            newParameterAnnotations.add(parameterAnnotations.get(oldIndex));
+            newParameterAnnotations.add(parameterAnnotations.get(parameterIndex));
           }
         }
       }
@@ -1493,6 +1503,23 @@
         return setParameterAnnotations(ParameterAnnotationsList.empty());
       }
 
+      if (argumentInfoCollection.hasArgumentPermutation()) {
+        List<DexAnnotationSet> newPermutedParameterAnnotations =
+            Arrays.asList(new DexAnnotationSet[method.getParameters().size()]);
+        for (int parameterIndex = newNumberOfMissingParameterAnnotations;
+            parameterIndex < method.getParameters().size();
+            parameterIndex++) {
+          int argumentIndex = parameterIndex + method.getFirstNonReceiverArgumentIndex();
+          int newArgumentIndex = argumentInfoCollection.getNewArgumentIndex(argumentIndex, 0);
+          int newParameterIndex = newArgumentIndex - method.getFirstNonReceiverArgumentIndex();
+          newPermutedParameterAnnotations.set(
+              newParameterIndex,
+              newParameterAnnotations.get(parameterIndex - newNumberOfMissingParameterAnnotations));
+        }
+        newParameterAnnotations = newPermutedParameterAnnotations;
+        newNumberOfMissingParameterAnnotations = 0;
+      }
+
       return setParameterAnnotations(
           ParameterAnnotationsList.create(
               newParameterAnnotations.toArray(DexAnnotationSet.EMPTY_ARRAY),
diff --git a/src/main/java/com/android/tools/r8/graph/proto/ArgumentInfoCollection.java b/src/main/java/com/android/tools/r8/graph/proto/ArgumentInfoCollection.java
index 0a9fcbf..cbfa83d 100644
--- a/src/main/java/com/android/tools/r8/graph/proto/ArgumentInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/proto/ArgumentInfoCollection.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoFixer;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.IntObjConsumer;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry;
 import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
@@ -344,13 +343,6 @@
    */
   public Consumer<DexEncodedMethod.Builder> createParameterAnnotationsRemover(
       DexEncodedMethod method) {
-    if (numberOfRemovedArguments() > 0 && !method.parameterAnnotationsList.isEmpty()) {
-      return builder -> {
-        int firstArgumentIndex = method.getFirstNonReceiverArgumentIndex();
-        builder.removeParameterAnnotations(
-            oldIndex -> getArgumentInfo(oldIndex + firstArgumentIndex).isRemovedArgumentInfo());
-      };
-    }
-    return ConsumerUtils.emptyConsumer();
+    return builder -> builder.rewriteParameterAnnotations(method, this);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java
index e28c76b..b1237ed 100644
--- a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java
@@ -14,7 +14,9 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IterableUtils;
@@ -81,9 +83,22 @@
                   return method;
                 }
                 DexMethod newMethodReference = newMethodSignature.withHolder(clazz, dexItemFactory);
-                lensBuilder.recordNewMethodSignature(method, newMethodReference);
-                // TODO(b/195112263): Fixup any optimization info and parameter annotations.
-                return method.toTypeSubstitutedMethod(newMethodReference);
+                RewrittenPrototypeDescription prototypeChanges =
+                    lensBuilder.recordNewMethodSignature(method, newMethodReference);
+                // TODO(b/195112263): Assert that the method does not have any optimization info.
+                //  If/when enabling proto normalization after the final round of tree shaking, this
+                //  should simply clear the optimization info, or replace it by a
+                //  ThrowingMethodOptimizationInfo since we should never use the optimization info
+                //  after this point.
+                return method.toTypeSubstitutedMethod(
+                    newMethodReference,
+                    builder -> {
+                      if (!prototypeChanges.isEmpty()) {
+                        builder
+                            .apply(prototypeChanges.createParameterAnnotationsRemover(method))
+                            .setGenericSignature(MethodTypeSignature.noSignature());
+                      }
+                    });
               });
     }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java
index dc91a10..bfeef49 100644
--- a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java
@@ -62,20 +62,26 @@
 
   @Override
   public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
+    if (this == applied) {
+      return originalMethod;
+    }
     return newMethodSignatures.getOrDefault(originalMethod, originalMethod);
   }
 
   @Override
   public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
       DexMethod method, GraphLens codeLens) {
+    if (this == codeLens) {
+      return RewrittenPrototypeDescription.none();
+    }
     DexMethod previousMethodSignature = getPreviousMethodSignature(method);
     RewrittenPrototypeDescription previousPrototypeChanges =
         getPrevious().lookupPrototypeChangesForMethodDefinition(previousMethodSignature);
     if (previousMethodSignature == method) {
       return previousPrototypeChanges;
     }
-    assert prototypeChanges.containsKey(method);
-    return previousPrototypeChanges.combine(prototypeChanges.get(method));
+    return previousPrototypeChanges.combine(
+        prototypeChanges.getOrDefault(method, RewrittenPrototypeDescription.none()));
   }
 
   @Override
@@ -132,11 +138,17 @@
       this.appView = appView;
     }
 
-    public Builder recordNewMethodSignature(DexEncodedMethod method, DexMethod newMethodSignature) {
+    public RewrittenPrototypeDescription recordNewMethodSignature(
+        DexEncodedMethod method, DexMethod newMethodSignature) {
       assert method.getReference() != newMethodSignature;
-      prototypeChanges.put(newMethodSignature, computePrototypeChanges(method, newMethodSignature));
       newMethodSignatures.put(method.getReference(), newMethodSignature);
-      return this;
+      if (!method.getParameters().equals(newMethodSignature.getParameters())) {
+        RewrittenPrototypeDescription prototypeChangesForMethod =
+            computePrototypeChanges(method, newMethodSignature);
+        prototypeChanges.put(newMethodSignature, prototypeChangesForMethod);
+        return prototypeChangesForMethod;
+      }
+      return RewrittenPrototypeDescription.none();
     }
 
     // TODO(b/195112263): Canonicalize the permutation maps.
diff --git a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithParameterAnnotationsTest.java b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithParameterAnnotationsTest.java
new file mode 100644
index 0000000..a9b9af9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithParameterAnnotationsTest.java
@@ -0,0 +1,153 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.optimize.proto;
+
+import static com.android.tools.r8.utils.codeinspector.AnnotationMatchers.hasParameterAnnotationTypes;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.MethodMatchers.hasParameters;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.TypeSubject;
+import com.google.common.collect.ImmutableList;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+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 ProtoNormalizationWithParameterAnnotationsTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8Compat(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(Foo.class, Bar.class)
+        .addKeepRuntimeVisibleAnnotations()
+        .addOptionsModification(
+            options -> options.testing.enableExperimentalProtoNormalization = true)
+        .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        // TODO(b/173398086): uniqueMethodWithName() does not work with proto changes.
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject mainClassSubject = inspector.clazz(Main.class);
+              assertThat(mainClassSubject, isPresent());
+
+              TypeSubject aTypeSubject = inspector.clazz(A.class).asTypeSubject();
+              TypeSubject bTypeSubject = inspector.clazz(B.class).asTypeSubject();
+              TypeSubject fooTypeSubject = inspector.clazz(Foo.class).asTypeSubject();
+              TypeSubject barTypeSubject = inspector.clazz(Bar.class).asTypeSubject();
+
+              // Main.bar() has parameter annotations [@Bar, @Foo].
+              MethodSubject barMethodSubject = mainClassSubject.uniqueMethodWithName("bar");
+              assertThat(barMethodSubject, isPresent());
+              assertThat(barMethodSubject, hasParameters(aTypeSubject, bTypeSubject));
+              assertThat(
+                  barMethodSubject,
+                  hasParameterAnnotationTypes(
+                      ImmutableList.of(barTypeSubject), ImmutableList.of(fooTypeSubject)));
+
+              // Main.baz() has parameter annotations [, @Foo].
+              MethodSubject bazMethodSubject = mainClassSubject.uniqueMethodWithName("baz");
+              assertThat(bazMethodSubject, isPresent());
+              assertThat(bazMethodSubject, hasParameters(aTypeSubject, bTypeSubject));
+              assertThat(
+                  bazMethodSubject,
+                  hasParameterAnnotationTypes(
+                      ImmutableList.of(), ImmutableList.of(fooTypeSubject)));
+
+              // Main.qux() has parameter annotations [@Foo, ].
+              MethodSubject quxMethodSubject = mainClassSubject.uniqueMethodWithName("qux");
+              assertThat(quxMethodSubject, isPresent());
+              assertThat(quxMethodSubject, hasParameters(aTypeSubject, bTypeSubject));
+              assertThat(
+                  quxMethodSubject,
+                  hasParameterAnnotationTypes(
+                      ImmutableList.of(fooTypeSubject), ImmutableList.of()));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A", "B", "A", "B", "A", "B", "A", "B");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      foo(new A(), new B());
+      bar(new B(), new A());
+      baz(new B(), new A());
+      qux(new B(), new A());
+    }
+
+    @NeverInline
+    static void foo(A a, B b) {
+      System.out.println(a);
+      System.out.println(b);
+    }
+
+    @NeverInline
+    static void bar(@Foo B b, @Bar A a) {
+      System.out.println(a);
+      System.out.println(b);
+    }
+
+    @NeverInline
+    static void baz(@Foo B b, A a) {
+      System.out.println(a);
+      System.out.println(b);
+    }
+
+    @NeverInline
+    static void qux(B b, @Foo A a) {
+      System.out.println(a);
+      System.out.println(b);
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class A {
+
+    @Override
+    public String toString() {
+      return "A";
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class B {
+
+    @Override
+    public String toString() {
+      return "B";
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface Foo {}
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface Bar {}
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithoutSharingTest.java b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithoutSharingTest.java
index 0088b3b..d9190df 100644
--- a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithoutSharingTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithoutSharingTest.java
@@ -41,8 +41,6 @@
             options -> options.testing.enableExperimentalProtoNormalization = true)
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
-        // TODO(b/173398086): uniqueMethodWithName() does not work with proto changes.
-        .noMinification()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 7f35d90..61c6fb5 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -115,7 +115,7 @@
   }
 
   @Override
-  public List<AnnotationSubject> annotations() {
+  public List<FoundAnnotationSubject> annotations() {
     throw new Unreachable("Cannot determine if an absent class has annotations");
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
index 8d91f3c..6eeab19 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
@@ -60,7 +60,7 @@
   }
 
   @Override
-  public List<AnnotationSubject> annotations() {
+  public List<FoundAnnotationSubject> annotations() {
     throw new Unreachable("Cannot determine if an absent field has annotations");
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
index 9be6468..a4fb7b4 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -87,6 +87,16 @@
   }
 
   @Override
+  public List<List<FoundAnnotationSubject>> getParameterAnnotations() {
+    throw new Unreachable("Cannot get the parameter annotations for an absent method");
+  }
+
+  @Override
+  public List<FoundAnnotationSubject> getParameterAnnotations(int index) {
+    throw new Unreachable("Cannot get the parameter annotations for an absent method");
+  }
+
+  @Override
   public ProgramMethod getProgramMethod() {
     return null;
   }
@@ -127,7 +137,7 @@
   }
 
   @Override
-  public List<AnnotationSubject> annotations() {
+  public List<FoundAnnotationSubject> annotations() {
     throw new Unreachable("Cannot determine if an absent method has annotations");
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AnnotationMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AnnotationMatchers.java
new file mode 100644
index 0000000..8965f41
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AnnotationMatchers.java
@@ -0,0 +1,103 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public class AnnotationMatchers {
+
+  public static Matcher<MethodSubject> hasParameterAnnotationTypes(
+      List<TypeSubject>... typeSubjects) {
+    return hasParameterAnnotationTypes(Arrays.asList(typeSubjects));
+  }
+
+  public static Matcher<MethodSubject> hasParameterAnnotationTypes(
+      List<List<TypeSubject>> typeSubjects) {
+    return new TypeSafeMatcher<MethodSubject>() {
+
+      @Override
+      protected boolean matchesSafely(MethodSubject subject) {
+        List<List<FoundAnnotationSubject>> parameterAnnotations = subject.getParameterAnnotations();
+        if (parameterAnnotations.size() != typeSubjects.size()) {
+          return false;
+        }
+        for (int parameterIndex = 0;
+            parameterIndex < parameterAnnotations.size();
+            parameterIndex++) {
+          List<FoundAnnotationSubject> parameterAnnotationsForParameter =
+              parameterAnnotations.get(parameterIndex);
+          List<TypeSubject> typeSubjectsForParameter = typeSubjects.get(parameterIndex);
+          if (!hasAnnotationTypes(typeSubjectsForParameter)
+              .matches(parameterAnnotationsForParameter)) {
+            return false;
+          }
+        }
+        return true;
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText(
+            "has parameter annotation types ["
+                + StringUtils.join(
+                    "; ",
+                    typeSubjects,
+                    typeSubjectsForParameter ->
+                        StringUtils.join(", ", typeSubjectsForParameter, TypeSubject::getTypeName))
+                + "]");
+      }
+
+      @Override
+      public void describeMismatchSafely(MethodSubject subject, Description description) {
+        description.appendText("method did not");
+      }
+    };
+  }
+
+  public static Matcher<List<FoundAnnotationSubject>> hasAnnotationTypes(
+      TypeSubject... typeSubjects) {
+    return hasAnnotationTypes(Arrays.asList(typeSubjects));
+  }
+
+  public static Matcher<List<FoundAnnotationSubject>> hasAnnotationTypes(
+      List<TypeSubject> typeSubjects) {
+    return new TypeSafeMatcher<List<FoundAnnotationSubject>>() {
+
+      @Override
+      protected boolean matchesSafely(List<FoundAnnotationSubject> subjects) {
+        if (subjects.size() != typeSubjects.size()) {
+          return false;
+        }
+        for (int i = 0; i < subjects.size(); i++) {
+          FoundAnnotationSubject subject = subjects.get(i);
+          TypeSubject typeSubject = typeSubjects.get(i);
+          if (!subject.getType().equals(typeSubject)) {
+            return false;
+          }
+        }
+        return true;
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText(
+            "has annotation types ["
+                + StringUtils.join(", ", typeSubjects, TypeSubject::getTypeName)
+                + "]");
+      }
+
+      @Override
+      public void describeMismatchSafely(
+          List<FoundAnnotationSubject> subjects, Description description) {
+        description.appendText("annotations did not");
+      }
+    };
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
index 754b470..2c38d35 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
@@ -10,7 +10,7 @@
 
 public abstract class ClassOrMemberSubject extends Subject {
 
-  public abstract List<AnnotationSubject> annotations();
+  public abstract List<FoundAnnotationSubject> annotations();
 
   public abstract AnnotationSubject annotation(String name);
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
index d06b289..e73c25a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
@@ -14,13 +14,22 @@
 public class FoundAnnotationSubject extends AnnotationSubject {
 
   private final DexAnnotation annotation;
+  private final CodeInspector codeInspector;
 
-  FoundAnnotationSubject(DexAnnotation annotation) {
+  FoundAnnotationSubject(DexAnnotation annotation, CodeInspector codeInspector) {
     this.annotation = annotation;
+    this.codeInspector = codeInspector;
   }
 
-  public static List<AnnotationSubject> listFromDex(DexAnnotationSet annotations) {
-    return ListUtils.map(annotations.annotations, FoundAnnotationSubject::new);
+  public static List<FoundAnnotationSubject> listFromDex(
+      DexAnnotationSet annotations, CodeInspector codeInspector) {
+    return ListUtils.map(
+        annotations.annotations,
+        annotation -> new FoundAnnotationSubject(annotation, codeInspector));
+  }
+
+  public TypeSubject getType() {
+    return new TypeSubject(codeInspector, annotation.getAnnotationType());
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index cdc420e..fbf91d6 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -359,8 +359,8 @@
   }
 
   @Override
-  public List<AnnotationSubject> annotations() {
-    return FoundAnnotationSubject.listFromDex(dexClass.annotations());
+  public List<FoundAnnotationSubject> annotations() {
+    return FoundAnnotationSubject.listFromDex(dexClass.annotations(), codeInspector);
   }
 
   @Override
@@ -372,7 +372,7 @@
     DexAnnotation annotation = codeInspector.findAnnotation(name, dexClass.annotations());
     return annotation == null
         ? new AbsentAnnotationSubject()
-        : new FoundAnnotationSubject(annotation);
+        : new FoundAnnotationSubject(annotation, codeInspector);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
index 2ea0107..c2daded 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -114,7 +114,7 @@
   }
 
   @Override
-  public List<AnnotationSubject> annotations() {
+  public List<FoundAnnotationSubject> annotations() {
     throw new Unimplemented();
   }
 
@@ -123,7 +123,7 @@
     DexAnnotation annotation = codeInspector.findAnnotation(name, dexField.annotations());
     return annotation == null
         ? new AbsentAnnotationSubject()
-        : new FoundAnnotationSubject(annotation);
+        : new FoundAnnotationSubject(annotation, codeInspector);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index b99b090..b33f4a3 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -42,6 +42,7 @@
 import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
@@ -136,6 +137,24 @@
   }
 
   @Override
+  public List<FoundAnnotationSubject> getParameterAnnotations(int index) {
+    return FoundAnnotationSubject.listFromDex(
+        getMethod().getParameterAnnotation(index), codeInspector);
+  }
+
+  @Override
+  public List<List<FoundAnnotationSubject>> getParameterAnnotations() {
+    List<List<FoundAnnotationSubject>> parameterAnnotations =
+        new ArrayList<>(getMethod().getParameters().size());
+    for (int parameterIndex = 0;
+        parameterIndex < getMethod().getParameters().size();
+        parameterIndex++) {
+      parameterAnnotations.add(getParameterAnnotations(parameterIndex));
+    }
+    return parameterAnnotations;
+  }
+
+  @Override
   public ProgramMethod getProgramMethod() {
     return new ProgramMethod(clazz.getDexProgramClass(), getMethod());
   }
@@ -343,8 +362,8 @@
   }
 
   @Override
-  public List<AnnotationSubject> annotations() {
-    return FoundAnnotationSubject.listFromDex(dexMethod.annotations());
+  public List<FoundAnnotationSubject> annotations() {
+    return FoundAnnotationSubject.listFromDex(dexMethod.annotations(), codeInspector);
   }
 
   @Override
@@ -352,7 +371,7 @@
     DexAnnotation annotation = codeInspector.findAnnotation(name, dexMethod.annotations());
     return annotation == null
         ? new AbsentAnnotationSubject()
-        : new FoundAnnotationSubject(annotation);
+        : new FoundAnnotationSubject(annotation, codeInspector);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 3b7fe05..dda3e35 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -56,6 +56,10 @@
 
   public abstract List<TypeSubject> getParameters();
 
+  public abstract List<List<FoundAnnotationSubject>> getParameterAnnotations();
+
+  public abstract List<FoundAnnotationSubject> getParameterAnnotations(int index);
+
   public abstract ProgramMethod getProgramMethod();
 
   public Iterator<InstructionSubject> iterateInstructions() {