Add GenericSignatureEnqueuerAnalysis to visit generic types

This will basically just ensure that we can lookup definitions for
types only mentioned in generic signatures.

Bug: 129925954
Change-Id: I4afc63432969a5ec4b5944f9f6c319ad4dc4a7d0
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 34b73a8..0f43472 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -429,6 +429,8 @@
           annotationRemover.ensureValid().run();
           classesToRetainInnerClassAttributeFor =
               annotationRemover.getClassesToRetainInnerClassAttributeFor();
+          new GenericSignatureRewriter(appView, NamingLens.getIdentityLens())
+              .run(appView.appInfo().classes(), executorService);
         }
       } finally {
         timing.end();
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java
new file mode 100644
index 0000000..b01445d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java
@@ -0,0 +1,151 @@
+// 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.graph;
+
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.ReturnType;
+import com.android.tools.r8.graph.GenericSignature.TypeSignature;
+import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
+import com.android.tools.r8.shaking.EnqueuerWorklist;
+import java.util.List;
+
+public class GenericSignatureEnqueuerAnalysis extends EnqueuerAnalysis {
+
+  private final GenericSignatureVisitor visitor;
+
+  public GenericSignatureEnqueuerAnalysis(DexDefinitionSupplier definitionSupplier) {
+    visitor = new GenericSignatureTypeVisitor(definitionSupplier);
+  }
+
+  @Override
+  public void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {
+    visitor.visitClassSignature(clazz.getClassSignature());
+  }
+
+  @Override
+  public void processNewlyLiveField(ProgramField field) {
+    visitor.visitFieldTypeSignature(field.getDefinition().getGenericSignature());
+  }
+
+  @Override
+  public void processNewlyLiveMethod(ProgramMethod method) {
+    visitor.visitMethodSignature(method.getDefinition().getGenericSignature());
+  }
+
+  private static class GenericSignatureTypeVisitor implements GenericSignatureVisitor {
+
+    private final DexDefinitionSupplier definitionSupplier;
+
+    private GenericSignatureTypeVisitor(DexDefinitionSupplier definitionSupplier) {
+      this.definitionSupplier = definitionSupplier;
+    }
+
+    @Override
+    public void visitClassSignature(ClassSignature classSignature) {
+      if (classSignature.hasNoSignature()) {
+        return;
+      }
+      classSignature.visit(this);
+    }
+
+    @Override
+    public void visitMethodSignature(MethodTypeSignature methodSignature) {
+      if (methodSignature.hasNoSignature()) {
+        return;
+      }
+      methodSignature.visit(this);
+    }
+
+    @Override
+    public void visitFieldTypeSignature(FieldTypeSignature fieldSignature) {
+      if (fieldSignature.hasNoSignature()) {
+        return;
+      }
+      if (fieldSignature.isStar()) {
+        return;
+      }
+      if (fieldSignature.isTypeVariableSignature()) {
+        return;
+      }
+      if (fieldSignature.isArrayTypeSignature()) {
+        fieldSignature.asArrayTypeSignature().visit(this);
+        return;
+      }
+      assert fieldSignature.isClassTypeSignature();
+      visitClassTypeSignature(fieldSignature.asClassTypeSignature());
+    }
+
+    private void visitClassTypeSignature(ClassTypeSignature classTypeSignature) {
+      definitionSupplier.definitionFor(classTypeSignature.type);
+      classTypeSignature.visit(this);
+    }
+
+    @Override
+    public void visitFormalTypeParameters(List<FormalTypeParameter> formalTypeParameters) {
+      formalTypeParameters.forEach(formalTypeParameter -> formalTypeParameter.visit(this));
+    }
+
+    @Override
+    public void visitClassBound(FieldTypeSignature fieldSignature) {
+      visitFieldTypeSignature(fieldSignature);
+    }
+
+    @Override
+    public void visitInterfaceBound(FieldTypeSignature fieldSignature) {
+      visitFieldTypeSignature(fieldSignature);
+    }
+
+    @Override
+    public void visitSuperClass(ClassTypeSignature classTypeSignature) {
+      visitClassTypeSignature(classTypeSignature);
+    }
+
+    @Override
+    public void visitSuperInterface(ClassTypeSignature classTypeSignature) {
+      visitClassTypeSignature(classTypeSignature);
+    }
+
+    @Override
+    public void visitTypeSignature(TypeSignature typeSignature) {
+      if (typeSignature.isBaseTypeSignature()) {
+        return;
+      }
+      assert typeSignature.isFieldTypeSignature();
+      visitFieldTypeSignature(typeSignature.asFieldTypeSignature());
+    }
+
+    @Override
+    public void visitSimpleClass(ClassTypeSignature classTypeSignature) {
+      visitClassTypeSignature(classTypeSignature);
+    }
+
+    @Override
+    public void visitReturnType(ReturnType returnType) {
+      if (returnType.isVoidDescriptor()) {
+        return;
+      }
+      visitTypeSignature(returnType.typeSignature);
+    }
+
+    @Override
+    public void visitMethodTypeSignatures(List<TypeSignature> typeSignatures) {
+      typeSignatures.forEach(this::visitTypeSignature);
+    }
+
+    @Override
+    public void visitThrowsSignatures(List<TypeSignature> typeSignatures) {
+      typeSignatures.forEach(this::visitTypeSignature);
+    }
+
+    @Override
+    public void visitTypeArguments(List<FieldTypeSignature> typeArguments) {
+      typeArguments.forEach(this::visitFieldTypeSignature);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
index 7804c5f..e9b45f1 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
@@ -38,21 +38,21 @@
   }
 
   public ClassSignature rewrite(ClassSignature classSignature) {
-    if (classSignature.hasNoSignature() || appView.graphLens().isIdentityLens()) {
+    if (classSignature.hasNoSignature()) {
       return classSignature;
     }
     return new ClassSignatureRewriter().run(classSignature);
   }
 
   public FieldTypeSignature rewrite(FieldTypeSignature fieldTypeSignature) {
-    if (fieldTypeSignature.hasNoSignature() || appView.graphLens().isIdentityLens()) {
+    if (fieldTypeSignature.hasNoSignature()) {
       return fieldTypeSignature;
     }
     return new TypeSignatureRewriter().run(fieldTypeSignature);
   }
 
   public MethodTypeSignature rewrite(MethodTypeSignature methodTypeSignature) {
-    if (methodTypeSignature.hasNoSignature() || appView.graphLens().isIdentityLens()) {
+    if (methodTypeSignature.hasNoSignature()) {
       return methodTypeSignature;
     }
     return new MethodTypeSignatureRewriter().run(methodTypeSignature);
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 629506c..a7fdc85 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -47,6 +47,7 @@
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.GenericSignatureEnqueuerAnalysis;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.LookupLambdaTarget;
@@ -2730,6 +2731,10 @@
           new KotlinMetadataEnqueuerExtension(
               appView, enqueuerDefinitionSupplier, initialPrunedTypes));
     }
+    if (appView.options().getProguardConfiguration() != null
+        && appView.options().getProguardConfiguration().getKeepAttributes().signature) {
+      registerAnalysis(new GenericSignatureEnqueuerAnalysis(enqueuerDefinitionSupplier));
+    }
     if (mode.isInitialTreeShaking()) {
       // This is simulating the effect of the "root set" applied rules.
       // This is done only in the initial pass, in subsequent passes the "rules" are reapplied
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/UnknownClassInSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/UnknownClassInSignatureTest.java
new file mode 100644
index 0000000..4c2ac5e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/UnknownClassInSignatureTest.java
@@ -0,0 +1,83 @@
+// 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.graph.genericsignature;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.transformers.ClassFileTransformer.FieldPredicate;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class UnknownClassInSignatureTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String NEW_CLASS_SIGNATURE = "<T:LUnknownClass1;>LUnknownClass2<LUnknownClass3;>;";
+  private final String NEW_FIELD_SIGNATURE = "LUnknownClass4<LUnknownClass4;>;";
+  private final String NEW_METHOD_SIGNATURE = "()LUnkownClass5<LunknownPackage/UnknownClass6;>;";
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public UnknownClassInSignatureTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(
+            transformer(Main.class)
+                .removeInnerClasses()
+                .setGenericSignature(NEW_CLASS_SIGNATURE)
+                .setGenericSignature(FieldPredicate.onName("field"), NEW_FIELD_SIGNATURE)
+                .setGenericSignature(MethodPredicate.onName("main"), NEW_METHOD_SIGNATURE)
+                .transform())
+        .addKeepAllClassesRule()
+        .addKeepAttributes(
+            ProguardKeepAttributes.SIGNATURE,
+            ProguardKeepAttributes.ENCLOSING_METHOD,
+            ProguardKeepAttributes.INNER_CLASSES)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!")
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(Main.class);
+              assertThat(clazz, isPresent());
+              assertEquals(NEW_CLASS_SIGNATURE, clazz.getFinalSignatureAttribute());
+              FieldSubject field = clazz.uniqueFieldWithFinalName("field");
+              assertThat(field, isPresent());
+              assertEquals(NEW_FIELD_SIGNATURE, field.getFinalSignatureAttribute());
+              MethodSubject method = clazz.uniqueMethodWithFinalName("main");
+              assertThat(method, isPresent());
+              assertEquals(NEW_METHOD_SIGNATURE, method.getFinalSignatureAttribute());
+            });
+  }
+
+  public static class Main {
+
+    private List<Main> field;
+
+    public static void main(String[] args) {
+      System.out.println("Hello World!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 86e4cb7..89ddee8 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -607,6 +607,20 @@
     return renameField(FieldPredicate.onName(oldName), newName);
   }
 
+  public ClassFileTransformer setGenericSignature(FieldPredicate predicate, String newSignature) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public FieldVisitor visitField(
+              int access, String name, String descriptor, String signature, Object value) {
+            if (predicate.test(access, name, descriptor, signature, value)) {
+              return super.visitField(access, name, descriptor, newSignature, value);
+            }
+            return super.visitField(access, name, descriptor, signature, value);
+          }
+        });
+  }
+
   /** Abstraction of the MethodVisitor.visitMethodInsn method with its sub visitor. */
   @FunctionalInterface
   public interface MethodInsnTransform {