Merge "Ignore -dontskipnonpubliclibraryclassmembers"
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d273090..20ba8b1 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -210,7 +210,7 @@
                 "Shrinking can't be performed because some library classes are missing.");
           }
         }
-        rootSet = new RootSetBuilder(application, options.keepRules).run(executorService);
+        rootSet = new RootSetBuilder(application, appInfo, options.keepRules).run(executorService);
         Enqueuer enqueuer = new Enqueuer(rootSet, appInfo);
         appInfo = enqueuer.run(timing);
         if (options.printSeeds) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 22ce470..7aef78c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -155,22 +155,37 @@
     }
   }
 
+  private void removeLambdaDeserializationMethods() {
+    if (lambdaRewriter != null) {
+      lambdaRewriter.removeLambdaDeserializationMethods(application.classes());
+    }
+  }
+
+  private void synthesizeLambdaClasses(Builder builder) {
+    if (lambdaRewriter != null) {
+      lambdaRewriter.adjustAccessibility(builder);
+      lambdaRewriter.synthesizeLambdaClasses(builder);
+    }
+  }
+
+  private void desugarInterfaceMethods(
+      Builder builder, InterfaceMethodRewriter.Flavor includeAllResources) {
+    if (interfaceMethodRewriter != null) {
+      interfaceMethodRewriter.desugarInterfaceMethods(builder, includeAllResources);
+    }
+  }
+
   public DexApplication convertToDex() {
+    removeLambdaDeserializationMethods();
+
     convertClassesToDex(application.classes());
 
     // Build a new application with jumbo string info,
     Builder builder = new Builder(application);
     builder.setHighestSortingString(highestSortingString);
 
-    // Lambda rewriter
-    if (lambdaRewriter != null) {
-      lambdaRewriter.adjustAccessibility(builder);
-      lambdaRewriter.synthesizeLambdaClasses(builder);
-    }
-
-    if (interfaceMethodRewriter != null) {
-      interfaceMethodRewriter.desugarInterfaceMethods(builder, ExcludeDexResources);
-    }
+    synthesizeLambdaClasses(builder);
+    desugarInterfaceMethods(builder, ExcludeDexResources);
 
     return builder.build();
   }
@@ -207,6 +222,8 @@
   }
 
   public DexApplication optimize(ExecutorService executorService) throws ExecutionException {
+    removeLambdaDeserializationMethods();
+
     timing.begin("Build call graph");
     callGraph = CallGraph.build(application, appInfo.withSubtyping(), graphLense);
     timing.end();
@@ -265,15 +282,8 @@
       }
     }
 
-    // Lambda rewriter.
-    if (lambdaRewriter != null) {
-      lambdaRewriter.adjustAccessibility(builder);
-      lambdaRewriter.synthesizeLambdaClasses(builder);
-    }
-
-    if (interfaceMethodRewriter != null) {
-      interfaceMethodRewriter.desugarInterfaceMethods(builder, IncludeAllResources);
-    }
+    synthesizeLambdaClasses(builder);
+    desugarInterfaceMethods(builder, IncludeAllResources);
 
     if (outliner != null) {
       timing.begin("IR conversion phase 2");
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 5644096..87241cf 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -46,9 +46,11 @@
   private static final String METHODHANDLE_TYPE_DESCR = "Ljava/lang/invoke/MethodHandle;";
   private static final String OBJECT_ARRAY_TYPE_DESCR = "[Ljava/lang/Object;";
   private static final String SERIALIZABLE_TYPE_DESCR = "Ljava/io/Serializable;";
+  private static final String SERIALIZED_LAMBDA_TYPE_DESCR = "Ljava/lang/invoke/SerializedLambda;";
 
   private static final String METAFACTORY_METHOD_NAME = "metafactory";
   private static final String METAFACTORY_ALT_METHOD_NAME = "altMetafactory";
+  private static final String DESERIALIZE_LAMBDA_METHOD_NAME = "$deserializeLambda$";
 
   static final String LAMBDA_CLASS_NAME_PREFIX = "-$$Lambda$";
   static final String EXPECTED_LAMBDA_METHOD_PREFIX = "lambda$";
@@ -68,6 +70,9 @@
   final DexString classConstructorName;
   final DexString instanceFieldName;
 
+  final DexString deserializeLambdaMethodName;
+  final DexProto deserializeLambdaMethodProto;
+
   // Maps call sites seen so far to inferred lambda descriptor. It is intended
   // to help avoid re-matching call sites we already seen. Note that same call
   // site may match one or several lambda classes.
@@ -116,6 +121,10 @@
     this.classConstructorName = factory.createString(Constants.CLASS_INITIALIZER_NAME);
     this.instanceFieldName = factory.createString(LAMBDA_INSTANCE_FIELD_NAME);
     this.serializableType = factory.createType(SERIALIZABLE_TYPE_DESCR);
+
+    this.deserializeLambdaMethodName = factory.createString(DESERIALIZE_LAMBDA_METHOD_NAME);
+    this.deserializeLambdaMethodProto = factory.createProto(
+        factory.objectType, new DexType[] { factory.createType(SERIALIZED_LAMBDA_TYPE_DESCR) });
   }
 
   /**
@@ -151,6 +160,35 @@
     }
   }
 
+  /** Remove lambda deserialization methods. */
+  public void removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
+    for (DexProgramClass clazz : classes) {
+      // Search for a lambda deserialization method and remove it if found.
+      DexEncodedMethod[] directMethods = clazz.directMethods;
+      if (directMethods != null) {
+        int methodCount = directMethods.length;
+        for (int i = 0; i < methodCount; i++) {
+          DexEncodedMethod encoded = directMethods[i];
+          DexMethod method = encoded.method;
+          if (method.name == deserializeLambdaMethodName &&
+              method.proto == deserializeLambdaMethodProto) {
+            assert encoded.accessFlags.isStatic();
+            assert encoded.accessFlags.isPrivate();
+            assert encoded.accessFlags.isSynthetic();
+
+            DexEncodedMethod[] newMethods = new DexEncodedMethod[methodCount - 1];
+            System.arraycopy(directMethods, 0, newMethods, 0, i);
+            System.arraycopy(directMethods, i + 1, newMethods, i, methodCount - i - 1);
+            clazz.directMethods = newMethods;
+
+            // We assume there is only one such method in the class.
+            break;
+          }
+        }
+      }
+    }
+  }
+
   /**
    * Adjust accessibility of referenced application symbols or
    * creates necessary accessors.
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 7103abe..28b68b1 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -238,7 +238,13 @@
         int end = liveRange.end;
         Integer nextEnd;
         while ((nextEnd = nextInRange(start, end, ends)) != null) {
-          ranges.add(new LocalRange(value, getRegisterForValue(value, start), start, nextEnd));
+          // If an argument value has been split, we have disallowed argument reuse and therefore,
+          // the argument value is also in the argument register throughout the method. For debug
+          // information, we always use the argument register whenever a local corresponds to an
+          // argument value. That avoids ending and restarting locals whenever we move arguments
+          // to lower register.
+          int register = getRegisterForValue(value, value.isArgument() ? 0 : start);
+          ranges.add(new LocalRange(value, register, start, nextEnd));
           Integer nextStart = nextInRange(nextEnd, end, starts);
           if (nextStart == null) {
             start = -1;
@@ -797,9 +803,6 @@
     maxRegisterNumber = Math.max(maxRegisterNumber, maxRegister);
   }
 
-  // Select a spill register.
-  // TODO(ager): At this point this always takes the next unused register number. We need a
-  // more intelligent selection of spill registers.
   private int getSpillRegister(LiveIntervals intervals) {
     int registerNumber = nextUnusedRegisterNumber++;
     maxRegisterNumber = registerNumber;
@@ -873,6 +876,18 @@
   private boolean allocateSingleInterval(LiveIntervals unhandledInterval, ArgumentReuseMode mode) {
     int registerConstraint = unhandledInterval.getRegisterLimit();
     assert registerConstraint <= Constants.U16BIT_MAX;
+
+    assert unhandledInterval.requiredRegisters() <= 2;
+    boolean needsRegisterPair = unhandledInterval.requiredRegisters() == 2;
+
+    // Just use the argument register if an argument split has no register constraint. That will
+    // avoid move generation for the argument.
+    if (registerConstraint == Constants.U16BIT_MAX && unhandledInterval.isArgumentInterval()) {
+      int argumentRegister = unhandledInterval.getSplitParent().getRegister();
+      assignRegisterToUnhandledInterval(unhandledInterval, needsRegisterPair, argumentRegister);
+      return true;
+    }
+
     if (registerConstraint < Constants.U16BIT_MAX) {
       // We always have argument sentinels that will not actually occupy registers. Therefore, we
       // allow the use of NUMBER_OF_SENTINEL_REGISTERS more than the limit.
@@ -952,8 +967,6 @@
       }
     }
 
-    assert unhandledInterval.requiredRegisters() <= 2;
-    boolean needsRegisterPair = unhandledInterval.requiredRegisters() == 2;
     // Attempt to use register hints.
     if (useRegisterHint(unhandledInterval, registerConstraint, freePositions, needsRegisterPair)) {
       return true;
@@ -1320,7 +1333,6 @@
             splitRangesForSpilledConstant(splitChild, registerNumber);
           } else {
             splitRangesForSpilledInterval(splitChild, registerNumber);
-
           }
         }
       }
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 6dbf79b..83647b7 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -517,6 +517,8 @@
       Log.verbose(getClass(), "Adding static field `%s` to live set.", encodedField.field);
     }
     liveFields.add(encodedField, reason);
+    // Add all dependent members to the workqueue.
+    enqueueRootItems(rootSet.getDependentItems(encodedField));
   }
 
   private void markInstanceFieldAsLive(DexEncodedField field, KeepReason reason) {
@@ -526,6 +528,8 @@
       Log.verbose(getClass(), "Adding instance field `%s` to live set.", field.field);
     }
     liveFields.add(field, reason);
+    // Add all dependent members to the workqueue.
+    enqueueRootItems(rootSet.getDependentItems(field));
   }
 
   private void markDirectStaticOrConstructorMethodAsLive(
@@ -832,6 +836,8 @@
         processAnnotations(parameterAnnotation.annotations);
       }
       method.registerReachableDefinitions(new UseRegistry(method));
+      // Add all dependent members to the workqueue.
+      enqueueRootItems(rootSet.getDependentItems(method));
     }
   }
 
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 7e27fe0..2b086ae 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -20,6 +20,7 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.nio.CharBuffer;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
@@ -375,6 +376,10 @@
         } else if (acceptString("eswithmembernames")) {
           builder.setType(ProguardKeepRuleType.KEEP_CLASSES_WITH_MEMBERS);
           builder.getModifiersBuilder().allowsShrinking = true;
+        } else {
+          // The only path to here is through "-keep" followed by "class".
+          unacceptString("-keepclass");
+          throw parseError("Unknown option");
         }
       } else {
         builder.setType(ProguardKeepRuleType.KEEP);
@@ -905,6 +910,15 @@
       return contents.substring(start, end);
     }
 
+    private void unacceptString(String expected) {
+      assert position >= expected.length();
+      position -= expected.length();
+      for (int i = 0; i < expected.length(); i++) {
+        assert expected.charAt(i) == contents.charAt(position + i);
+      }
+    }
+
+
     private void checkNotNegatedPattern() throws ProguardRuleParserException {
       skipWhitespace();
       if (acceptChar('!')) {
@@ -947,9 +961,11 @@
       for (int lineNumber = 0; lineNumber < lines.length; lineNumber++) {
         String line = lines[lineNumber];
         if (remaining <= line.length() || lineNumber == lines.length - 1) {
-          return path.toString() + ":" + (lineNumber + 1) + ":" + remaining + "\n" + line;
+          String arrow = CharBuffer.allocate(remaining).toString().replace( '\0', ' ' ) + '^';
+          return path.toString() + ":" + (lineNumber + 1) + ":" + remaining + "\n" + line
+              + '\n' + arrow;
         }
-        remaining -= (line.length() + 1); // include newline.
+        remaining -= (line.length() + 1); // Include newline.
       }
       return path.toString();
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 848a743..8afe151 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.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.graph.AppInfo;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
@@ -38,6 +39,7 @@
 public class RootSetBuilder {
 
   private DexApplication application;
+  private final AppInfo appInfo;
   private final List<ProguardConfigurationRule> rules;
   private final Map<DexItem, ProguardKeepRule> noShrinking = new IdentityHashMap<>();
   private final Set<DexItem> noOptimization = Sets.newIdentityHashSet();
@@ -47,14 +49,15 @@
   private final Set<ProguardConfigurationRule> rulesThatUseExtendsOrImplementsWrong =
       Sets.newIdentityHashSet();
   private final Set<DexItem> checkDiscarded = Sets.newIdentityHashSet();
-  private final Map<DexType, Map<DexItem, ProguardKeepRule>> dependentNoShrinking =
+  private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking =
       new IdentityHashMap<>();
   private final Map<DexItem, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
   private final Map<DexItem, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
 
-  public RootSetBuilder(DexApplication application,
+  public RootSetBuilder(DexApplication application, AppInfo appInfo,
       List<ProguardConfigurationRule> rules) {
     this.application = application;
+    this.appInfo = appInfo;
     this.rules = rules;
   }
 
@@ -409,6 +412,39 @@
     addItemToSets(clazz, rule, null, null);
   }
 
+  private void includeDescriptor(DexItem item, DexType type, ProguardKeepRule context) {
+    if (type.isArrayType()) {
+      type = type.toBaseType(application.dexItemFactory);
+    }
+    if (type.isPrimitiveType()) {
+      return;
+    }
+    DexClass definition = appInfo.definitionFor(type);
+    if (definition == null || definition.isLibraryClass()) {
+      return;
+    }
+    // Keep the type if the item is also kept.
+    dependentNoShrinking.computeIfAbsent(item, x -> new IdentityHashMap<>())
+        .put(definition, context);
+    // Unconditionally add to no-obfuscation, as that is only checked for surviving items.
+    noObfuscation.add(definition);
+  }
+
+  private void includeDescriptorClasses(DexItem item, ProguardKeepRule context) {
+    if (item instanceof DexEncodedMethod) {
+      DexMethod method = ((DexEncodedMethod) item).method;
+      includeDescriptor(item, method.proto.returnType, context);
+      for (DexType value : method.proto.parameters.values) {
+        includeDescriptor(item, value, context);
+      }
+    } else if (item instanceof DexEncodedField) {
+      DexField field = ((DexEncodedField) item).field;
+      includeDescriptor(item, field.type, context);
+    } else {
+      assert item instanceof DexClass;
+    }
+  }
+
   private synchronized void addItemToSets(DexItem item, ProguardConfigurationRule context,
       ProguardMemberRule rule, DexType onlyIfClassKept) {
     if (context instanceof ProguardKeepRule) {
@@ -436,6 +472,9 @@
         assert onlyIfClassKept == null;
         keepPackageName.add(item);
       }
+      if (modifiers.includeDescriptorClasses) {
+        includeDescriptorClasses(item, keepRule);
+      }
       if (modifiers.checkDiscarded) {
         checkDiscarded.add(item);
       }
@@ -456,14 +495,49 @@
     public final Set<DexItem> checkDiscarded;
     public final Map<DexItem, ProguardMemberRule> noSideEffects;
     public final Map<DexItem, ProguardMemberRule> assumedValues;
-    private final Map<DexType, Map<DexItem, ProguardKeepRule>> dependentNoShrinking;
+    private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking;
+
+    private boolean legalNoObfuscationItem(DexItem item) {
+      if (!(item instanceof DexProgramClass
+          || item instanceof DexLibraryClass
+          || item instanceof DexEncodedMethod
+          || item instanceof DexEncodedField)) {
+      }
+      assert item instanceof DexProgramClass
+          || item instanceof DexLibraryClass
+          || item instanceof DexEncodedMethod
+          || item instanceof DexEncodedField;
+      return true;
+    }
+
+    private boolean legalNoObfuscationItems(Set<DexItem> items) {
+      items.forEach(this::legalNoObfuscationItem);
+      return true;
+    }
+
+    private boolean legalDependentNoShrinkingItem(DexItem item) {
+      if (!(item instanceof DexType
+          || item instanceof DexEncodedMethod
+          || item instanceof DexEncodedField)) {
+      }
+      assert item instanceof DexType
+          || item instanceof DexEncodedMethod
+          || item instanceof DexEncodedField;
+      return true;
+    }
+
+    private boolean legalDependentNoShrinkingItems(
+        Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking) {
+      dependentNoShrinking.keySet().forEach(this::legalDependentNoShrinkingItem);
+      return true;
+    }
 
     private RootSet(Map<DexItem, ProguardKeepRule> noShrinking,
         Set<DexItem> noOptimization, Set<DexItem> noObfuscation, Set<DexItem> reasonAsked,
         Set<DexItem> keepPackageName, Set<DexItem> checkDiscarded,
         Map<DexItem, ProguardMemberRule> noSideEffects,
         Map<DexItem, ProguardMemberRule> assumedValues,
-        Map<DexType, Map<DexItem, ProguardKeepRule>> dependentNoShrinking) {
+        Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking) {
       this.noShrinking = Collections.unmodifiableMap(noShrinking);
       this.noOptimization = Collections.unmodifiableSet(noOptimization);
       this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
@@ -473,11 +547,16 @@
       this.noSideEffects = Collections.unmodifiableMap(noSideEffects);
       this.assumedValues = Collections.unmodifiableMap(assumedValues);
       this.dependentNoShrinking = dependentNoShrinking;
+      assert legalNoObfuscationItems(noObfuscation);
+      assert legalDependentNoShrinkingItems(dependentNoShrinking);
     }
 
-    Map<DexItem, ProguardKeepRule> getDependentItems(DexType type) {
+    Map<DexItem, ProguardKeepRule> getDependentItems(DexItem item) {
+      assert item instanceof DexType
+          || item instanceof DexEncodedMethod
+          || item instanceof DexEncodedField;
       return Collections
-          .unmodifiableMap(dependentNoShrinking.getOrDefault(type, Collections.emptyMap()));
+          .unmodifiableMap(dependentNoShrinking.getOrDefault(item, Collections.emptyMap()));
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index 9a2b9f2..3f69c2e 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -5,12 +5,12 @@
 
 import com.android.tools.r8.CompilationException;
 import java.io.BufferedReader;
-import java.io.BufferedWriter;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 public class FileUtils {
@@ -66,12 +66,11 @@
   }
 
   public static void writeTextFile(Path file, List<String> lines) throws IOException {
-    try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) {
-      for (String line : lines) {
-        writer.write(line);
-        writer.write("\n");
-      }
-    }
+    Files.write(file, lines);
+  }
+
+  public static void writeTextFile(Path file, String... lines) throws IOException {
+    Files.write(file, Arrays.asList(lines));
   }
 
   public static Path validateOutputFile(Path path) throws CompilationException {
diff --git a/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java b/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
index d2e738d..8a1119a 100644
--- a/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
+++ b/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package lambdadesugaringnplus;
 
+import java.io.Serializable;
 import java.lang.annotation.Annotation;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -296,6 +297,23 @@
     }
   }
 
+  static class B62168701 {
+    interface I extends Serializable {
+      String getValue();
+    }
+
+    interface J {
+      static void dump() {
+        I i = () -> "B62168701 -- OK";
+        System.out.println(i.getValue());
+      }
+    }
+
+    static void test() {
+      J.dump();
+    }
+  }
+
   static void z(Z p) {
     System.out.println(p.foo(null));
   }
@@ -401,5 +419,6 @@
     B38306708.test();
     B38308515.test();
     B38302860.test();
+    B62168701.test();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 978f9de..a5e944a 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.InternalCompilerError;
+import com.android.tools.r8.errors.Unimplemented;
 import java.io.File;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -86,8 +88,10 @@
       D8Command command = builder.setOutputPath(out).build();
       try {
         ToolHelper.runD8(command, this::combinedOptionConsumer);
+      } catch (Unimplemented | CompilationError | InternalCompilerError re) {
+        throw re;
       } catch (RuntimeException re) {
-        throw re instanceof CompilationError ? re : re.getCause();
+        throw re.getCause() == null ? re : re.getCause();
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
index ea94040..6f51624 100644
--- a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.InternalCompilerError;
+import com.android.tools.r8.errors.Unimplemented;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.function.UnaryOperator;
@@ -32,8 +34,10 @@
       D8Command command = builder.addProgramFiles(inputFile).setOutputPath(out).build();
       try {
         ToolHelper.runD8(command, this::combinedOptionConsumer);
+      } catch (Unimplemented | CompilationError | InternalCompilerError re) {
+        throw re;
       } catch (RuntimeException re) {
-        throw re instanceof CompilationError ? re : re.getCause();
+        throw re.getCause() == null ? re : re.getCause();
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
new file mode 100644
index 0000000..a93fb12
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -0,0 +1,143 @@
+// Copyright (c) 2017, 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;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+
+public class TestBase {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  /**
+   * Write lines of text to a temporary file.
+   */
+  protected Path writeTextToTempFile(String... lines) throws IOException{
+    Path file = temp.newFile().toPath();
+    FileUtils.writeTextFile(file, lines);
+    return file;
+  }
+
+  /**
+   * Build an AndroidApp with the specified test classes.
+   */
+  protected static AndroidApp readClasses(Class... classes) throws IOException {
+    AndroidApp.Builder builder = AndroidApp.builder();
+    for (Class clazz : classes) {
+      builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
+    }
+    return builder.build();
+  }
+
+  /**
+   * Build an AndroidApp with the specified test classes.
+   */
+  protected static AndroidApp readClasses(List<Class> classes) throws IOException {
+    return readClasses(classes.toArray(new Class[classes.size()]));
+  }
+
+  /**
+   * Create a temporary JAR file containing the specified test classes.
+   */
+  protected Path jarTestClasses(Class... classes) throws IOException {
+    Path jar = File.createTempFile("junit", ".jar", temp.getRoot()).toPath();
+    try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar.toFile()))) {
+      for (Class clazz : classes) {
+        try (FileInputStream in =
+            new FileInputStream(ToolHelper.getClassFileForTestClass(clazz).toFile())) {
+          out.putNextEntry(new ZipEntry(clazz.getCanonicalName().replace('.', '/') + ".class"));
+          ByteStreams.copy(in, out);
+          out.closeEntry();
+        }
+      }
+    }
+    return jar;
+  }
+
+  /**
+   * Create a temporary JAR file containing the specified test classes.
+   */
+  protected Path jarTestClasses(List<Class> classes) throws IOException {
+    return jarTestClasses(classes.toArray(new Class[classes.size()]));
+  }
+
+  /**
+   * Compile an application with R8 using the supplied proguard configuration.
+   */
+  protected  AndroidApp compileWithR8(List<Class> classes, Path proguardConfig)
+      throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
+    return compileWithR8(readClasses(classes), proguardConfig);
+  }
+
+  /**
+   * Compile an application with R8 using the supplied proguard configuration.
+   */
+  protected AndroidApp compileWithR8(AndroidApp app, Path proguardConfig)
+      throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
+    R8Command command =
+        ToolHelper.prepareR8CommandBuilder(app)
+            .addProguardConfigurationFiles(proguardConfig)
+            .build();
+    return ToolHelper.runR8(command);
+  }
+
+  /**
+   * Compile an application with R8 using the supplied proguard configuration.
+   */
+  protected AndroidApp compileWithR8(
+      AndroidApp app, Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
+      throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
+    R8Command command =
+        ToolHelper.prepareR8CommandBuilder(app)
+            .addProguardConfigurationFiles(proguardConfig)
+            .build();
+    return ToolHelper.runR8(command, optionsConsumer);
+  }
+
+  /**
+   * Run application on Art with the specified main class.
+   */
+  protected String runOnArt(AndroidApp app, Class mainClass) throws IOException {
+    Path out = File.createTempFile("junit", ".zip", temp.getRoot()).toPath();
+    app.writeToZip(out, true);
+    return ToolHelper.runArtNoVerificationErrors(
+        ImmutableList.of(out.toString()), mainClass.getCanonicalName(), null);
+  }
+
+  /**
+   * Run a single class application on Java.
+   */
+  protected String runOnJava(Class mainClass) throws Exception {
+    ProcessResult result = ToolHelper.runJava(mainClass);
+    if (result.exitCode != 0) {
+      System.out.println("Std out:");
+      System.out.println(result.stdout);
+      System.out.println("Std err:");
+      System.out.println(result.stderr);
+      assertEquals(0, result.exitCode);
+    }
+    return result.stdout;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 6e45d0b..a5213e2 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -405,6 +405,10 @@
         .read();
   }
 
+  public static R8Command.Builder prepareR8CommandBuilder(AndroidApp app) {
+    return R8Command.builder(app);
+  }
+
   public static AndroidApp runR8(AndroidApp app)
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
     return runR8(R8Command.builder(app).build());
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 9fb98ae..115f1f9 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -10,16 +10,18 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.graph.DexAccessFlags;
 import com.android.tools.r8.graph.DexItemFactory;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 
-public class ProguardConfigurationParserTest {
+public class ProguardConfigurationParserTest extends TestBase {
 
   private static final String VALID_PROGUARD_DIR = "src/test/proguard/valid/";
   private static final String INVALID_PROGUARD_DIR = "src/test/proguard/invalid/";
@@ -340,4 +342,17 @@
     ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
     parser.parse(Paths.get(PARSE_AND_SKIP_SINGLE_ARGUMENT));
   }
+
+  @Test
+  public void parseInvalidKeepClassOption() throws IOException, ProguardRuleParserException {
+    thrown.expect(ProguardRuleParserException.class);
+    thrown.expectMessage("Unknown option at ");
+    ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+    Path proguardConfig = writeTextToTempFile(
+        "-keepclassx public class * {  ",
+        "  native <methods>;           ",
+        "}                             "
+    );
+    parser.parse(proguardConfig);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/ClassWithNativeMethods.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/ClassWithNativeMethods.java
new file mode 100644
index 0000000..0c43035
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/ClassWithNativeMethods.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2017, 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.shaking.includedescriptorclasses;
+
+public class ClassWithNativeMethods {
+  public static StaticFieldType staticField;
+  public InstanceFieldType instanceField;
+  public native void method1(NativeArgumentType a);
+  public native NativeReturnType method2();
+  public native NativeReturnType method3(NativeArgumentType a);
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
new file mode 100644
index 0000000..0e24d9b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
@@ -0,0 +1,240 @@
+// Copyright (c) 2017, 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.shaking.includedescriptorclasses;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.junit.Test;
+
+public class IncludeDescriptorClassesTest extends TestBase {
+
+  private static String PROGUARD = "third_party/proguard/proguard5.2.1/bin/proguard.sh";
+
+  private Path runProguard(Path inJar, Path config) throws IOException {
+    Path outJar = File.createTempFile("junit", ".jar", temp.getRoot()).toPath();
+    List<String> command = new ArrayList<>();
+    command.add(PROGUARD);
+    command.add("-forceprocessing");  // Proguard just checks the creation time on the in/out jars.
+    command.add("-injars");
+    command.add(inJar.toString());
+    command.add("-libraryjars");
+    command.add(ToolHelper.getDefaultAndroidJar());
+    command.add("@" + config);
+    command.add("-outjar");
+    command.add(outJar.toString());
+    command.add("-printmapping");
+    ProcessBuilder builder = new ProcessBuilder(command);
+    ToolHelper.ProcessResult result = ToolHelper.runProcess(builder);
+    if (result.exitCode != 0) {
+      fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
+    }
+    return outJar;
+  }
+
+  private Set<String> readJarClasses(Path jar) throws IOException {
+    Set<String> result = new HashSet<>();
+    try (ZipInputStream in = new ZipInputStream(new FileInputStream(jar.toFile()))) {
+      ZipEntry entry = in.getNextEntry();
+      while (entry != null) {
+        String name = entry.getName();
+        if (name.endsWith(".class")) {
+          result.add(name.substring(0, name.length() - ".class".length()).replace('/', '.'));
+        }
+        entry = in.getNextEntry();
+      }
+    }
+    return result;
+  }
+
+  private String keepMain(Class clazz) {
+    return "-keep public class " + clazz.getCanonicalName() + " {\n"
+        + "  public static void main(java.lang.String[]);\n"
+        + "}";
+  }
+
+  private class Result {
+    final DexInspector inspector;
+    final Set<String> classesAfterProguard;
+
+    Result(DexInspector inspector, Set<String> classesAfterProguard) {
+      this.inspector = inspector;
+      this.classesAfterProguard = classesAfterProguard;
+    }
+
+    void assertKept(Class clazz) {
+      assertTrue(inspector.clazz(clazz.getCanonicalName()).isPresent());
+      assertFalse(inspector.clazz(clazz.getCanonicalName()).isRenamed());
+      if (classesAfterProguard != null) {
+        assertTrue(classesAfterProguard.contains(clazz.getCanonicalName()));
+      }
+    }
+
+    void assertRemoved(Class clazz) {
+      assertFalse(inspector.clazz(clazz.getCanonicalName()).isPresent());
+      // TODO(sgjesse): Also check that it was not just renamed...
+      if (classesAfterProguard != null) {
+        assertFalse(classesAfterProguard.contains(clazz.getCanonicalName()));
+      }
+    }
+
+    void assertRenamed(Class clazz) {
+      assertTrue(inspector.clazz(clazz.getCanonicalName()).isPresent());
+      assertTrue(inspector.clazz(clazz.getCanonicalName()).isRenamed());
+      // TODO(sgjesse): Also check that it was actually renamed...
+      if (classesAfterProguard != null) {
+        assertFalse(classesAfterProguard.contains(clazz.getCanonicalName()));
+      }
+    }
+
+  }
+
+  private List<Class> applicationClasses = ImmutableList.of(
+      ClassWithNativeMethods.class, NativeArgumentType.class, NativeReturnType.class,
+      StaticFieldType.class, InstanceFieldType.class);
+  private List<Class> mainClasses = ImmutableList.of(
+      MainCallMethod1.class, MainCallMethod2.class, MainCallMethod3.class);
+
+  Result runTest(Class mainClass, Path proguardConfig) throws Exception {
+    List<Class> classes = new ArrayList<>(applicationClasses);
+    classes.add(mainClass);
+
+    DexInspector inspector = new DexInspector(compileWithR8(classes, proguardConfig));
+
+    Set<String> classesAfterProguard = null;
+    // Actually running Proguard should only be during development.
+    if (false) {
+      Path proguardedJar = runProguard(jarTestClasses(classes), proguardConfig);
+      classesAfterProguard = readJarClasses(proguardedJar);
+    }
+
+    return new Result(inspector, classesAfterProguard);
+  }
+
+  @Test
+  public void testNoIncludesDescriptorClasses() throws Exception {
+    for (Class mainClass : mainClasses) {
+      List<Class> allClasses = new ArrayList<>(applicationClasses);
+      allClasses.add(mainClass);
+
+      Path proguardConfig = writeTextToTempFile(
+          keepMain(mainClass),
+          "-keepclasseswithmembers class * {   ",
+          "  <fields>;                         ",
+          "  native <methods>;                 ",
+          "}                                   ",
+          "-allowaccessmodification            "
+      );
+
+      Result result = runTest(mainClass, proguardConfig);
+
+      // Without includedescriptorclasses return type argument type and field type are removed.
+      result.assertKept(ClassWithNativeMethods.class);
+      result.assertRemoved(NativeArgumentType.class);
+      result.assertRemoved(NativeReturnType.class);
+      result.assertRemoved(InstanceFieldType.class);
+      result.assertRemoved(StaticFieldType.class);
+    }
+  }
+
+  @Test
+  public void testKeepClassesWithMembers() throws Exception {
+    for (Class mainClass : mainClasses) {
+      Path proguardConfig = writeTextToTempFile(
+          keepMain(mainClass),
+          "-keepclasseswithmembers,includedescriptorclasses class * {  ",
+          "  <fields>;                                                 ",
+          "  native <methods>;                                         ",
+          "}                                                           ",
+          "-allowaccessmodification                                    "
+      );
+
+      Result result = runTest(mainClass, proguardConfig);
+
+      // With includedescriptorclasses return type, argument type ad field type are not renamed.
+      result.assertKept(ClassWithNativeMethods.class);
+      result.assertKept(NativeArgumentType.class);
+      result.assertKept(NativeReturnType.class);
+      result.assertKept(InstanceFieldType.class);
+      result.assertKept(StaticFieldType.class);
+    }
+  }
+
+  @Test
+  public void testKeepClassMembers() throws Exception {
+    for (Class mainClass : mainClasses) {
+      Path proguardConfig = writeTextToTempFile(
+          keepMain(mainClass),
+          "-keepclassmembers,includedescriptorclasses class * {  ",
+          "  <fields>;                                           ",
+          "  native <methods>;                                   ",
+          "}                                                     ",
+          "-allowaccessmodification                              "
+      );
+
+      Result result = runTest(mainClass, proguardConfig);
+
+      // With includedescriptorclasses return type and argument type are not renamed.
+      result.assertRenamed(ClassWithNativeMethods.class);
+      result.assertKept(NativeArgumentType.class);
+      result.assertKept(NativeReturnType.class);
+      result.assertKept(InstanceFieldType.class);
+      result.assertKept(StaticFieldType.class);
+    }
+  }
+
+    @Test
+    public void testKeepClassMemberNames() throws Exception {
+      for (Class mainClass : mainClasses) {
+        Path proguardConfig = writeTextToTempFile(
+            keepMain(mainClass),
+            // same as -keepclassmembers,allowshrinking,includedescriptorclasses
+            "-keepclassmembernames,includedescriptorclasses class * {  ",
+            "  <fields>;                                               ",
+            "  native <methods>;                                       ",
+            "}                                                         ",
+            "-allowaccessmodification                                  "
+        );
+
+        Result result = runTest(mainClass, proguardConfig);
+
+        boolean useNativeArgumentType =
+            mainClass == MainCallMethod1.class || mainClass == MainCallMethod3.class;
+        boolean useNativeReturnType =
+            mainClass == MainCallMethod2.class || mainClass == MainCallMethod3.class;
+
+        result.assertRenamed(ClassWithNativeMethods.class);
+        if (useNativeArgumentType) {
+          result.assertKept(NativeArgumentType.class);
+        } else {
+          result.assertRemoved(NativeArgumentType.class);
+        }
+
+        if (useNativeReturnType) {
+          result.assertKept(NativeReturnType.class);
+        } else {
+          result.assertRemoved(NativeReturnType.class);
+        }
+
+        result.assertRemoved(InstanceFieldType.class);
+        result.assertRemoved(StaticFieldType.class);
+      }
+    }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/InstanceFieldType.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/InstanceFieldType.java
new file mode 100644
index 0000000..4e7fe34
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/InstanceFieldType.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2017, 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.shaking.includedescriptorclasses;
+
+public class InstanceFieldType {
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/MainCallMethod1.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/MainCallMethod1.java
new file mode 100644
index 0000000..a4ae00f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/MainCallMethod1.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2017, 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.shaking.includedescriptorclasses;
+
+public class MainCallMethod1 {
+  public static void main(String[] args) {
+    ClassWithNativeMethods cwnm = new ClassWithNativeMethods();
+    // Don't mention the argument type.
+    cwnm.method1(null);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/MainCallMethod2.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/MainCallMethod2.java
new file mode 100644
index 0000000..f3b2fa6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/MainCallMethod2.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2017, 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.shaking.includedescriptorclasses;
+
+public class MainCallMethod2 {
+  public static void main(String[] args) {
+    ClassWithNativeMethods cwnm = new ClassWithNativeMethods();
+    // Don't mention the return type.
+    cwnm.method2();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/MainCallMethod3.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/MainCallMethod3.java
new file mode 100644
index 0000000..22d9b70
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/MainCallMethod3.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2017, 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.shaking.includedescriptorclasses;
+
+public class MainCallMethod3 {
+  public static void main(String[] args) {
+    ClassWithNativeMethods cwnm = new ClassWithNativeMethods();
+    // Don't mention the argument or return type.
+    cwnm.method1(null);
+    cwnm.method2();
+    cwnm.method3(null);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/NativeArgumentType.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/NativeArgumentType.java
new file mode 100644
index 0000000..e8af30b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/NativeArgumentType.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2017, 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.shaking.includedescriptorclasses;
+
+public class NativeArgumentType {
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/NativeReturnType.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/NativeReturnType.java
new file mode 100644
index 0000000..f68a434
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/NativeReturnType.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2017, 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.shaking.includedescriptorclasses;
+
+public class NativeReturnType {
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/StaticFieldType.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/StaticFieldType.java
new file mode 100644
index 0000000..0a83151
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/StaticFieldType.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2017, 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.shaking.includedescriptorclasses;
+
+public class StaticFieldType {
+
+}
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 39e7dd2..4e661ba 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -271,6 +271,8 @@
     public abstract String getOriginalDescriptor();
 
     public abstract String getFinalDescriptor();
+
+    public abstract boolean isRenamed();
   }
 
   private class AbsentClassSubject extends ClassSubject {
@@ -322,6 +324,11 @@
     public String getFinalDescriptor() {
       return null;
     }
+
+    @Override
+    public boolean isRenamed() {
+      return false;
+    }
   }
 
   public class FoundClassSubject extends ClassSubject {
@@ -456,6 +463,11 @@
     public String getFinalDescriptor() {
       return dexClass.type.descriptor.toString();
     }
+
+    @Override
+    public boolean isRenamed() {
+      return naming == null || !getFinalDescriptor().equals(getOriginalDescriptor());
+    }
   }
 
   public abstract class MemberSubject extends Subject {
@@ -469,6 +481,8 @@
     public abstract boolean isFinal();
 
     public abstract Signature getOriginalSignature();
+
+    public abstract Signature getFinalSignature();
   }
 
   public abstract class MethodSubject extends MemberSubject {
@@ -485,6 +499,8 @@
         Predicate<InstructionSubject> filter) {
       return null;
     }
+
+    public abstract boolean isRenamed();
   }
 
   public class AbsentMethodSubject extends MethodSubject {
@@ -495,6 +511,11 @@
     }
 
     @Override
+    public boolean isRenamed() {
+      return false;
+    }
+
+    @Override
     public boolean hasAll(DexAccessFlags flags) {
       return false;
     }
@@ -528,6 +549,11 @@
     public Signature getOriginalSignature() {
       return null;
     }
+
+    @Override
+    public Signature getFinalSignature() {
+      return null;
+    }
   }
 
   public class FoundMethodSubject extends MethodSubject {
@@ -546,6 +572,11 @@
     }
 
     @Override
+    public boolean isRenamed() {
+      return clazz.naming == null || !getFinalSignature().name.equals(getOriginalSignature().name);
+    }
+
+    @Override
     public boolean hasAll(DexAccessFlags flags) {
       return dexMethod.accessFlags.containsAllOf(flags);
     }
@@ -577,13 +608,18 @@
 
     @Override
     public MethodSignature getOriginalSignature() {
-      MethodSignature signature = MemberNaming.MethodSignature.fromDexMethod(dexMethod.method);
+      MethodSignature signature = getFinalSignature();
       return clazz.naming != null ?
           (MethodSignature) clazz.naming.lookup(signature).getOriginalSignature() :
           signature;
     }
 
     @Override
+    public MethodSignature getFinalSignature() {
+      return MemberNaming.MethodSignature.fromDexMethod(dexMethod.method);
+    }
+
+    @Override
     public Iterator<InstructionSubject> iterateInstructions() {
       return new InstructionIterator(this);
     }
@@ -633,6 +669,11 @@
     }
 
     @Override
+    public Signature getFinalSignature() {
+      return null;
+    }
+
+    @Override
     public DexEncodedField getField() {
       return null;
     }
@@ -679,13 +720,18 @@
 
     @Override
     public FieldSignature getOriginalSignature() {
-      FieldSignature signature = MemberNaming.FieldSignature.fromDexField(dexField.field);
+      FieldSignature signature = getFinalSignature();
       return clazz.naming != null ?
           (FieldSignature) clazz.naming.lookup(signature).getOriginalSignature() :
           signature;
     }
 
     @Override
+    public FieldSignature getFinalSignature() {
+      return MemberNaming.FieldSignature.fromDexField(dexField.field);
+    }
+
+    @Override
     public DexEncodedField getField() {
       return dexField;
     }