Merge "Fix pg compilation and relax r8 assertion when running run_on_as_app"
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 00157f5..3985cd7 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -5,10 +5,12 @@
 
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FlagFile;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -21,6 +23,15 @@
 
 public class D8CommandParser extends BaseCompilerCommandParser {
 
+  private static final Set<String> OPTIONS_WITH_PARAMETER =
+      ImmutableSet.of(
+          "--output",
+          "--lib",
+          "--classpath",
+          "--min-api",
+          "--main-dex-list",
+          "--main-dex-list-output");
+
   private static final String APK_EXTENSION = ".apk";
   private static final String JAR_EXTENSION = ".jar";
   private static final String ZIP_EXTENSION = ".zip";
@@ -106,13 +117,16 @@
               "                          # <file> must be an existing directory or a zip file.",
               "  --lib <file>            # Add <file> as a library resource.",
               "  --classpath <file>      # Add <file> as a classpath resource.",
-              "  --min-api               # Minimum Android API level compatibility",
+              "  --min-api <number>      # Minimum Android API level compatibility, default: "
+                  + AndroidApiLevel.getDefault().getLevel()
+                  + ".",
               "  --intermediate          # Compile an intermediate result intended for later",
               "                          # merging.",
               "  --file-per-class        # Produce a separate dex file per input class",
               "  --no-desugaring         # Force disable desugaring.",
               "  --main-dex-list <file>  # List of classes to place in the primary dex file.",
-              "  --main-dex-list-output <file> # Output resulting main dex list in <file>.",
+              "  --main-dex-list-output <file>",
+              "                          # Output resulting main dex list in <file>.",
               "  --version               # Print the version of d8.",
               "  --help                  # Print this message."));
 
@@ -153,6 +167,16 @@
     String[] expandedArgs = FlagFile.expandFlagFiles(args, builder);
     for (int i = 0; i < expandedArgs.length; i++) {
       String arg = expandedArgs[i].trim();
+      String nextArg = null;
+      if (OPTIONS_WITH_PARAMETER.contains(arg)) {
+        if (++i < expandedArgs.length) {
+          nextArg = expandedArgs[i];
+        } else {
+          builder.error(
+              new StringDiagnostic("Missing parameter for " + expandedArgs[i - 1] + ".", origin));
+          break;
+        }
+      }
       if (arg.length() == 0) {
         continue;
       } else if (arg.equals("--help")) {
@@ -176,19 +200,18 @@
       } else if (arg.equals("--file-per-class")) {
         outputMode = OutputMode.DexFilePerClass;
       } else if (arg.equals("--output")) {
-        String output = expandedArgs[++i];
         if (outputPath != null) {
           builder.error(
               new StringDiagnostic(
-                  "Cannot output both to '" + outputPath.toString() + "' and '" + output + "'",
+                  "Cannot output both to '" + outputPath.toString() + "' and '" + nextArg + "'",
                   origin));
           continue;
         }
-        outputPath = Paths.get(output);
+        outputPath = Paths.get(nextArg);
       } else if (arg.equals("--lib")) {
-        builder.addLibraryFiles(Paths.get(expandedArgs[++i]));
+        builder.addLibraryFiles(Paths.get(nextArg));
       } else if (arg.equals("--classpath")) {
-        Path file = Paths.get(expandedArgs[++i]);
+        Path file = Paths.get(nextArg);
         try {
           if (!Files.exists(file)) {
             throw new NoSuchFileException(file.toString());
@@ -206,17 +229,16 @@
           builder.error(new ExceptionDiagnostic(e, new PathOrigin(file)));
         }
       } else if (arg.equals("--main-dex-list")) {
-        builder.addMainDexListFiles(Paths.get(expandedArgs[++i]));
+        builder.addMainDexListFiles(Paths.get(nextArg));
       } else if (arg.equals("--main-dex-list-output")) {
-        builder.setMainDexListOutputPath(Paths.get(expandedArgs[++i]));
+        builder.setMainDexListOutputPath(Paths.get(nextArg));
       } else if (arg.equals("--optimize-multidex-for-linearalloc")) {
         builder.setOptimizeMultidexForLinearAlloc(true);
       } else if (arg.equals("--min-api")) {
-        String minApiString = expandedArgs[++i];
         if (hasDefinedApiLevel) {
           builder.error(new StringDiagnostic("Cannot set multiple --min-api options", origin));
         } else {
-          parseMinApi(builder, minApiString, origin);
+          parseMinApi(builder, nextArg, origin);
           hasDefinedApiLevel = true;
         }
       } else if (arg.equals("--intermediate")) {
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index 580b068..b91739a 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -4,14 +4,28 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FlagFile;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableSet;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
+import java.util.Set;
 
 public class R8CommandParser extends BaseCompilerCommandParser {
 
+  private static final Set<String> OPTIONS_WITH_PARAMETER =
+      ImmutableSet.of(
+          "--output",
+          "--lib",
+          "--min-api",
+          "--main-dex-rules",
+          "--main-dex-list",
+          "--main-dex-list-output",
+          "--pg-conf",
+          "--pg-map-output");
+
   public static void main(String[] args) throws CompilationFailedException {
     R8Command command = parse(args, Origin.root()).build();
     if (command.isPrintHelp()) {
@@ -45,7 +59,9 @@
               "  --output <file>          # Output result in <file>.",
               "                           # <file> must be an existing directory or a zip file.",
               "  --lib <file>             # Add <file> as a library resource.",
-              "  --min-api                # Minimum Android API level compatibility.",
+              "  --min-api <number>       # Minimum Android API level compatibility, default: "
+                  + AndroidApiLevel.getDefault().getLevel()
+                  + ".",
               "  --pg-conf <file>         # Proguard configuration <file>.",
               "  --pg-map-output <file>   # Output the resulting name and line mapping to <file>.",
               "  --no-tree-shaking        # Force disable tree shaking of unreachable classes.",
@@ -55,7 +71,8 @@
               "  --main-dex-rules <file>  # Proguard keep rules for classes to place in the",
               "                           # primary dex file.",
               "  --main-dex-list <file>   # List of classes to place in the primary dex file.",
-              "  --main-dex-list-output <file>  # Output the full main-dex list in <file>.",
+              "  --main-dex-list-output <file>  ",
+              "                           # Output the full main-dex list in <file>.",
               "  --version                # Print the version of r8.",
               "  --help                   # Print this message."));
   /**
@@ -102,6 +119,17 @@
     String[] expandedArgs = FlagFile.expandFlagFiles(args, builder);
     for (int i = 0; i < expandedArgs.length; i++) {
       String arg = expandedArgs[i].trim();
+      String nextArg = null;
+      if (OPTIONS_WITH_PARAMETER.contains(arg)) {
+        if (++i < expandedArgs.length) {
+          nextArg = expandedArgs[i];
+        } else {
+          builder.error(
+              new StringDiagnostic(
+                  "Missing parameter for " + expandedArgs[i - 1] + ".", argsOrigin));
+          break;
+        }
+      }
       if (arg.length() == 0) {
         continue;
       } else if (arg.equals("--help")) {
@@ -137,26 +165,24 @@
         }
         state.outputMode = OutputMode.ClassFile;
       } else if (arg.equals("--output")) {
-        String outputPath = expandedArgs[++i];
         if (state.outputPath != null) {
           builder.error(
               new StringDiagnostic(
                   "Cannot output both to '"
                       + state.outputPath.toString()
                       + "' and '"
-                      + outputPath
+                      + nextArg
                       + "'",
                   argsOrigin));
         }
-        state.outputPath = Paths.get(outputPath);
+        state.outputPath = Paths.get(nextArg);
       } else if (arg.equals("--lib")) {
-        builder.addLibraryFiles(Paths.get(expandedArgs[++i]));
+        builder.addLibraryFiles(Paths.get(nextArg));
       } else if (arg.equals("--min-api")) {
-        String minApiString = expandedArgs[++i];
         if (state.hasDefinedApiLevel) {
           builder.error(new StringDiagnostic("Cannot set multiple --min-api options", argsOrigin));
         } else {
-          parseMinApi(builder, minApiString, argsOrigin);
+          parseMinApi(builder, nextArg, argsOrigin);
           state.hasDefinedApiLevel = true;
         }
       } else if (arg.equals("--no-tree-shaking")) {
@@ -166,17 +192,17 @@
       } else if (arg.equals("--no-desugaring")) {
         builder.setDisableDesugaring(true);
       } else if (arg.equals("--main-dex-rules")) {
-        builder.addMainDexRulesFiles(Paths.get(expandedArgs[++i]));
+        builder.addMainDexRulesFiles(Paths.get(nextArg));
       } else if (arg.equals("--main-dex-list")) {
-        builder.addMainDexListFiles(Paths.get(expandedArgs[++i]));
+        builder.addMainDexListFiles(Paths.get(nextArg));
       } else if (arg.equals("--main-dex-list-output")) {
-        builder.setMainDexListOutputPath(Paths.get(expandedArgs[++i]));
+        builder.setMainDexListOutputPath(Paths.get(nextArg));
       } else if (arg.equals("--optimize-multidex-for-linearalloc")) {
         builder.setOptimizeMultidexForLinearAlloc(true);
       } else if (arg.equals("--pg-conf")) {
-        builder.addProguardConfigurationFiles(Paths.get(expandedArgs[++i]));
+        builder.addProguardConfigurationFiles(Paths.get(nextArg));
       } else if (arg.equals("--pg-map-output")) {
-        builder.setProguardMapOutputPath(Paths.get(expandedArgs[++i]));
+        builder.setProguardMapOutputPath(Paths.get(nextArg));
       } else if (arg.equals("--no-data-resources")) {
         state.includeDataResources = false;
       } else {
diff --git a/src/main/java/com/android/tools/r8/references/Reference.java b/src/main/java/com/android/tools/r8/references/Reference.java
index 0e3d988..decd262 100644
--- a/src/main/java/com/android/tools/r8/references/Reference.java
+++ b/src/main/java/com/android/tools/r8/references/Reference.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.MapMaker;
+import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.List;
@@ -150,6 +151,17 @@
         returnType == Void.TYPE ? null : typeFromClass(returnType));
   }
 
+  /** Get a method reference from a Java reflection constructor. */
+  public static MethodReference methodFromMethod(Constructor<?> method) {
+    Class<?> holderClass = method.getDeclaringClass();
+    Class<?>[] parameterTypes = method.getParameterTypes();
+    ImmutableList.Builder<TypeReference> builder = ImmutableList.builder();
+    for (Class<?> parameterType : parameterTypes) {
+      builder.add(typeFromClass(parameterType));
+    }
+    return method(classFromClass(holderClass), "<init>", builder.build(), null);
+  }
+
   /** Get a field reference from its full reference specification. */
   public static FieldReference field(
       ClassReference holderClass, String fieldName, TypeReference fieldType) {
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 f011931..6d24a8c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -307,6 +307,10 @@
     this.options = options;
   }
 
+  private static <T> SetWithReason<T> newSetWithoutReasonReporter() {
+    return new SetWithReason<>((f, r) -> {});
+  }
+
   private void enqueueRootItems(Map<DexReference, Set<ProguardKeepRule>> items) {
     items.entrySet().forEach(this::enqueueRootItem);
   }
@@ -1097,6 +1101,7 @@
       SetWithReason<DexEncodedField> reachableFields = reachableInstanceFields.get(type);
       if (reachableFields != null) {
         for (DexEncodedField field : reachableFields.getItems()) {
+          // TODO(b/120959039): Should the reason this field is reachable come from the set?
           markInstanceFieldAsLive(field, KeepReason.reachableFromLiveType(type));
         }
       }
@@ -1241,14 +1246,14 @@
     if (encodedField.accessFlags.isStatic()) {
       markStaticFieldAsLive(encodedField.field, reason);
     } else {
-      SetWithReason<DexEncodedField> reachable =
-          reachableInstanceFields.computeIfAbsent(
-              encodedField.field.clazz, ignore -> new SetWithReason<>((f, r) -> {}));
-      // TODO(b/120959039): The reachable.add test might be hiding other paths to the field.
-      if (reachable.add(encodedField, reason)
-          && isInstantiatedOrHasInstantiatedSubtype(encodedField.field.clazz)) {
+      if (isInstantiatedOrHasInstantiatedSubtype(encodedField.field.clazz)) {
         // We have at least one live subtype, so mark it as live.
         markInstanceFieldAsLive(encodedField, reason);
+      } else {
+        // Add the field to the reachable set if the type later becomes instantiated.
+        reachableInstanceFields
+            .computeIfAbsent(encodedField.field.clazz, ignore -> newSetWithoutReasonReporter())
+            .add(encodedField, reason);
       }
     }
   }
@@ -1292,7 +1297,7 @@
       // TODO(b/120959039): The reachable.add test might be hiding other paths to the method.
       SetWithReason<DexEncodedMethod> reachable =
           reachableVirtualMethods.computeIfAbsent(
-              encodedMethod.method.holder, (ignore) -> new SetWithReason<>((m, r) -> {}));
+              encodedMethod.method.holder, ignore -> newSetWithoutReasonReporter());
       if (reachable.add(encodedMethod, reason)) {
         // Abstract methods cannot be live.
         if (!encodedMethod.accessFlags.isAbstract()) {
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 e3a8273..5acd2a8 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -1433,11 +1433,27 @@
     }
 
     private IdentifierPatternWithWildcards acceptIdentifierWithBackreference(IdentifierType kind) {
+      IdentifierPatternWithWildcardsAndNegation pattern =
+          acceptIdentifierWithBackreference(kind, false);
+      if (pattern == null) {
+        return null;
+      }
+      assert !pattern.negated;
+      return pattern.patternWithWildcards;
+    }
+
+    private IdentifierPatternWithWildcardsAndNegation acceptIdentifierWithBackreference(
+        IdentifierType kind, boolean allowNegation) {
       ImmutableList.Builder<ProguardWildcard> wildcardsCollector = ImmutableList.builder();
       StringBuilder currentAsterisks = null;
       int asteriskCount = 0;
       StringBuilder currentBackreference = null;
       skipWhitespace();
+
+      final char quote = acceptQuoteIfPresent();
+      final boolean quoted = isQuote(quote);
+      final boolean negated = allowNegation ? acceptChar('!') : false;
+
       int start = position;
       int end = position;
       while (!eof(end)) {
@@ -1516,9 +1532,17 @@
           currentBackreference = new StringBuilder();
           end += Character.charCount(current);
         } else {
+          if (quoted && quote != current) {
+            throw reporter.fatalError(
+                new StringDiagnostic(
+                    "Invalid character '" + (char) current + "', expected end-quote.",
+                    origin,
+                    getPosition()));
+          }
           break;
         }
       }
+      position = quoted ? end + 1 : end;
       if (currentAsterisks != null) {
         wildcardsCollector.add(new ProguardWildcard.Pattern(currentAsterisks.toString()));
       }
@@ -1530,10 +1554,8 @@
       if (start == end) {
         return null;
       }
-      position = end;
-      return new IdentifierPatternWithWildcards(
-          contents.substring(start, end),
-          wildcardsCollector.build());
+      return new IdentifierPatternWithWildcardsAndNegation(
+          contents.substring(start, end), wildcardsCollector.build(), negated);
     }
 
     private String acceptFieldNameOrIntegerForReturn() {
@@ -1627,20 +1649,20 @@
       }
     }
 
+    private void parseClassNameAddToBuilder(ProguardClassNameList.Builder builder)
+        throws ProguardRuleParserException {
+      IdentifierPatternWithWildcardsAndNegation name = parseClassName(true);
+      builder.addClassName(
+          name.negated,
+          ProguardTypeMatcher.create(name.patternWithWildcards, ClassOrType.CLASS, dexItemFactory));
+      skipWhitespace();
+    }
+
     private ProguardClassNameList parseClassNames() throws ProguardRuleParserException {
       ProguardClassNameList.Builder builder = ProguardClassNameList.builder();
-      skipWhitespace();
-      boolean negated = acceptChar('!');
-      builder.addClassName(negated,
-          ProguardTypeMatcher.create(parseClassName(), ClassOrType.CLASS, dexItemFactory));
-      skipWhitespace();
-      while (acceptChar(',')) {
-        skipWhitespace();
-        negated = acceptChar('!');
-        builder.addClassName(negated,
-            ProguardTypeMatcher.create(parseClassName(), ClassOrType.CLASS, dexItemFactory));
-        skipWhitespace();
-      }
+      do {
+        parseClassNameAddToBuilder(builder);
+      } while (acceptChar(','));
       return builder.build();
     }
 
@@ -1650,8 +1672,15 @@
     }
 
     private IdentifierPatternWithWildcards parseClassName() throws ProguardRuleParserException {
-      IdentifierPatternWithWildcards name =
-          acceptIdentifierWithBackreference(IdentifierType.CLASS_NAME);
+      IdentifierPatternWithWildcardsAndNegation name = parseClassName(false);
+      assert !name.negated;
+      return name.patternWithWildcards;
+    }
+
+    private IdentifierPatternWithWildcardsAndNegation parseClassName(boolean allowNegation)
+        throws ProguardRuleParserException {
+      IdentifierPatternWithWildcardsAndNegation name =
+          acceptIdentifierWithBackreference(IdentifierType.CLASS_NAME, allowNegation);
       if (name == null) {
         throw parseError("Class name expected");
       }
@@ -1838,4 +1867,15 @@
       return false;
     }
   }
+
+  static class IdentifierPatternWithWildcardsAndNegation {
+    final IdentifierPatternWithWildcards patternWithWildcards;
+    final boolean negated;
+
+    IdentifierPatternWithWildcardsAndNegation(
+        String pattern, List<ProguardWildcard> wildcards, boolean negated) {
+      patternWithWildcards = new IdentifierPatternWithWildcards(pattern, wildcards);
+      this.negated = negated;
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 6b51909..c191d9a 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -507,6 +507,12 @@
     assertEquals(0, new ZipFile(emptyZip.toFile(), StandardCharsets.UTF_8).size());
   }
 
+  @Test(expected = CompilationFailedException.class)
+  public void missingParameterForLastOption() throws CompilationFailedException {
+    DiagnosticsChecker.checkErrorsContains(
+        "Missing parameter", handler -> parse(handler, "--output"));
+  }
+
   private D8Command parse(String... args) throws CompilationFailedException {
     return D8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 5331ca9..980e495 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -619,6 +619,12 @@
     runCustomResourceProcessing(false, false, 0);
   }
 
+  @Test(expected = CompilationFailedException.class)
+  public void missingParameterForLastOption() throws CompilationFailedException {
+    DiagnosticsChecker.checkErrorsContains(
+        "Missing parameter", handler -> parse(handler, "--output"));
+  }
+
   private R8Command parse(String... args) throws CompilationFailedException {
     return R8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
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
new file mode 100644
index 0000000..3112ae7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
@@ -0,0 +1,97 @@
+// 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.b124357885;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import org.junit.Test;
+
+public class B124357885Test extends TestBase {
+
+  private void checkSignatureAnnotation(AnnotationSubject signature) {
+    DexAnnotationElement[] elements = signature.getAnnotation().elements;
+    assertEquals(1, elements.length);
+    assertEquals("value", elements[0].name.toString());
+    assertTrue(elements[0].value instanceof DexValueArray);
+    DexValueArray array = (DexValueArray) elements[0].value;
+    StringBuilder builder = new StringBuilder();
+    for (DexValue value : array.getValues()) {
+      assertTrue(value instanceof DexValueString);
+      builder.append(((DexValueString) value).value);
+    }
+    // TODO(124357885): This should be the minified name for FooImpl instead of Foo.
+    String fooDescriptor = DescriptorUtils.javaTypeToDescriptor(Foo.class.getTypeName());
+    StringBuilder expected =
+        new StringBuilder()
+            .append("()")
+            .append(fooDescriptor.substring(0, fooDescriptor.length() - 1))  // Remove the ;.
+            .append("<Ljava/lang/String;>")
+            .append(";");  // Add the ; here.
+    assertEquals(expected.toString(), builder.toString());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(Backend.DEX)
+        .addProgramClasses(Main.class, Service.class, Foo.class, FooImpl.class)
+        .addKeepMainRule(Main.class)
+        .addKeepRules("-keepattributes Signature,InnerClasses,EnclosingMethod")
+        .compile()
+        .inspect(inspector -> {
+          assertThat(inspector.clazz(Main.class), allOf(isPresent(), not(isRenamed())));
+          assertThat(inspector.clazz(Service.class), allOf(isPresent(), isRenamed()));
+          assertThat(inspector.clazz(Foo.class), not(isPresent()));
+          assertThat(inspector.clazz(FooImpl.class), allOf(isPresent(), isRenamed()));
+          // TODO(124477502): Using uniqueMethodWithName("fooList") does not work.
+          assertEquals(1, inspector.clazz(Service.class).allMethods().size());
+          MethodSubject fooList = inspector.clazz(Service.class).allMethods().get(0);
+          AnnotationSubject signature = fooList.annotation("dalvik.annotation.Signature");
+          checkSignatureAnnotation(signature);
+        })
+        .run(Main.class)
+        .assertFailureWithErrorThatMatches(
+            containsString(
+                "java.lang.ClassNotFoundException: "
+                    + "Didn't find class \"com.android.tools.r8.naming.b124357885.Foo\""));
+  }
+}
+
+class Main {
+  public static void main(String... args) throws Exception {
+    Method method = Service.class.getMethod("fooList");
+    ParameterizedType type = (ParameterizedType) method.getGenericReturnType();
+    Class<?> rawType = (Class<?>) type.getRawType();
+    System.out.println(rawType.getName());
+
+    // Convince R8 we only use subtypes to get class merging of Foo into FooImpl.
+    Foo<String> foo = new FooImpl<>();
+    System.out.println(foo);
+  }
+}
+
+interface Service {
+  Foo<String> fooList();
+}
+
+interface Foo<T> {}
+
+class FooImpl<T> implements Foo<T> {}
\ No newline at end of file
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 f3aba75..cac6dd3 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -2402,12 +2402,14 @@
 
   @Test
   public void b124181032() throws Exception {
+    // Test spaces and quotes in class name list.
     ProguardConfigurationParser parser;
     parser = new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(
         createConfigurationForTesting(
             ImmutableList.of(
-                "-keepclassmembers class a.b.c.**, !**Client, !**Interceptor {",
+                "-keepclassmembers class \"a.b.c.**\" ,"
+                    + " !**d , '!**e' , \"!**f\" , g , 'h' , \"i\" { ",
                 "<fields>;",
                 "<init>();",
                 "}")));
@@ -2415,6 +2417,6 @@
     assertEquals(1, rules.size());
     ProguardConfigurationRule rule = rules.get(0);
     assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS.toString(), rule.typeString());
-    assertEquals("a.b.c.**,!**Client,!**Interceptor", rule.getClassNames().toString());
+    assertEquals("a.b.c.**,!**d,!**e,!**f,g,h,i", rule.getClassNames().toString());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
index e518181..3ba9b99 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
@@ -8,9 +8,7 @@
 @Keep
 public class KeptByFieldReflectionTest {
 
-  // TODO(b/123262024): This field must be kept un-initialized. Otherwise the "-whyareyoukeeping"
-  // output tested will hit the initialization in <init> and not the reflective access.
-  public int foo;
+  public int foo = 42;
 
   public static void main(String[] args) throws Exception {
     // Due to b/123210548 the object cannot be created by a reflective newInstance call.
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
index 62dcebe..6ba1f08 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
@@ -30,7 +30,7 @@
   private static final Class<?> CLASS = KeptByFieldReflectionTest.class;
   private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS);
 
-  private final String EXPECTED_STDOUT = StringUtils.lines("got foo: 0");
+  private final String EXPECTED_STDOUT = StringUtils.lines("got foo: 42");
 
   private final String EXPECTED_WHYAREYOUKEEPING =
       StringUtils.lines(
@@ -55,6 +55,7 @@
   public void test() throws Exception {
     MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
     FieldReference fooField = fieldFromField(CLASS.getDeclaredField("foo"));
+    MethodReference fooInit = methodFromMethod(CLASS.getDeclaredConstructor());
 
     if (backend == Backend.CF) {
       testForJvm().addProgramClasses(CLASSES).run(CLASS).assertSuccessWithOutput(EXPECTED_STDOUT);
@@ -80,6 +81,11 @@
 
     inspector.method(mainMethod).assertNotRenamed().assertKeptBy(keepMain);
 
+    // The field is primarily kept by the reflective lookup in main.
     inspector.field(fooField).assertRenamed().assertReflectedFrom(mainMethod);
+
+    // The field is also kept by the write in Foo.<init>.
+    // We may want to change that behavior. See b/124428834.
+    inspector.field(fooField).assertRenamed().assertKeptBy(inspector.method(fooInit));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
index 8e24088..87ae2ec 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -116,4 +116,9 @@
   public boolean hasLocalVariableTable() {
     throw new Unreachable("Cannot determine if an absent method has a local variable table");
   }
+
+  @Override
+  public AnnotationSubject annotation(String name) {
+    return new AbsentAnnotationSubject();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 796532d..6581a47 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugEvent;
 import com.android.tools.r8.graph.DexDebugInfo;
@@ -283,4 +284,13 @@
   public String toString() {
     return dexMethod.toSourceString();
   }
+
+  @Override
+  public AnnotationSubject annotation(String name) {
+    DexAnnotation annotation = codeInspector.findAnnotation(name, dexMethod.annotations);
+    return annotation == null
+        ? new AbsentAnnotationSubject()
+        : new FoundAnnotationSubject(annotation);
+  }
+
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 8fb4001..1575372 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -72,4 +72,6 @@
   public boolean isMethodSubject() {
     return true;
   }
+
+  public abstract AnnotationSubject annotation(String name);
 }