Version 1.4.73

Cherry pick: Relax quote requirement for -flattenpackagehierarchy and -repackageclasses
CL: https://r8-review.googlesource.com/c/r8/+/35882

Cherry pick: Resolve ambiguity in Proguard configuration parser
CL: https://r8-review.googlesource.com/c/r8/+/35884

Cherry pick: Add a test for include directive after repackaging directive
CL: https://r8-review.googlesource.com/c/r8/+/35887

Cherry pick: Fix "unknown error parsing signature".
CL: https://r8-review.googlesource.com/c/r8/+/34623

Bug: 128468335, 111247743, 117102022, 117201271, 120432751, 121052026, 126502419
Change-Id: I511eaa5e66d2d9d68621e5e1e58f4bb6815c3cb6
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 37f1791..a59fb57 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -272,6 +272,7 @@
       RootSet rootSet;
       String proguardSeedsData = null;
       timing.begin("Strip unused code");
+      Set<DexType> classesToRetainInnerClassAttributeFor = null;
       try {
         Set<DexType> missingClasses = appView.appInfo().getMissingClasses();
         missingClasses = filterMissingClasses(
@@ -348,7 +349,14 @@
           new AbstractMethodRemover(appView.appInfo().withLiveness()).run();
         }
 
-        new AnnotationRemover(appView.appInfo().withLiveness(), appView.graphLense(), options)
+        classesToRetainInnerClassAttributeFor =
+            AnnotationRemover.computeClassesToRetainInnerClassAttributeFor(
+                appView.appInfo().withLiveness(), options);
+        new AnnotationRemover(
+                appView.appInfo().withLiveness(),
+                appView.graphLense(),
+                options,
+                classesToRetainInnerClassAttributeFor)
             .ensureValid(compatibility)
             .run();
 
@@ -605,7 +613,12 @@
               }
             }
             // Remove annotations that refer to types that no longer exist.
-            new AnnotationRemover(appView.appInfo().withLiveness(), appView.graphLense(), options)
+            assert classesToRetainInnerClassAttributeFor != null;
+            new AnnotationRemover(
+                    appView.appInfo().withLiveness(),
+                    appView.graphLense(),
+                    options,
+                    classesToRetainInnerClassAttributeFor)
                 .run();
             if (!mainDexClasses.isEmpty()) {
               // Remove types that no longer exists from the computed main dex list.
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 574be8d..57992d1 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.4.72";
+  public static final String LABEL = "1.4.73";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index fd8508b..20f4bc2 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -188,8 +188,16 @@
         // Pick the renamed inner class from the fully renamed binary name.
         String fullRenamedBinaryName =
             getClassBinaryNameFromDescriptor(renamedDescriptor.toString());
-        renamedSignature.append(
-            fullRenamedBinaryName.substring(enclosingRenamedBinaryName.length() + 1));
+        int innerClassPos = enclosingRenamedBinaryName.length() + 1;
+        if (innerClassPos < fullRenamedBinaryName.length()) {
+          renamedSignature.append(fullRenamedBinaryName.substring(innerClassPos));
+        } else {
+          reporter.warning(
+              new StringDiagnostic(
+                  "Should have retained InnerClasses attribute of " + type + ".",
+                  appView.appInfo().originFor(type)));
+          renamedSignature.append(name);
+        }
       } else {
         // Did not find the class - keep the inner class name as is.
         // TODO(110085899): Warn about missing classes in signatures?
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 3fa9db5..25e0832 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
@@ -18,7 +19,12 @@
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 public class AnnotationRemover {
 
@@ -26,12 +32,18 @@
   private final GraphLense lense;
   private final ProguardKeepAttributes keep;
   private final InternalOptions options;
+  private final Set<DexType> classesToRetainInnerClassAttributeFor;
 
-  public AnnotationRemover(AppInfoWithLiveness appInfo, GraphLense lense, InternalOptions options) {
+  public AnnotationRemover(
+      AppInfoWithLiveness appInfo,
+      GraphLense lense,
+      InternalOptions options,
+      Set<DexType> classesToRetainInnerClassAttributeFor) {
     this.appInfo = appInfo;
     this.lense = lense;
     this.keep = options.getProguardConfiguration().getKeepAttributes();
     this.options = options;
+    this.classesToRetainInnerClassAttributeFor = classesToRetainInnerClassAttributeFor;
   }
 
   /**
@@ -140,6 +152,79 @@
     return this;
   }
 
+  private static boolean hasGenericEnclosingClass(
+      DexProgramClass clazz,
+      Map<DexType, DexProgramClass> enclosingClasses,
+      Set<DexProgramClass> genericClasses) {
+    while (true) {
+      DexProgramClass enclosingClass = enclosingClasses.get(clazz.type);
+      if (enclosingClass == null) {
+        return false;
+      }
+      if (genericClasses.contains(enclosingClass)) {
+        return true;
+      }
+      clazz = enclosingClass;
+    }
+  }
+
+  private static boolean hasSignatureAnnotation(DexProgramClass clazz, DexItemFactory itemFactory) {
+    for (DexAnnotation annotation : clazz.annotations.annotations) {
+      if (DexAnnotation.isSignatureAnnotation(annotation, itemFactory)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public static Set<DexType> computeClassesToRetainInnerClassAttributeFor(
+      AppInfoWithLiveness appInfo, InternalOptions options) {
+    // In case of minification for certain inner classes we need to retain their InnerClass
+    // attributes because their minified name still needs to be in hierarchical format
+    // (enclosing$inner) otherwise the GenericSignatureRewriter can't produce the correct,
+    // renamed signature.
+
+    // More precisely:
+    // - we're going to retain the InnerClass attribute that refers to the same class as 'inner'
+    // - for live, inner, nonstatic classes
+    // - that are enclosed by a class with a generic signature.
+
+    // In compat mode we always keep all InnerClass attributes (if requested).
+    // If not requested we never keep any. In these cases don't compute eligible classes.
+    if (options.forceProguardCompatibility
+        || !options.getProguardConfiguration().getKeepAttributes().innerClasses) {
+      return Collections.emptySet();
+    }
+
+    // Build lookup table and set of the interesting classes.
+    // enclosingClasses.get(clazz) gives the enclosing class of 'clazz'
+    Map<DexType, DexProgramClass> enclosingClasses = new IdentityHashMap<>();
+    Set<DexProgramClass> genericClasses = Sets.newIdentityHashSet();
+
+    Iterable<DexProgramClass> programClasses = appInfo.classes();
+    for (DexProgramClass clazz : programClasses) {
+      if (hasSignatureAnnotation(clazz, options.itemFactory)) {
+        genericClasses.add(clazz);
+      }
+      for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
+        if ((innerClassAttribute.getAccess() & Constants.ACC_STATIC) == 0
+            && innerClassAttribute.getOuter() == clazz.type) {
+          enclosingClasses.put(innerClassAttribute.getInner(), clazz);
+        }
+      }
+    }
+
+    Set<DexType> result = Sets.newIdentityHashSet();
+    for (DexProgramClass clazz : programClasses) {
+      if (clazz.getInnerClassAttributeForThisClass() != null
+          && appInfo.liveTypes.contains(clazz.type)
+          && hasGenericEnclosingClass(clazz, enclosingClasses, genericClasses)) {
+        result.add(clazz.type);
+      }
+    }
+    return result;
+  }
+
   public void run() {
     for (DexProgramClass clazz : appInfo.classes()) {
       stripAttributes(clazz);
@@ -208,6 +293,15 @@
     return false;
   }
 
+  private static boolean hasInnerClassesFromSet(DexProgramClass clazz, Set<DexType> innerClasses) {
+    for (InnerClassAttribute attr : clazz.getInnerClasses()) {
+      if (attr.getOuter() == clazz.type && innerClasses.contains(attr.getInner())) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   private void stripAttributes(DexProgramClass clazz) {
     // If [clazz] is mentioned by a keep rule, it could be used for reflection, and we therefore
     // need to keep the enclosing method and inner classes attributes, if requested. In Proguard
@@ -215,15 +309,40 @@
     // To ensure reflection from both inner to outer and and outer to inner for kept classes - even
     // if only one side is kept - keep the attributes is any class mentioned in these attributes
     // is kept.
-    if (appInfo.isPinned(clazz.type)
-        || enclosingMethodPinned(clazz)
-        || innerClassPinned(clazz)
-        || options.forceProguardCompatibility) {
+    boolean keptAnyway =
+        appInfo.isPinned(clazz.type)
+            || enclosingMethodPinned(clazz)
+            || innerClassPinned(clazz)
+            || options.forceProguardCompatibility;
+    boolean keepForThisInnerClass = false;
+    boolean keepForThisEnclosingClass = false;
+    if (!keptAnyway) {
+      keepForThisInnerClass = classesToRetainInnerClassAttributeFor.contains(clazz.type);
+      keepForThisEnclosingClass =
+          hasInnerClassesFromSet(clazz, classesToRetainInnerClassAttributeFor);
+    }
+    if (keptAnyway || keepForThisInnerClass || keepForThisEnclosingClass) {
       if (!keep.enclosingMethod) {
         clazz.clearEnclosingMethod();
       }
       if (!keep.innerClasses) {
         clazz.clearInnerClasses();
+      } else if (!keptAnyway) {
+        // We're keeping this only because of classesToRetainInnerClassAttributeFor.
+        final boolean finalKeepForThisInnerClass = keepForThisInnerClass;
+        final boolean finalKeepForThisEnclosingClass = keepForThisEnclosingClass;
+        clazz.removeInnerClasses(
+            ica -> {
+              if (finalKeepForThisInnerClass && ica.getInner() == clazz.type) {
+                return false;
+              }
+              if (finalKeepForThisEnclosingClass
+                  && ica.getOuter() == clazz.type
+                  && classesToRetainInnerClassAttributeFor.contains(ica.getInner())) {
+                return false;
+              }
+              return true;
+            });
       }
     } else {
       // These attributes are only relevant for reflection, and this class is not used for
@@ -232,5 +351,4 @@
       clazz.clearInnerClasses();
     }
   }
-
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 5fa6d1c..0291692 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -177,7 +177,7 @@
     reporter.failIfPendingErrors();
   }
 
-  private static enum IdentifierType {
+  private enum IdentifierType {
     CLASS_NAME,
     ANY
   }
@@ -292,7 +292,11 @@
           configurationBuilder.setPackagePrefix(parsePackageNameOrEmptyString());
           expectClosingQuote(quote);
         } else {
-          configurationBuilder.setPackagePrefix("");
+          if (hasNextChar('-')) {
+            configurationBuilder.setPackagePrefix("");
+          } else {
+            configurationBuilder.setPackagePrefix(parsePackageNameOrEmptyString());
+          }
         }
       } else if (acceptString("flattenpackagehierarchy")) {
         if (configurationBuilder.getPackageObfuscationMode() == PackageObfuscationMode.REPACKAGE) {
@@ -308,7 +312,11 @@
             configurationBuilder.setFlattenPackagePrefix(parsePackageNameOrEmptyString());
             expectClosingQuote(quote);
           } else {
-            configurationBuilder.setFlattenPackagePrefix("");
+            if (hasNextChar('-')) {
+              configurationBuilder.setFlattenPackagePrefix("");
+            } else {
+              configurationBuilder.setFlattenPackagePrefix(parsePackageNameOrEmptyString());
+            }
           }
         }
       } else if (acceptString("overloadaggressively")) {
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 30159d3..da4cfbc 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -10,6 +10,7 @@
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.concurrent.ExecutionException;
 
 public abstract class TestBuilder<RR extends TestRunResult, T extends TestBuilder<RR, T>> {
 
@@ -35,9 +36,10 @@
   }
 
   public abstract RR run(String mainClass)
-      throws IOException, CompilationFailedException;
+      throws CompilationFailedException, ExecutionException, IOException;
 
-  public RR run(Class mainClass) throws IOException, CompilationFailedException {
+  public RR run(Class mainClass)
+      throws CompilationFailedException, ExecutionException, IOException {
     return run(mainClass.getTypeName());
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index d74fab5..a785de3 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -4,6 +4,8 @@
 package com.android.tools.r8;
 
 import static com.android.tools.r8.TestBase.Backend.DEX;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.ToolHelper.DexVm;
@@ -13,6 +15,7 @@
 import com.android.tools.r8.debug.DexDebugTestConfig;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -43,16 +46,18 @@
 
   protected abstract RR createRunResult(ProcessResult result);
 
-  public RR run(Class<?> mainClass) throws IOException {
+  public RR run(Class<?> mainClass) throws ExecutionException, IOException {
     return run(mainClass.getTypeName());
   }
 
-  public RR run(String mainClass) throws IOException {
+  public RR run(String mainClass) throws ExecutionException, IOException {
+    ClassSubject mainClassSubject = inspector().clazz(mainClass);
+    assertThat(mainClassSubject, isPresent());
     switch (getBackend()) {
       case DEX:
-        return runArt(additionalRunClassPath, mainClass);
+        return runArt(additionalRunClassPath, mainClassSubject.getFinalName());
       case CF:
-        return runJava(additionalRunClassPath, mainClass);
+        return runJava(additionalRunClassPath, mainClassSubject.getFinalName());
       default:
         throw new Unreachable();
     }
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 9e8f74a..a6afe52 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -14,6 +14,7 @@
 import java.io.PrintStream;
 import java.nio.file.Path;
 import java.util.Collection;
+import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
@@ -85,7 +86,8 @@
   }
 
   @Override
-  public RR run(String mainClass) throws IOException, CompilationFailedException {
+  public RR run(String mainClass)
+      throws CompilationFailedException, ExecutionException, IOException {
     return compile().run(mainClass);
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index cbf8e81..217463e 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -18,7 +18,7 @@
         B extends BaseCompilerCommand.Builder<C, B>,
         CR extends TestCompileResult<CR, RR>,
         RR extends TestRunResult,
-        T extends TestCompilerBuilder<C, B, CR, RR, T>>
+        T extends TestShrinkerBuilder<C, B, CR, RR, T>>
     extends TestCompilerBuilder<C, B, CR, RR, T> {
 
   protected boolean enableMinification = true;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ExtraMethodNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ExtraMethodNullTest.java
index 42b0ce4..3ece2da 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ExtraMethodNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ExtraMethodNullTest.java
@@ -6,16 +6,14 @@
 
 import static org.hamcrest.CoreMatchers.containsString;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
-import java.io.IOException;
 import org.junit.Test;
 
 public class ExtraMethodNullTest extends TestBase {
 
   @Test
-  public void test() throws IOException, CompilationFailedException {
+  public void test() throws Exception {
     testForR8(Backend.DEX)
         .addProgramClassesAndInnerClasses(One.class)
         .addKeepMainRule(One.class)
diff --git a/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java b/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java
new file mode 100644
index 0000000..9ade2f9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java
@@ -0,0 +1,174 @@
+// Copyright (c) 2019, 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.naming.signature;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import org.junit.Test;
+
+public class GenericSignatureRenamingTest extends TestBase {
+
+  @Test
+  public void testJVM() throws Exception {
+    testForJvm().addTestClasspath().run(Main.class).assertSuccess();
+  }
+
+  @Test
+  public void testR8Dex() throws Exception {
+    test(testForR8(Backend.DEX));
+  }
+
+  @Test
+  public void testR8CompatDex() throws Exception {
+    test(testForR8Compat(Backend.DEX));
+  }
+
+  @Test
+  public void testR8DexNoMinify() throws Exception {
+    test(testForR8(Backend.DEX).addKeepRules("-dontobfuscate"));
+  }
+
+  @Test
+  public void testR8Cf() throws Exception {
+    test(testForR8(Backend.CF));
+  }
+
+  @Test
+  public void testR8CfNoMinify() throws Exception {
+    test(testForR8(Backend.CF).addKeepRules("-dontobfuscate"));
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8()
+        .addProgramClasses(Main.class)
+        .addProgramClassesAndInnerClasses(A.class, B.class, CY.class, CYY.class)
+        .setMode(CompilationMode.RELEASE)
+        .compile()
+        .assertNoMessages()
+        .run(Main.class)
+        .assertSuccess();
+  }
+
+  private void test(R8TestBuilder builder) throws Exception {
+    builder
+        .addKeepRules("-dontoptimize")
+        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod,Signature")
+        .addKeepMainRule(Main.class)
+        .addProgramClasses(Main.class)
+        .addProgramClassesAndInnerClasses(A.class, B.class, CY.class, CYY.class)
+        .setMode(CompilationMode.RELEASE)
+        .compile()
+        .assertNoMessages()
+        .run(Main.class)
+        .assertSuccess();
+  }
+}
+
+class A<T> {
+  class Y {
+
+    class YY {}
+
+    class ZZ extends YY {
+      public YY yy;
+
+      YY newYY() {
+        return new YY();
+      }
+    }
+
+    ZZ zz() {
+      return new ZZ();
+    }
+  }
+
+  class Z extends Y {}
+
+  static class S {}
+
+  Y newY() {
+    return new Y();
+  }
+
+  Z newZ() {
+    return new Z();
+  }
+
+  Y.ZZ newZZ() {
+    return new Y().zz();
+  }
+}
+
+class B<T extends A<T>> {}
+
+class CY<T extends A<T>.Y> {}
+
+class CYY<T extends A<T>.Y.YY> {}
+
+class Main {
+
+  private static void check(boolean b, String message) {
+    if (!b) {
+      throw new RuntimeException("Check failed: " + message);
+    }
+  }
+
+  public static void main(String[] args) {
+    A.Z z = new A().newZ();
+    A.Y.YY yy = new A().newZZ().yy;
+
+    B b = new B();
+    CY cy = new CY();
+
+    CYY cyy = new CYY();
+    A.S s = new A.S();
+
+    // Check if names of Z and ZZ shows A as a superclass.
+    Class classA = new A().getClass();
+    String nameA = classA.getName();
+
+    TypeVariable[] v = classA.getTypeParameters();
+    check(v != null && v.length == 1, classA + " expected to have 1 type parameter.");
+
+    Class classZ = new A().newZ().getClass();
+    String nameZ = classZ.getName();
+    check(nameZ.startsWith(nameA + "$"), nameZ + " expected to start with " + nameA + "$.");
+
+    Class classZZ = new A().newZZ().getClass();
+    String nameZZ = classZZ.getName();
+    check(nameZZ.startsWith(nameA + "$"), nameZZ + " expected to start with " + nameA + "$.");
+
+    // Check that the owner of the superclass of Z is A.
+    Class ownerClassOfSuperOfZ = getEnclosingClass(classZ.getGenericSuperclass());
+
+    check(
+        ownerClassOfSuperOfZ == A.class,
+        ownerClassOfSuperOfZ + " expected to be equal to " + A.class);
+
+    // Check that the owner-owner of the superclass of Z is A.
+    Class ownerOfownerOfSuperOfZZ =
+        getEnclosingClass(classZZ.getGenericSuperclass()).getEnclosingClass();
+
+    check(
+        ownerOfownerOfSuperOfZZ == A.class,
+        ownerOfownerOfSuperOfZZ + " expected to be equal to " + A.class);
+  }
+
+  private static Class getEnclosingClass(Type type) {
+    if (type instanceof ParameterizedType) {
+      // On the JVM it's a ParameterizedType.
+      return (Class) ((ParameterizedType) ((ParameterizedType) type).getOwnerType()).getRawType();
+    } else {
+      // On the ART it's Class.
+      check(type instanceof Class, type + " expected to be a ParameterizedType or Class.");
+      return ((Class) type).getEnclosingClass();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/proguard/configuration/RepackagingCompatibilityTest.java b/src/test/java/com/android/tools/r8/proguard/configuration/RepackagingCompatibilityTest.java
new file mode 100644
index 0000000..afaf999
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/proguard/configuration/RepackagingCompatibilityTest.java
@@ -0,0 +1,135 @@
+// Copyright (c) 2019, 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.proguard.configuration;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 RepackagingCompatibilityTest extends TestBase {
+
+  private enum Quote {
+    SINGLE,
+    DOUBLE,
+    NONE
+  }
+
+  private static final String expectedOutput = StringUtils.lines("Hello world!");
+  private static final Class<?> mainClass = RepackagingCompatabilityTestClass.class;
+
+  private final String directive;
+  private final Quote quote;
+  private final boolean repackageToRoot;
+
+  @Parameters(name = "Directive: {0}, quote: {1}, repackage to root: {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of("-flattenpackagehierarchy", "-repackageclasses"),
+        Quote.values(),
+        BooleanUtils.values());
+  }
+
+  public RepackagingCompatibilityTest(String directive, Quote quote, boolean repackageToRoot) {
+    this.directive = directive;
+    this.quote = quote;
+    this.repackageToRoot = repackageToRoot;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    runTest(testForR8(Backend.DEX), "R8");
+  }
+
+  @Test
+  public void testProguard() throws Exception {
+    runTest(testForProguard(), "Proguard");
+  }
+
+  private void runTest(TestShrinkerBuilder<?, ?, ?, ?, ?> builder, String shrinker)
+      throws Exception {
+    assumeFalse(
+        String.format(
+            "Only repackage to root when there are no quotes"
+                + " (repackageToRoot: %s, quote: %s, shrinker: %s)",
+            repackageToRoot, quote, shrinker),
+        repackageToRoot && quote != Quote.NONE);
+
+    TestRunResult<?> result =
+        builder
+            .addProgramClasses(mainClass)
+            .addKeepRules(getKeepRules())
+            .run(mainClass)
+            .assertSuccessWithOutput(expectedOutput);
+
+    ClassSubject testClassSubject = result.inspector().clazz(mainClass);
+    assertThat(testClassSubject, isPresent());
+    if (repackageToRoot) {
+      if (directive.equals("-flattenpackagehierarchy")) {
+        assertThat(testClassSubject.getFinalName(), startsWith("a."));
+      } else if (directive.equals("-repackageclasses")) {
+        assertThat(testClassSubject.getFinalName(), not(containsString(".")));
+      } else {
+        fail();
+      }
+    } else {
+      assertThat(testClassSubject.getFinalName(), startsWith("greeter."));
+    }
+  }
+
+  private List<String> getKeepRules() {
+    return ImmutableList.of(
+        // Keep main(), but allow obfuscation
+        "-keep,allowobfuscation class " + mainClass.getTypeName() + " {",
+        "  public static void main(...);",
+        "}",
+        // Ensure main() is not renamed
+        "-keepclassmembernames class " + mainClass.getTypeName() + " {",
+        "  public static void main(...);",
+        "}",
+        getRepackagingRule());
+  }
+
+  private String getRepackagingRule() {
+    if (repackageToRoot) {
+      return directive;
+    }
+    switch (quote) {
+      case SINGLE:
+        return directive + " 'greeter'";
+      case DOUBLE:
+        return directive + " \"greeter\"";
+      case NONE:
+        return directive + " greeter";
+      default:
+        throw new Unreachable();
+    }
+  }
+}
+
+class RepackagingCompatabilityTestClass {
+
+  public static void main(String[] args) {
+    System.out.println("Hello world!");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 2171cc4..c5fc045 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -1314,7 +1314,8 @@
       parser.parse(createConfigurationForTesting(ImmutableList.of("-keepattributes xxx,")));
       fail();
     } catch (AbortException e) {
-      assertTrue(handler.errors.get(0).getDiagnosticMessage().contains("Expected list element at "));
+      assertTrue(
+          handler.errors.get(0).getDiagnosticMessage().contains("Expected list element at "));
     }
   }
 
@@ -2438,4 +2439,57 @@
     assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS.toString(), rule.typeString());
     assertEquals("a.b.c.**,!**d,!**e,!**f,g,h,i", rule.getClassNames().toString());
   }
+
+  @Test
+  public void directiveAfterRepackagingRuleTest() {
+    List<PackageObfuscationMode> packageObfuscationModes =
+        ImmutableList.of(PackageObfuscationMode.FLATTEN, PackageObfuscationMode.REPACKAGE);
+    for (PackageObfuscationMode packageObfuscationMode : packageObfuscationModes) {
+      String directive =
+          packageObfuscationMode == PackageObfuscationMode.FLATTEN
+              ? "-flattenpackagehierarchy"
+              : "-repackageclasses";
+      ProguardConfigurationParser parser =
+          new ProguardConfigurationParser(new DexItemFactory(), reporter);
+      parser.parse(createConfigurationForTesting(ImmutableList.of(directive + " -keep class *")));
+      ProguardConfiguration configuration = parser.getConfig();
+      assertEquals(packageObfuscationMode, configuration.getPackageObfuscationMode());
+      assertEquals("", configuration.getPackagePrefix());
+
+      List<ProguardConfigurationRule> rules = configuration.getRules();
+      assertEquals(1, rules.size());
+
+      ProguardConfigurationRule rule = rules.get(0);
+      assertEquals(ProguardKeepRuleType.KEEP.toString(), rule.typeString());
+      assertEquals("*", rule.getClassNames().toString());
+    }
+  }
+
+  @Test
+  public void arobaseAfterRepackagingRuleTest() throws IOException {
+    Path includeFile = writeTextToTempFile("-keep class *");
+    List<PackageObfuscationMode> packageObfuscationModes =
+        ImmutableList.of(PackageObfuscationMode.FLATTEN, PackageObfuscationMode.REPACKAGE);
+    for (PackageObfuscationMode packageObfuscationMode : packageObfuscationModes) {
+      String directive =
+          packageObfuscationMode == PackageObfuscationMode.FLATTEN
+              ? "-flattenpackagehierarchy"
+              : "-repackageclasses";
+      ProguardConfigurationParser parser =
+          new ProguardConfigurationParser(new DexItemFactory(), reporter);
+      parser.parse(
+          createConfigurationForTesting(
+              ImmutableList.of(directive + " @" + includeFile.toAbsolutePath())));
+      ProguardConfiguration configuration = parser.getConfig();
+      assertEquals(packageObfuscationMode, configuration.getPackageObfuscationMode());
+      assertEquals("", configuration.getPackagePrefix());
+
+      List<ProguardConfigurationRule> rules = configuration.getRules();
+      assertEquals(1, rules.size());
+
+      ProguardConfigurationRule rule = rules.get(0);
+      assertEquals(ProguardKeepRuleType.KEEP.toString(), rule.typeString());
+      assertEquals("*", rule.getClassNames().toString());
+    }
+  }
 }