Soft-pin items retained by reflective identification

Change-Id: I5fe708e8689ba20be5547d9342edc6313521008f
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 1f58402..6be7520 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -411,6 +411,11 @@
   }
 
   public static ProgramMethod asProgramMethodOrNull(
+      DexEncodedMethod method, DexProgramClass holder) {
+    return method != null ? method.asProgramMethod(holder) : null;
+  }
+
+  public static ProgramMethod asProgramMethodOrNull(
       DexEncodedMethod method, DexDefinitionSupplier definitions) {
     return method != null ? method.asProgramMethod(definitions) : null;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index 3dc2b58..4a403c4 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -167,6 +167,10 @@
     return backing.getMethod(methodProto, methodName);
   }
 
+  public final DexEncodedMethod getMethod(DexMethodSignature method) {
+    return getMethod(method.getProto(), method.getName());
+  }
+
   public DexEncodedMethod getMethod(Predicate<DexEncodedMethod> predicate) {
     DexEncodedMethod result = backing.getDirectMethod(predicate);
     return result != null ? result : backing.getVirtualMethod(predicate);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 46732db..7a0c559 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.graph.DexEncodedMethod.asProgramMethodOrNull;
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.graph.FieldAccessInfoImpl.MISSING_FIELD_ACCESS_INFO;
 import static com.android.tools.r8.ir.desugar.LambdaDescriptor.isLambdaMetafactoryMethod;
@@ -48,6 +49,7 @@
 import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexMethodSignature;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexReference;
@@ -142,6 +144,7 @@
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.Visibility;
 import com.android.tools.r8.utils.WorkList;
+import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
 import com.android.tools.r8.utils.collections.ProgramFieldSet;
 import com.android.tools.r8.utils.collections.ProgramMethodMap;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -764,12 +767,14 @@
     return getProgramClassOrNull(member.getHolderType(), context);
   }
 
+  private DexClass getClassOrNullFromReflectiveAccess(DexType type, ProgramDefinition context) {
+    // To avoid that we report reflectively accessed types as missing.
+    return definitionFor(type, context, this::recordNonProgramClass, this::ignoreMissingClass);
+  }
+
   private DexProgramClass getProgramClassOrNullFromReflectiveAccess(
       DexType type, ProgramDefinition context) {
-    // To avoid that we report reflectively accessed types as missing.
-    DexClass clazz =
-        definitionFor(type, context, this::recordNonProgramClass, this::ignoreMissingClass);
-    return clazz != null && clazz.isProgramClass() ? clazz.asProgramClass() : null;
+    return asProgramClassOrNull(getClassOrNullFromReflectiveAccess(type, context));
   }
 
   private void warnIfLibraryTypeInheritsFromProgramType(DexLibraryClass clazz) {
@@ -858,6 +863,8 @@
         }
         if (clazz.isExternalizable(appView)) {
           workList.enqueueMarkMethodLiveAction(defaultInitializer, defaultInitializer, witness);
+          applyMinimumKeepInfoWhenLiveOrTargeted(
+              defaultInitializer, KeepMethodInfo.newEmptyJoiner().disallowOptimization());
         }
       }
     }
@@ -899,6 +906,9 @@
     }
     if (clazz.hasDefaultInitializer()) {
       workList.enqueueMarkMethodLiveAction(clazz.getProgramDefaultInitializer(), clazz, reason);
+      applyMinimumKeepInfoWhenLiveOrTargeted(
+          clazz.getProgramDefaultInitializer(),
+          KeepMethodInfo.newEmptyJoiner().disallowOptimization());
     }
   }
 
@@ -3232,9 +3242,7 @@
                   applyMinimumKeepInfoWhenLive(clazz, preconditionEvent, minimumKeepInfo),
               (field, minimumKeepInfo) ->
                   applyMinimumKeepInfoWhenLive(field, preconditionEvent, minimumKeepInfo),
-              (method, minimumKeepInfo) ->
-                  applyMinimumKeepInfoWhenLiveOrTargeted(
-                      method, preconditionEvent, minimumKeepInfo));
+              this::applyMinimumKeepInfoWhenLiveOrTargeted);
     }
     enqueueAllIfNotShrinking();
     trace(executorService, timing);
@@ -3373,8 +3381,14 @@
 
   private void applyMinimumKeepInfoWhenLiveOrTargeted(
       ProgramMethod method,
-      EnqueuerEvent preconditionEvent,
       KeepMethodInfo.Joiner minimumKeepInfo) {
+    applyMinimumKeepInfoWhenLiveOrTargeted(method, minimumKeepInfo, EnqueuerEvent.unconditional());
+  }
+
+  private void applyMinimumKeepInfoWhenLiveOrTargeted(
+      ProgramMethod method,
+      KeepMethodInfo.Joiner minimumKeepInfo,
+      EnqueuerEvent preconditionEvent) {
     if (liveMethods.contains(method) || targetedMethods.contains(method)) {
       keepInfo.joinMethod(method, info -> info.merge(minimumKeepInfo));
     } else {
@@ -3405,7 +3419,7 @@
       ProgramMethod method,
       KeepMethodInfo.Joiner minimumKeepInfo) {
     if (isPreconditionForMinimumKeepInfoSatisfied(preconditionEvent)) {
-      applyMinimumKeepInfoWhenLiveOrTargeted(method, preconditionEvent, minimumKeepInfo);
+      applyMinimumKeepInfoWhenLiveOrTargeted(method, minimumKeepInfo, preconditionEvent);
     } else {
       dependentMinimumKeepInfo
           .getOrCreateMinimumKeepInfoFor(preconditionEvent)
@@ -3429,7 +3443,7 @@
               applyMinimumKeepInfoWhenLive(field, preconditionEvent, minimumKeepInfoForField),
           (method, minimumKeepInfoForMethod) ->
               applyMinimumKeepInfoWhenLiveOrTargeted(
-                  method, preconditionEvent, minimumKeepInfoForMethod));
+                  method, minimumKeepInfoForMethod, preconditionEvent));
     }
   }
 
@@ -3480,10 +3494,6 @@
       assert old == null || old == clazz;
     }
 
-    public void addLiveMethods(Iterable<ProgramMethod> methods) {
-      methods.forEach(this::addLiveMethod);
-    }
-
     public void addLiveMethod(ProgramMethod method) {
       DexMethod signature = method.getDefinition().getReference();
       ProgramMethod old = liveMethods.put(signature, method);
@@ -3500,18 +3510,6 @@
       newInterfaces.add(newInterface);
     }
 
-    void addLiveMethodWithKeepAction(
-        ProgramMethod method, Consumer<KeepMethodInfo.Joiner> keepAction) {
-      addLiveMethod(method);
-      liveMethodsWithKeepActions.add(new Pair<>(method, keepAction));
-    }
-
-    public ProgramMethodSet getLiveMethods() {
-      ProgramMethodSet set = ProgramMethodSet.create();
-      liveMethods.values().forEach(set::add);
-      return set;
-    }
-
     public void addMinimumKeepInfo(ProgramMethod method, Consumer<KeepMethodInfo.Joiner> consumer) {
       consumer.accept(
           minimumKeepInfo.computeIfAbsent(method, ignoreKey(KeepMethodInfo::newEmptyJoiner)));
@@ -3543,8 +3541,7 @@
 
       minimumKeepInfo.forEach(
           (method, minimumKeepInfoForMethod) ->
-              enqueuer.applyMinimumKeepInfoWhenLiveOrTargeted(
-                  method, UnconditionalKeepInfoEvent.get(), minimumKeepInfoForMethod));
+              enqueuer.applyMinimumKeepInfoWhenLiveOrTargeted(method, minimumKeepInfoForMethod));
     }
   }
 
@@ -4531,18 +4528,19 @@
       if (clazz == null) {
         return;
       }
-      DexEncodedMethod targetedMethodDefinition = clazz.lookupMethod(targetedMethodReference);
-      if (targetedMethodDefinition == null) {
+      ProgramMethod targetedMethod = clazz.lookupProgramMethod(targetedMethodReference);
+      if (targetedMethod == null) {
         return;
       }
-      ProgramMethod targetedMethod = new ProgramMethod(clazz, targetedMethodDefinition);
       KeepReason reason = KeepReason.reflectiveUseIn(method);
-      if (targetedMethodDefinition.isStatic() || targetedMethodDefinition.isInstanceInitializer()) {
+      if (targetedMethod.getDefinition().belongsToDirectPool()) {
         markMethodAsTargeted(targetedMethod, reason);
         markDirectStaticOrConstructorMethodAsLive(targetedMethod, reason);
       } else {
         markVirtualMethodAsLive(targetedMethod, reason);
       }
+      applyMinimumKeepInfoWhenLiveOrTargeted(
+          targetedMethod, KeepMethodInfo.newEmptyJoiner().disallowOptimization());
     }
   }
 
@@ -4572,6 +4570,8 @@
       markClassAsInstantiatedWithReason(clazz, reason);
       markMethodAsTargeted(defaultInitializer, reason);
       markDirectStaticOrConstructorMethodAsLive(defaultInitializer, reason);
+      applyMinimumKeepInfoWhenLiveOrTargeted(
+          defaultInitializer, KeepMethodInfo.newEmptyJoiner().disallowOptimization());
     }
   }
 
@@ -4673,6 +4673,8 @@
       markClassAsInstantiatedWithReason(clazz, reason);
       markMethodAsTargeted(initializer, reason);
       markDirectStaticOrConstructorMethodAsLive(initializer, reason);
+      applyMinimumKeepInfoWhenLiveOrTargeted(
+          initializer, KeepMethodInfo.newEmptyJoiner().disallowOptimization());
     }
   }
 
@@ -4795,6 +4797,7 @@
       DexType serviceType, ProgramMethod context, KeepReason reason) {
     List<DexType> serviceImplementationTypes =
         appView.appServices().serviceImplementationsFor(serviceType);
+    DexMethodSignatureSet serviceMethods = getServiceMethods(serviceType, context);
     for (DexType serviceImplementationType : serviceImplementationTypes) {
       if (!serviceImplementationType.isClassType()) {
         // Should never happen.
@@ -4803,12 +4806,46 @@
 
       DexProgramClass serviceImplementationClass =
           getProgramClassOrNull(serviceImplementationType, context);
-      if (serviceImplementationClass != null && serviceImplementationClass.isProgramClass()) {
-        markClassAsInstantiatedWithReason(serviceImplementationClass, reason);
+      if (serviceImplementationClass == null) {
+        continue;
+      }
+
+      markClassAsInstantiatedWithReason(serviceImplementationClass, reason);
+
+      ProgramMethod defaultInitializer = serviceImplementationClass.getProgramDefaultInitializer();
+      if (defaultInitializer != null) {
+        applyMinimumKeepInfoWhenLiveOrTargeted(
+            defaultInitializer, KeepMethodInfo.newEmptyJoiner().disallowOptimization());
+      }
+
+      for (DexMethodSignature serviceMethod : serviceMethods) {
+        ProgramMethod serviceImplementationMethod =
+            asProgramMethodOrNull(
+                serviceImplementationClass.getMethodCollection().getMethod(serviceMethod),
+                serviceImplementationClass);
+        if (serviceImplementationMethod != null) {
+          applyMinimumKeepInfoWhenLiveOrTargeted(
+              serviceImplementationMethod, KeepMethodInfo.newEmptyJoiner().disallowOptimization());
+        }
       }
     }
   }
 
+  private DexMethodSignatureSet getServiceMethods(DexType serviceType, ProgramMethod context) {
+    DexMethodSignatureSet serviceMethods = DexMethodSignatureSet.create();
+    WorkList<DexType> serviceTypes = WorkList.newIdentityWorkList(serviceType);
+    while (serviceTypes.hasNext()) {
+      DexType current = serviceTypes.next();
+      DexClass clazz = getClassOrNullFromReflectiveAccess(current, context);
+      if (clazz == null) {
+        continue;
+      }
+      clazz.forEachClassMethodMatching(DexEncodedMethod::belongsToVirtualPool, serviceMethods::add);
+      serviceTypes.addIfNotSeen(clazz.getInterfaces());
+    }
+    return serviceMethods;
+  }
+
   private static class SetWithReportedReason<T> {
 
     private final Set<T> items = Sets.newIdentityHashSet();
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java
index 8a3439d..93d5f60 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java
@@ -12,6 +12,10 @@
 
 public abstract class EnqueuerEvent {
 
+  public static UnconditionalKeepInfoEvent unconditional() {
+    return UnconditionalKeepInfoEvent.get();
+  }
+
   public DexDefinition getDefinition(DexDefinitionSupplier definitions) {
     return null;
   }
diff --git a/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
index a3ae2e9..7bf4064 100644
--- a/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
+++ b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
@@ -11,6 +11,8 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.KeepUnusedReturnValue;
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -53,40 +55,53 @@
   public void test() throws Exception {
     R8TestCompileResult compileResult =
         testForR8Compat(parameters.getBackend())
-            .addProgramClasses(Main.class, Service.class, Foo.class, FooImpl.class)
+            .addProgramClasses(Main.class, Foo.class, FooImpl.class)
             .addKeepMainRule(Main.class)
             .addKeepAttributes(
                 ProguardKeepAttributes.SIGNATURE,
                 ProguardKeepAttributes.INNER_CLASSES,
                 ProguardKeepAttributes.ENCLOSING_METHOD)
+            .applyIf(
+                minification,
+                testBuilder ->
+                    testBuilder.addKeepRules(
+                        "-keep,allowoptimization,allowshrinking class "
+                            + Main.class.getTypeName()
+                            + " { *** test(); }"))
+            .enableInliningAnnotations()
+            .enableKeepUnusedReturnValueAnnotations()
             .minification(minification)
             .setMinApi(parameters.getApiLevel())
             .compile()
             .inspect(
                 inspector -> {
-                  assertThat(inspector.clazz(Main.class), isPresentAndNotRenamed());
+                  ClassSubject mainClass = inspector.clazz(Main.class);
+                  assertThat(mainClass, isPresentAndNotRenamed());
                   assertThat(inspector.clazz(Foo.class), not(isPresent()));
                   assertThat(inspector.clazz(FooImpl.class), isPresentAndRenamed(minification));
-                  ClassSubject serviceClass = inspector.clazz(Service.class);
-                  assertThat(serviceClass, isPresentAndRenamed(minification));
-                  // TODO(124477502): Using uniqueMethodWithName("fooList") does not work.
-                  assertEquals(1, serviceClass.allMethods().size());
-                  MethodSubject fooList = serviceClass.allMethods().get(0);
-                  assertThat(fooList, isPresent());
+                  // TODO(124477502): Using uniqueMethodWithName("test") does not work.
+                  MethodSubject testMethod =
+                      mainClass.uniqueMethodThatMatches(
+                          method ->
+                              method.isStatic()
+                                  && !method.getMethod().getName().toString().equals("main"));
+                  assertThat(testMethod, isPresent());
                   checkSignature(
-                      inspector, fooList.asFoundMethodSubject().getFinalSignatureAttribute());
+                      inspector, testMethod.asFoundMethodSubject().getFinalSignatureAttribute());
                 });
 
     String fooImplFinalName = compileResult.inspector().clazz(FooImpl.class).getFinalName();
 
     compileResult
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutput(StringUtils.lines(fooImplFinalName, fooImplFinalName));
+        .assertSuccessWithOutput(
+            StringUtils.lines(fooImplFinalName, fooImplFinalName, "Hello world!"));
   }
 
   public static class Main {
     public static void main(String... args) throws Exception {
-      Method method = Service.class.getMethod("fooList");
+      String methodName = System.currentTimeMillis() >= 0 ? "test" : null;
+      Method method = Main.class.getDeclaredMethod(methodName);
 
       try {
         ParameterizedType type = (ParameterizedType) method.getGenericReturnType();
@@ -99,11 +114,17 @@
       // Convince R8 we only use subtypes to get class merging of Foo into FooImpl.
       Foo<String> foo = new FooImpl<>();
       System.out.println(foo.getClass().getCanonicalName());
-    }
-  }
 
-  interface Service {
-    Foo<String> fooList();
+      // Ensure test() remains in output.
+      test();
+    }
+
+    @NeverInline
+    @KeepUnusedReturnValue
+    static Foo<String> test() {
+      System.out.println("Hello world!");
+      return new FooImpl<>();
+    }
   }
 
   interface Foo<T> {}