Introduce a generic signature evaluator

This CL also introduce the notion of an invalid generic signature that
is maintained.

Bug: 172999267
Change-Id: I350acc7142509be28ef71ccada81aefe1b91ae72
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
index a92dc75..1dc1f4f 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -134,6 +134,12 @@
     default boolean hasNoSignature() {
       return !hasSignature();
     }
+
+    default boolean isInvalid() {
+      return false;
+    }
+
+    DexDefinitionSignature<T> toInvalid();
   }
 
   public static class FormalTypeParameter {
@@ -207,6 +213,12 @@
     }
 
     @Override
+    public InvalidClassSignature toInvalid() {
+      // Since we could create the structure we are also able to generate a string for it.
+      return new InvalidClassSignature(toString());
+    }
+
+    @Override
     public boolean isClassSignature() {
       return true;
     }
@@ -216,6 +228,10 @@
       return this;
     }
 
+    public List<FormalTypeParameter> getFormalTypeParameters() {
+      return formalTypeParameters;
+    }
+
     public void visit(GenericSignatureVisitor visitor) {
       visitor.visitFormalTypeParameters(formalTypeParameters);
       visitor.visitSuperClass(superClassSignature);
@@ -244,6 +260,42 @@
     }
   }
 
+  private static class InvalidClassSignature extends ClassSignature {
+
+    private final String genericSignatureString;
+
+    InvalidClassSignature(String genericSignatureString) {
+      super(EMPTY_TYPE_PARAMS, NO_FIELD_TYPE_SIGNATURE, EMPTY_SUPER_INTERFACES);
+      this.genericSignatureString = genericSignatureString;
+    }
+
+    @Override
+    public String toRenamedString(NamingLens namingLens, Predicate<DexType> isTypeMissing) {
+      return genericSignatureString;
+    }
+
+    @Override
+    public String toString() {
+      return genericSignatureString;
+    }
+
+    @Override
+    public InvalidClassSignature toInvalid() {
+      assert false : "Should not invoke toInvalid on an invalid signature";
+      return this;
+    }
+
+    @Override
+    public void visit(GenericSignatureVisitor visitor) {
+      assert false : "Should not visit an invalid signature";
+    }
+
+    @Override
+    public boolean isInvalid() {
+      return true;
+    }
+  }
+
   public abstract static class TypeSignature {
 
     public boolean isFieldTypeSignature() {
@@ -354,6 +406,43 @@
     public static FieldTypeSignature noSignature() {
       return NO_FIELD_TYPE_SIGNATURE;
     }
+
+    @Override
+    public InvalidFieldTypeSignature toInvalid() {
+      return new InvalidFieldTypeSignature(toString());
+    }
+  }
+
+  private static class InvalidFieldTypeSignature extends FieldTypeSignature {
+
+    private final String genericSignature;
+
+    public InvalidFieldTypeSignature(String genericSignature) {
+      super(WildcardIndicator.NONE);
+      this.genericSignature = genericSignature;
+    }
+
+    @Override
+    public FieldTypeSignature asArgument(WildcardIndicator indicator) {
+      assert false : "Should not be called for an invalid signature";
+      return this;
+    }
+
+    @Override
+    public String toString() {
+      return genericSignature;
+    }
+
+    @Override
+    public String toRenamedString(NamingLens namingLens, Predicate<DexType> isTypeMissing) {
+      return genericSignature;
+    }
+
+    @Override
+    public InvalidFieldTypeSignature toInvalid() {
+      assert false : " Should not be called for an invalid signature";
+      return this;
+    }
   }
 
   static final class StarFieldTypeSignature extends FieldTypeSignature {
@@ -436,10 +525,6 @@
       return argument;
     }
 
-    public boolean isNoSignature() {
-      return this == NO_FIELD_TYPE_SIGNATURE;
-    }
-
     @Override
     public ArrayTypeSignature toArrayTypeSignature() {
       return new ArrayTypeSignature(this);
@@ -538,7 +623,7 @@
       return new ArrayTypeSignature(this);
     }
 
-    public String getTypeVariable() {
+    public String typeVariable() {
       return typeVariable;
     }
   }
@@ -674,6 +759,47 @@
     public String toString() {
       return toRenamedString(NamingLens.getIdentityLens(), alwaysTrue());
     }
+
+    @Override
+    public InvalidMethodTypeSignature toInvalid() {
+      return new InvalidMethodTypeSignature(toString());
+    }
+  }
+
+  private static class InvalidMethodTypeSignature extends MethodTypeSignature {
+
+    private final String genericSignature;
+
+    public InvalidMethodTypeSignature(String genericSignature) {
+      super(EMPTY_TYPE_PARAMS, EMPTY_TYPE_SIGNATURES, ReturnType.VOID, EMPTY_TYPE_SIGNATURES);
+      this.genericSignature = genericSignature;
+    }
+
+    @Override
+    public String toRenamedString(NamingLens namingLens, Predicate<DexType> isTypeMissing) {
+      return genericSignature;
+    }
+
+    @Override
+    public String toString() {
+      return genericSignature;
+    }
+
+    @Override
+    public boolean isInvalid() {
+      return true;
+    }
+
+    @Override
+    public void visit(GenericSignatureVisitor visitor) {
+      assert false : "Should not visit an invalid signature";
+    }
+
+    @Override
+    public InvalidMethodTypeSignature toInvalid() {
+      assert false : "Should not be called for an invalid signature";
+      return this;
+    }
   }
 
   public static ClassSignature parseClassSignature(
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
new file mode 100644
index 0000000..a7979f6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
@@ -0,0 +1,305 @@
+// 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 static com.android.tools.r8.graph.GenericSignatureCorrectnessHelper.SignatureEvaluationResult.INVALID_APPLICATION_COUNT;
+import static com.android.tools.r8.graph.GenericSignatureCorrectnessHelper.SignatureEvaluationResult.INVALID_INTERFACE_COUNT;
+import static com.android.tools.r8.graph.GenericSignatureCorrectnessHelper.SignatureEvaluationResult.INVALID_SUPER_TYPE;
+import static com.android.tools.r8.graph.GenericSignatureCorrectnessHelper.SignatureEvaluationResult.INVALID_TYPE_VARIABLE_UNDEFINED;
+import static com.android.tools.r8.graph.GenericSignatureCorrectnessHelper.SignatureEvaluationResult.VALID;
+
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.DexDefinitionSignature;
+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 java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public class GenericSignatureCorrectnessHelper {
+
+  private enum Mode {
+    VERIFY,
+    MARK_AS_INVALID;
+
+    public boolean doNotVerify() {
+      return markAsInvalid();
+    }
+
+    public boolean markAsInvalid() {
+      return this == MARK_AS_INVALID;
+    }
+  }
+
+  public enum SignatureEvaluationResult {
+    INVALID_SUPER_TYPE,
+    INVALID_INTERFACE_COUNT,
+    INVALID_APPLICATION_COUNT,
+    INVALID_TYPE_VARIABLE_UNDEFINED,
+    VALID;
+
+    boolean isValid() {
+      return this == VALID;
+    }
+
+    boolean isInvalid() {
+      return this != VALID;
+    }
+  }
+
+  private final AppView<?> appView;
+  private final Mode mode;
+
+  private GenericSignatureCorrectnessHelper(AppView<?> appView, Mode mode) {
+    this.appView = appView;
+    this.mode = mode;
+  }
+
+  public static GenericSignatureCorrectnessHelper createForInitialCheck(AppView<?> appView) {
+    return new GenericSignatureCorrectnessHelper(appView, Mode.MARK_AS_INVALID);
+  }
+
+  public static GenericSignatureCorrectnessHelper createForVerification(AppView<?> appView) {
+    return new GenericSignatureCorrectnessHelper(appView, Mode.VERIFY);
+  }
+
+  public void run() {
+    appView.appInfo().classes().forEach(this::evaluateSignaturesForClass);
+  }
+
+  public SignatureEvaluationResult evaluateSignaturesForClass(DexProgramClass clazz) {
+    GenericSignatureContextEvaluator genericSignatureContextEvaluator =
+        new GenericSignatureContextEvaluator(appView, clazz, mode);
+    ClassSignature classSignature = clazz.getClassSignature();
+    SignatureEvaluationResult result = VALID;
+    if (classSignature.hasNoSignature() || !classSignature.isInvalid()) {
+      result = genericSignatureContextEvaluator.evaluateClassSignature(classSignature);
+      if (result.isInvalid() && mode.markAsInvalid()) {
+        clazz.setClassSignature(classSignature.toInvalid());
+      }
+    }
+    for (DexEncodedMethod method : clazz.methods()) {
+      SignatureEvaluationResult methodResult =
+          evaluate(
+              method::getGenericSignature,
+              genericSignatureContextEvaluator::visitMethodSignature,
+              method::setGenericSignature);
+      if (result.isValid() && methodResult.isInvalid()) {
+        result = methodResult;
+      }
+    }
+    for (DexEncodedField field : clazz.fields()) {
+      SignatureEvaluationResult fieldResult =
+          evaluate(
+              field::getGenericSignature,
+              genericSignatureContextEvaluator::visitFieldTypeSignature,
+              field::setGenericSignature);
+      if (result.isValid() && fieldResult.isInvalid()) {
+        result = fieldResult;
+      }
+    }
+    return result;
+  }
+
+  private <T extends DexDefinitionSignature<?>> SignatureEvaluationResult evaluate(
+      Supplier<T> getter, Function<T, SignatureEvaluationResult> evaluate, Consumer<T> setter) {
+    T signature = getter.get();
+    if (signature.hasNoSignature() || signature.isInvalid()) {
+      // Already marked as invalid, do nothing
+      return VALID;
+    }
+    SignatureEvaluationResult signatureResult = evaluate.apply(signature);
+    assert signatureResult.isValid() || mode.doNotVerify();
+    if (signatureResult.isInvalid() && mode.doNotVerify()) {
+      setter.accept((T) signature.toInvalid());
+    }
+    return signatureResult;
+  }
+
+  private static class GenericSignatureContextEvaluator {
+
+    private final AppView<?> appView;
+    private final DexProgramClass context;
+    private final Set<String> classFormalTypeParameters = new HashSet<>();
+    private final Set<String> methodTypeArguments = new HashSet<>();
+    private final Mode mode;
+
+    public GenericSignatureContextEvaluator(
+        AppView<?> appView, DexProgramClass context, Mode mode) {
+      this.appView = appView;
+      this.context = context;
+      this.mode = mode;
+    }
+
+    private SignatureEvaluationResult evaluateClassSignature(ClassSignature classSignature) {
+      classSignature
+          .getFormalTypeParameters()
+          .forEach(param -> classFormalTypeParameters.add(param.name));
+      if (classSignature.hasNoSignature()) {
+        return VALID;
+      }
+      SignatureEvaluationResult signatureEvaluationResult =
+          evaluateFormalTypeParameters(classSignature.formalTypeParameters);
+      if (signatureEvaluationResult.isInvalid()) {
+        return signatureEvaluationResult;
+      }
+      if ((context.superType != appView.dexItemFactory().objectType
+              && context.superType != classSignature.superClassSignature().type())
+          || (context.superType == appView.dexItemFactory().objectType
+              && classSignature.superClassSignature().hasNoSignature())) {
+        assert mode.doNotVerify();
+        return INVALID_SUPER_TYPE;
+      }
+      signatureEvaluationResult =
+          evaluateTypeArgumentsAppliedToType(
+              classSignature.superClassSignature().typeArguments(), context.superType);
+      if (signatureEvaluationResult.isInvalid()) {
+        return signatureEvaluationResult;
+      }
+      List<ClassTypeSignature> superInterfaces = classSignature.superInterfaceSignatures();
+      if (context.interfaces.size() != superInterfaces.size()) {
+        assert mode.doNotVerify();
+        return INVALID_INTERFACE_COUNT;
+      }
+      DexType[] actualInterfaces = context.interfaces.values;
+      for (int i = 0; i < actualInterfaces.length; i++) {
+        signatureEvaluationResult =
+            evaluateTypeArgumentsAppliedToType(
+                superInterfaces.get(i).typeArguments(), actualInterfaces[i]);
+        if (signatureEvaluationResult.isInvalid()) {
+          return signatureEvaluationResult;
+        }
+      }
+      return VALID;
+    }
+
+    private SignatureEvaluationResult visitMethodSignature(MethodTypeSignature methodSignature) {
+      methodSignature
+          .getFormalTypeParameters()
+          .forEach(param -> methodTypeArguments.add(param.name));
+      SignatureEvaluationResult evaluateResult =
+          evaluateFormalTypeParameters(methodSignature.getFormalTypeParameters());
+      if (evaluateResult.isInvalid()) {
+        return evaluateResult;
+      }
+      evaluateResult = evaluateTypeArguments(methodSignature.typeSignatures);
+      if (evaluateResult.isInvalid()) {
+        return evaluateResult;
+      }
+      evaluateResult = evaluateTypeArguments(methodSignature.throwsSignatures);
+      if (evaluateResult.isInvalid()) {
+        return evaluateResult;
+      }
+      ReturnType returnType = methodSignature.returnType();
+      if (!returnType.isVoidDescriptor()) {
+        evaluateResult = evaluateTypeArgument(returnType.typeSignature());
+        if (evaluateResult.isInvalid()) {
+          return evaluateResult;
+        }
+      }
+      methodTypeArguments.clear();
+      return evaluateResult;
+    }
+
+    private SignatureEvaluationResult evaluateTypeArguments(List<TypeSignature> typeSignatures) {
+      for (TypeSignature typeSignature : typeSignatures) {
+        SignatureEvaluationResult signatureEvaluationResult = evaluateTypeArgument(typeSignature);
+        if (signatureEvaluationResult.isInvalid()) {
+          return signatureEvaluationResult;
+        }
+      }
+      return VALID;
+    }
+
+    private SignatureEvaluationResult visitFieldTypeSignature(FieldTypeSignature fieldSignature) {
+      return evaluateTypeArgument(fieldSignature);
+    }
+
+    private SignatureEvaluationResult evaluateFormalTypeParameters(
+        List<FormalTypeParameter> typeParameters) {
+      for (FormalTypeParameter typeParameter : typeParameters) {
+        SignatureEvaluationResult evaluationResult = evaluateTypeParameter(typeParameter);
+        if (evaluationResult.isInvalid()) {
+          return evaluationResult;
+        }
+      }
+      return VALID;
+    }
+
+    private SignatureEvaluationResult evaluateTypeParameter(FormalTypeParameter typeParameter) {
+      SignatureEvaluationResult evaluationResult = evaluateTypeArgument(typeParameter.classBound);
+      if (evaluationResult.isInvalid()) {
+        return evaluationResult;
+      }
+      if (typeParameter.interfaceBounds != null) {
+        for (FieldTypeSignature interfaceBound : typeParameter.interfaceBounds) {
+          evaluationResult = evaluateTypeArgument(interfaceBound);
+          if (evaluationResult != VALID) {
+            return evaluationResult;
+          }
+        }
+      }
+      return VALID;
+    }
+
+    private SignatureEvaluationResult evaluateTypeArgument(TypeSignature typeSignature) {
+      if (typeSignature.isBaseTypeSignature()) {
+        return VALID;
+      }
+      FieldTypeSignature fieldTypeSignature = typeSignature.asFieldTypeSignature();
+      if (fieldTypeSignature.hasNoSignature()) {
+        return VALID;
+      }
+      if (fieldTypeSignature.isTypeVariableSignature()) {
+        // This is in an applied position, just check that the variable is registered.
+        String typeVariable = fieldTypeSignature.asTypeVariableSignature().typeVariable();
+        if (classFormalTypeParameters.contains(typeVariable)
+            || methodTypeArguments.contains(typeVariable)) {
+          return VALID;
+        }
+        assert mode.doNotVerify();
+        return INVALID_TYPE_VARIABLE_UNDEFINED;
+      }
+      if (fieldTypeSignature.isArrayTypeSignature()) {
+        return evaluateTypeArgument(fieldTypeSignature.asArrayTypeSignature().elementSignature());
+      }
+      assert fieldTypeSignature.isClassTypeSignature();
+      return evaluateTypeArguments(fieldTypeSignature.asClassTypeSignature());
+    }
+
+    private SignatureEvaluationResult evaluateTypeArguments(ClassTypeSignature classTypeSignature) {
+      return evaluateTypeArgumentsAppliedToType(
+          classTypeSignature.typeArguments, classTypeSignature.type());
+    }
+
+    private SignatureEvaluationResult evaluateTypeArgumentsAppliedToType(
+        List<FieldTypeSignature> typeArguments, DexType type) {
+      for (FieldTypeSignature typeArgument : typeArguments) {
+        SignatureEvaluationResult evaluationResult = evaluateTypeArgument(typeArgument);
+        if (evaluationResult.isInvalid()) {
+          assert mode.doNotVerify();
+          return evaluationResult;
+        }
+      }
+      DexClass clazz = appView.definitionFor(type);
+      if (clazz == null) {
+        // We do not know if the application of arguments works or not.
+        return VALID;
+      }
+      if (typeArguments.size() != clazz.classSignature.getFormalTypeParameters().size()) {
+        assert mode.doNotVerify();
+        return INVALID_APPLICATION_COUNT;
+      }
+      return VALID;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
index f68675f..a670da0 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
@@ -148,7 +148,7 @@
     } else {
       assert fieldTypeSignature.isClassTypeSignature();
       ClassTypeSignature classTypeSignature = fieldTypeSignature.asClassTypeSignature();
-      if (classTypeSignature.isNoSignature()) {
+      if (classTypeSignature.hasNoSignature()) {
         return;
       }
       String renamedString = namingLens.lookupDescriptor(classTypeSignature.type).toString();
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 fd77b73..d20833f 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
@@ -57,14 +57,14 @@
   }
 
   public ClassSignature rewrite(ClassSignature classSignature) {
-    if (classSignature.hasNoSignature()) {
+    if (classSignature.hasNoSignature() || classSignature.isInvalid()) {
       return classSignature;
     }
     return new ClassSignatureRewriter().run(classSignature);
   }
 
   public FieldTypeSignature rewrite(FieldTypeSignature fieldTypeSignature) {
-    if (fieldTypeSignature.hasNoSignature()) {
+    if (fieldTypeSignature.hasNoSignature() || fieldTypeSignature.isInvalid()) {
       return fieldTypeSignature;
     }
     FieldTypeSignature rewrittenSignature = new TypeSignatureRewriter().run(fieldTypeSignature);
@@ -72,7 +72,7 @@
   }
 
   public MethodTypeSignature rewrite(MethodTypeSignature methodTypeSignature) {
-    if (methodTypeSignature.hasNoSignature()) {
+    if (methodTypeSignature.hasNoSignature() || methodTypeSignature.isInvalid()) {
       return methodTypeSignature;
     }
     return new MethodTypeSignatureRewriter().run(methodTypeSignature);
@@ -117,7 +117,7 @@
       classSignature.visit(this);
       if (rewrittenTypeParameters.isEmpty()
           && rewrittenSuperInterfaces.isEmpty()
-          && rewrittenSuperClass.isNoSignature()
+          && rewrittenSuperClass.hasNoSignature()
           && rewrittenSuperClass.type == factory.objectType) {
         return ClassSignature.noSignature();
       }
@@ -255,7 +255,7 @@
       }
       assert fieldTypeSignature.isClassTypeSignature();
       ClassTypeSignature classTypeSignature = fieldTypeSignature.asClassTypeSignature();
-      if (classTypeSignature.isNoSignature()) {
+      if (classTypeSignature.hasNoSignature()) {
         return classTypeSignature;
       }
       return new ClassTypeSignatureRewriter(false).run(classTypeSignature);
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureCorrectnessHelperTests.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureCorrectnessHelperTests.java
new file mode 100644
index 0000000..db76bf9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureCorrectnessHelperTests.java
@@ -0,0 +1,244 @@
+// Copyright (c) 2021, 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GenericSignatureCorrectnessHelper;
+import com.android.tools.r8.graph.GenericSignatureCorrectnessHelper.SignatureEvaluationResult;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class GenericSignatureCorrectnessHelperTests extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public GenericSignatureCorrectnessHelperTests(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testAllValid() throws Exception {
+    AppView<AppInfoWithClassHierarchy> appView =
+        computeAppViewWithClassHierachy(
+            buildInnerClasses(GenericSignatureCorrectnessHelperTests.class)
+                .addLibraryFile(ToolHelper.getJava8RuntimeJar())
+                .build());
+    GenericSignatureCorrectnessHelper check =
+        GenericSignatureCorrectnessHelper.createForVerification(appView);
+    check.run();
+  }
+
+  @Test
+  public void testMissingTypeArgumentInClassBound() throws Exception {
+    runTest(
+        ImmutableList.of(Base.class),
+        ImmutableList.of(
+            transformer(ClassWithClassBound.class)
+                .setGenericSignature(
+                    existing -> {
+                      // Replace the generic type parameter T with R.
+                      return existing.replace("<T:", "<R:");
+                    })
+                .transform()),
+        ClassWithClassBound.class,
+        SignatureEvaluationResult.INVALID_TYPE_VARIABLE_UNDEFINED);
+  }
+
+  @Test
+  public void testMissingTypeArgumentInInterfaceBound() throws Exception {
+    runTest(
+        ImmutableList.of(I.class, J.class),
+        ImmutableList.of(
+            transformer(ClassWithInterfaceBound.class)
+                .setGenericSignature(
+                    existing -> {
+                      // Replace the generic type parameter T with R.
+                      return existing.replace("<T:", "<R:");
+                    })
+                .transform()),
+        ClassWithInterfaceBound.class,
+        SignatureEvaluationResult.INVALID_TYPE_VARIABLE_UNDEFINED);
+  }
+
+  @Test
+  public void testMembersHavingInvalidTypeReference() throws Exception {
+    runTest(
+        ImmutableList.of(),
+        ImmutableList.of(
+            transformer(ClassWithMembersHavingInvalidTypeReference.class)
+                .setGenericSignature(
+                    existing -> {
+                      // Replace the generic type parameter T with R.
+                      return existing.replace("<T:", "<R:");
+                    })
+                .transform()),
+        ClassWithMembersHavingInvalidTypeReference.class,
+        SignatureEvaluationResult.INVALID_TYPE_VARIABLE_UNDEFINED);
+  }
+
+  @Test
+  public void testMethodHavingInvalidTypeReferences() throws Exception {
+    runTest(
+        ImmutableList.of(),
+        ImmutableList.of(
+            transformer(ClassWithMethodMissingTypeParameters.class)
+                .setGenericSignature(
+                    MethodPredicate.onName("test"),
+                    existing -> {
+                      // Replace the generic type parameter T with R.
+                      return existing.replace("<T:", "<R:");
+                    })
+                .transform()),
+        ClassWithMethodMissingTypeParameters.class,
+        SignatureEvaluationResult.INVALID_TYPE_VARIABLE_UNDEFINED);
+  }
+
+  @Test
+  public void testIncorrectNumberOfSuperInterfaces() throws Exception {
+    runTest(
+        ImmutableList.of(),
+        ImmutableList.of(
+            transformer(ClassWithInvalidNumberOfSuperInterfaces.class)
+                .setImplements(I.class)
+                .transform()),
+        ClassWithInvalidNumberOfSuperInterfaces.class,
+        SignatureEvaluationResult.INVALID_INTERFACE_COUNT);
+  }
+
+  @Test
+  public void testMissingArgument() throws Exception {
+    runTest(
+        ImmutableList.of(J.class),
+        ImmutableList.of(
+            transformer(ClassWithInvalidArgumentCount.class)
+                .setGenericSignature(
+                    existing -> {
+                      // Replace the generic type argument <TT;> with nothing
+                      return existing.replace("<TT;>", "");
+                    })
+                .transform()),
+        ClassWithInvalidArgumentCount.class,
+        SignatureEvaluationResult.INVALID_APPLICATION_COUNT);
+  }
+
+  @Test
+  public void testTooManyArguments() throws Exception {
+    runTest(
+        ImmutableList.of(J.class),
+        ImmutableList.of(
+            transformer(ClassWithInvalidArgumentCount.class)
+                .setGenericSignature(
+                    existing -> {
+                      // Replace the generic type argument <TT;> with nothing
+                      return existing.replace("<TT;>", "<TT;TT;>");
+                    })
+                .transform()),
+        ClassWithInvalidArgumentCount.class,
+        SignatureEvaluationResult.INVALID_APPLICATION_COUNT);
+  }
+
+  @Test
+  public void testClassWithInvalidSuperType() throws Exception {
+    runTest(
+        ImmutableList.of(Base.class, OtherBase.class),
+        ImmutableList.of(
+            transformer(ClassWithInvalidSuperType.class)
+                .setSuper(DescriptorUtils.javaTypeToDescriptor(OtherBase.class.getTypeName()))
+                .transform()),
+        ClassWithInvalidSuperType.class,
+        SignatureEvaluationResult.INVALID_SUPER_TYPE);
+  }
+
+  private void runTest(
+      List<Class<?>> classes,
+      List<byte[]> transformations,
+      Class<?> classToVerify,
+      SignatureEvaluationResult expected)
+      throws Exception {
+    AppView<AppInfoWithClassHierarchy> appView =
+        computeAppViewWithClassHierachy(
+            buildClasses(classes)
+                .addClassProgramData(transformations)
+                .addLibraryFile(ToolHelper.getJava8RuntimeJar())
+                .build());
+    GenericSignatureCorrectnessHelper check =
+        GenericSignatureCorrectnessHelper.createForInitialCheck(appView);
+    DexProgramClass clazz =
+        appView
+            .definitionFor(
+                appView
+                    .dexItemFactory()
+                    .createType(DescriptorUtils.javaTypeToDescriptor(classToVerify.getTypeName())))
+            .asProgramClass();
+    assertNotNull(clazz);
+    assertEquals(expected, check.evaluateSignaturesForClass(clazz));
+  }
+
+  public interface I {}
+
+  public interface J<T> {
+    <R extends Object & I & J<Integer>> R foo(T foo) throws CustomException;
+  }
+
+  public static class Base<T> {}
+
+  public static class CustomException extends Exception {}
+
+  public static class Empty {}
+
+  public static class ClassWithClassBound<T extends Base<T /* R */>> {}
+
+  public static class ClassWithInterfaceBound<T extends I & J<T /* R */>> {}
+
+  public abstract static class ClassWithMembersHavingInvalidTypeReference<T /* R */> {
+
+    T t;
+
+    public abstract T testReturn();
+
+    public abstract void testParameter(T t);
+  }
+
+  public abstract static class ClassOverridingTypeArgument<T> {
+
+    public abstract <T> T test();
+  }
+
+  public abstract static class ClassWithMethodMissingTypeParameters {
+
+    public abstract <T /* R */> T test(T foo);
+  }
+
+  public abstract static class ClassWithInvalidNumberOfSuperInterfaces<T>
+      implements I, J<T> /* I */ {}
+
+  public abstract static class ClassWithInvalidArgumentCount<T>
+      implements J<T> /* J and J<T,T> */ {}
+
+  public static class OtherBase<T> {}
+
+  public abstract static class ClassWithInvalidSuperType<T> extends Base<T> /* OtherBase<T> */ {}
+}
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 2b341aa..f828cfc 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -31,6 +31,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.ClassReader;
@@ -331,6 +332,10 @@
   }
 
   public ClassFileTransformer setGenericSignature(String newGenericSignature) {
+    return setGenericSignature(signature -> newGenericSignature);
+  }
+
+  public ClassFileTransformer setGenericSignature(Function<String, String> newGenericSignature) {
     return addClassTransformer(
         new ClassTransformer() {
           @Override
@@ -341,7 +346,8 @@
               String signature,
               String superName,
               String[] interfaces) {
-            super.visit(version, access, name, newGenericSignature, superName, interfaces);
+            super.visit(
+                version, access, name, newGenericSignature.apply(signature), superName, interfaces);
           }
         });
   }
@@ -587,6 +593,21 @@
         });
   }
 
+  public ClassFileTransformer setGenericSignature(
+      MethodPredicate predicate, Function<String, String> newSignature) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public MethodVisitor visitMethod(
+              int access, String name, String descriptor, String signature, String[] exceptions) {
+            return predicate.test(access, name, descriptor, signature, exceptions)
+                ? super.visitMethod(
+                    access, name, descriptor, newSignature.apply(signature), exceptions)
+                : super.visitMethod(access, name, descriptor, signature, exceptions);
+          }
+        });
+  }
+
   public ClassFileTransformer removeFields(FieldPredicate predicate) {
     return addClassTransformer(
         new ClassTransformer() {