Merge "Avoid <= in version checks as it is then easy to miss MR releases."
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 4025180..ee48da2 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.3.9-dev";
+  public static final String LABEL = "1.3.10-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 8de4a6e..84ceac3 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.ir.code.Invoke.Type;
+import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableSet;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
@@ -78,7 +79,8 @@
       if (typeMap.isEmpty() && methodMap.isEmpty() && fieldMap.isEmpty()) {
         return previousLense;
       }
-      return new NestedGraphLense(typeMap, methodMap, fieldMap, previousLense, dexItemFactory);
+      return new NestedGraphLense(
+          typeMap, methodMap, fieldMap, null, null, previousLense, dexItemFactory);
     }
 
   }
@@ -87,6 +89,10 @@
     return new Builder();
   }
 
+  public abstract DexField getOriginalFieldSignature(DexField field);
+
+  public abstract DexMethod getOriginalMethodSignature(DexMethod method);
+
   public abstract DexType lookupType(DexType type);
 
   // This overload can be used when the graph lense is known to be context insensitive.
@@ -156,6 +162,16 @@
   private static class IdentityGraphLense extends GraphLense {
 
     @Override
+    public DexField getOriginalFieldSignature(DexField field) {
+      return field;
+    }
+
+    @Override
+    public DexMethod getOriginalMethodSignature(DexMethod method) {
+      return method;
+    }
+
+    @Override
     public DexType lookupType(DexType type) {
       return type;
     }
@@ -197,16 +213,47 @@
     protected final Map<DexMethod, DexMethod> methodMap;
     protected final Map<DexField, DexField> fieldMap;
 
-    public NestedGraphLense(Map<DexType, DexType> typeMap, Map<DexMethod, DexMethod> methodMap,
-        Map<DexField, DexField> fieldMap, GraphLense previousLense, DexItemFactory dexItemFactory) {
+    // Maps that store the original signature of fields and methods that have been affected by
+    // vertical class merging. Needed to generate a correct Proguard map in the end.
+    private final BiMap<DexField, DexField> originalFieldSignatures;
+    private final BiMap<DexMethod, DexMethod> originalMethodSignatures;
+
+    public NestedGraphLense(
+        Map<DexType, DexType> typeMap,
+        Map<DexMethod, DexMethod> methodMap,
+        Map<DexField, DexField> fieldMap,
+        BiMap<DexField, DexField> originalFieldSignatures,
+        BiMap<DexMethod, DexMethod> originalMethodSignatures,
+        GraphLense previousLense,
+        DexItemFactory dexItemFactory) {
       this.typeMap = typeMap.isEmpty() ? null : typeMap;
       this.methodMap = methodMap;
       this.fieldMap = fieldMap;
+      this.originalFieldSignatures = originalFieldSignatures;
+      this.originalMethodSignatures = originalMethodSignatures;
       this.previousLense = previousLense;
       this.dexItemFactory = dexItemFactory;
     }
 
     @Override
+    public DexField getOriginalFieldSignature(DexField field) {
+      DexField originalField =
+          originalFieldSignatures != null
+              ? originalFieldSignatures.getOrDefault(field, field)
+              : field;
+      return previousLense.getOriginalFieldSignature(originalField);
+    }
+
+    @Override
+    public DexMethod getOriginalMethodSignature(DexMethod method) {
+      DexMethod originalMethod =
+          originalMethodSignatures != null
+              ? originalMethodSignatures.getOrDefault(method, method)
+              : method;
+      return previousLense.getOriginalMethodSignature(originalMethod);
+    }
+
+    @Override
     public DexType lookupType(DexType type) {
       if (type.isArrayType()) {
         synchronized (this) {
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 5b908af..c5d366a 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -25,7 +25,10 @@
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
 
@@ -150,7 +153,12 @@
   }
 
   public void write(Writer writer) throws IOException {
-    for (ClassNamingForNameMapper naming : classNameMappings.values()) {
+    // Sort classes by their original name such that the generated Proguard map is deterministic
+    // (and easy to navigate manually).
+    List<ClassNamingForNameMapper> classNamingForNameMappers =
+        new ArrayList<>(classNameMappings.values());
+    classNamingForNameMappers.sort(Comparator.comparing(x -> x.originalName));
+    for (ClassNamingForNameMapper naming : classNamingForNameMappers) {
       naming.write(writer);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
index 492bcd0..6ba18ac 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize;
 
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -88,6 +89,17 @@
     }
 
     @Override
+    public DexField getOriginalFieldSignature(DexField field) {
+      return previousLense.getOriginalFieldSignature(field);
+    }
+
+    @Override
+    public DexMethod getOriginalMethodSignature(DexMethod method) {
+      // TODO(b/79143143): implement this when re-enable bridge analysis.
+      throw new Unimplemented("BridgeLense.getOriginalMethodSignature() not implemented");
+    }
+
+    @Override
     public DexType lookupType(DexType type) {
       return previousLense.lookupType(type);
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
index 4ee0cee..98a0da4 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
@@ -38,7 +38,8 @@
       Map<DexMethod, DexMethod> methodMap,
       Map<DexField, DexField> fieldMap,
       GraphLense previousLense) {
-    super(ImmutableMap.of(), methodMap, fieldMap, previousLense, appInfo.dexItemFactory);
+    super(
+        ImmutableMap.of(), methodMap, fieldMap, null, null, previousLense, appInfo.dexItemFactory);
     this.appInfo = appInfo;
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
index 7a9c08f..ad7effd 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
@@ -25,6 +25,8 @@
         ImmutableMap.of(),
         ImmutableMap.of(),
         ImmutableMap.of(),
+        null,
+        null,
         appView.getGraphLense(),
         appView.getAppInfo().dexItemFactory);
     this.appView = appView;
diff --git a/src/main/java/com/android/tools/r8/shaking/InlineRule.java b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
index 565856c..f263bd6 100644
--- a/src/main/java/com/android/tools/r8/shaking/InlineRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
@@ -4,6 +4,8 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import java.util.List;
 
 public class InlineRule extends ProguardConfigurationRule {
@@ -12,20 +14,27 @@
     ALWAYS, FORCE, NEVER
   }
 
-  public static class Builder extends ProguardConfigurationRule.Builder {
+  public static class Builder extends ProguardConfigurationRule.Builder<InlineRule, Builder> {
 
     private Builder() {
+      super();
     }
 
     Type type;
 
+    @Override
+    public Builder self() {
+      return this;
+    }
+
     public Builder setType(Type type) {
       this.type = type;
       return this;
     }
 
+    @Override
     public InlineRule build() {
-      return new InlineRule(classAnnotation, classAccessFlags,
+      return new InlineRule(origin, getPosition(), source, classAnnotation, classAccessFlags,
           negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
           inheritanceClassName, inheritanceIsExtends, memberRules, type);
     }
@@ -34,6 +43,9 @@
   private final Type type;
 
   private InlineRule(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -45,13 +57,14 @@
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules,
       Type type) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules);
     this.type = type;
   }
 
-  public static InlineRule.Builder builder() {
-    return new InlineRule.Builder();
+  public static Builder builder() {
+    return new Builder();
   }
 
   public Type getType() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java
index 39d0335..4c1f9ef 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java
@@ -3,22 +3,36 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import java.util.List;
 
 public class ProguardAssumeNoSideEffectRule extends ProguardConfigurationRule {
 
-  public static class Builder extends ProguardClassSpecification.Builder {
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<ProguardAssumeNoSideEffectRule, Builder> {
 
-    private Builder() {}
+    private Builder() {
+      super();
+    }
 
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
     public ProguardAssumeNoSideEffectRule build() {
-      return new ProguardAssumeNoSideEffectRule(classAnnotation, classAccessFlags,
-          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
-          inheritanceClassName, inheritanceIsExtends, memberRules);
+      return new ProguardAssumeNoSideEffectRule(origin, getPosition(), source, classAnnotation,
+          classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
+          inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
     }
   }
 
   private ProguardAssumeNoSideEffectRule(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -29,8 +43,9 @@
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java
index 0dafb11..8b8f4dd 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java
@@ -3,21 +3,36 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import java.util.List;
 
 public class ProguardAssumeValuesRule extends ProguardConfigurationRule {
-  public static class Builder extends ProguardClassSpecification.Builder {
 
-    private Builder() {}
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<ProguardAssumeValuesRule, Builder> {
 
+    private Builder() {
+      super();
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
     public ProguardAssumeValuesRule build() {
-      return new ProguardAssumeValuesRule(classAnnotation, classAccessFlags, negatedClassAccessFlags,
-          classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
-          inheritanceIsExtends, memberRules);
+      return new ProguardAssumeValuesRule(origin, getPosition(), source, classAnnotation,
+          classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
+          inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
     }
   }
 
   private ProguardAssumeValuesRule(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -28,8 +43,9 @@
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
index 2461732..664cb25 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
@@ -3,23 +3,36 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import java.util.List;
 
 public class ProguardCheckDiscardRule extends ProguardConfigurationRule {
 
-  public static class Builder extends ProguardConfigurationRule.Builder {
+  public static class Builder extends
+      ProguardConfigurationRule.Builder<ProguardCheckDiscardRule, Builder> {
 
     private Builder() {
+      super();
     }
 
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
     public ProguardCheckDiscardRule build() {
-      return new ProguardCheckDiscardRule(classAnnotation, classAccessFlags,
-          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
-          inheritanceClassName, inheritanceIsExtends, memberRules);
+      return new ProguardCheckDiscardRule(origin, getPosition(), source, classAnnotation,
+          classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
+          inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
     }
   }
 
   private ProguardCheckDiscardRule(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -30,8 +43,9 @@
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules);
   }
 
   public static Builder builder() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
index 8de361e..5d610a8 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
@@ -3,6 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.position.TextPosition;
+import com.android.tools.r8.position.TextRange;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.LinkedList;
@@ -11,8 +15,13 @@
 
 public abstract class ProguardClassSpecification {
 
-  public static class Builder {
+  public abstract static class
+  Builder<C extends ProguardClassSpecification, B extends Builder<C, B>> {
 
+    protected Origin origin;
+    protected Position start;
+    protected Position end;
+    protected String source;
     protected ProguardTypeMatcher classAnnotation;
     protected ProguardAccessFlags classAccessFlags = new ProguardAccessFlags();
     protected ProguardAccessFlags negatedClassAccessFlags = new ProguardAccessFlags();
@@ -25,6 +34,46 @@
     protected List<ProguardMemberRule> memberRules = new LinkedList<>();
 
     protected Builder() {
+      this(Origin.unknown(), Position.UNKNOWN);
+    }
+
+    protected Builder(Origin origin, Position start) {
+      this.origin = origin;
+      this.start = start;
+    }
+
+    public abstract C build();
+
+    public abstract B self();
+
+    public B setOrigin(Origin origin) {
+      this.origin = origin;
+      return self();
+    }
+
+    public B setStart(Position start) {
+      this.start = start;
+      return self();
+    }
+
+    public B setEnd(Position end) {
+      this.end = end;
+      return self();
+    }
+
+    public B setSource(String source) {
+      this.source = source;
+      return self();
+    }
+
+    public Position getPosition() {
+      if (start == null) {
+        return Position.UNKNOWN;
+      }
+      if (end == null || !((start instanceof TextPosition) && (end instanceof TextPosition))) {
+        return start;
+      }
+      return new TextRange((TextPosition) start, (TextPosition) end);
     }
 
     public List<ProguardMemberRule> getMemberRules() {
@@ -117,6 +166,9 @@
     }
   }
 
+  private final Origin origin;
+  private final Position position;
+  private final String source;
   private final ProguardTypeMatcher classAnnotation;
   private final ProguardAccessFlags classAccessFlags;
   private final ProguardAccessFlags negatedClassAccessFlags;
@@ -129,6 +181,9 @@
   private final List<ProguardMemberRule> memberRules;
 
   protected ProguardClassSpecification(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -139,6 +194,9 @@
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
+    this.origin = origin;
+    this.position = position;
+    this.source =source;
     this.classAnnotation = classAnnotation;
     this.classAccessFlags = classAccessFlags;
     this.negatedClassAccessFlags = negatedClassAccessFlags;
@@ -152,6 +210,18 @@
     this.memberRules = memberRules;
   }
 
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  public Position getPosition() {
+    return position;
+  }
+
+  public String getSource() {
+    return source;
+  }
+
   public List<ProguardMemberRule> getMemberRules() {
     return memberRules;
   }
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 5b22456..e070db9 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -228,7 +228,7 @@
       } else if (acceptString("keepparameternames")) {
         configurationBuilder.setKeepParameterNames(true, origin, getPosition(optionStart));
       } else if (acceptString("checkdiscard")) {
-        ProguardCheckDiscardRule rule = parseCheckDiscardRule();
+        ProguardCheckDiscardRule rule = parseCheckDiscardRule(optionStart);
         configurationBuilder.addRule(rule);
       } else if (acceptString("keepdirectories")) {
         // TODO(74279367): Report an error until it's fully supported.
@@ -237,10 +237,10 @@
         }
         parsePathFilter(configurationBuilder::addKeepDirectories);
       } else if (acceptString("keep")) {
-        ProguardKeepRule rule = parseKeepRule();
+        ProguardKeepRule rule = parseKeepRule(optionStart);
         configurationBuilder.addRule(rule);
       } else if (acceptString("whyareyoukeeping")) {
-        ProguardWhyAreYouKeepingRule rule = parseWhyAreYouKeepingRule();
+        ProguardWhyAreYouKeepingRule rule = parseWhyAreYouKeepingRule(optionStart);
         configurationBuilder.addRule(rule);
       } else if (acceptString("dontoptimize")) {
         configurationBuilder.disableOptimization();
@@ -316,10 +316,10 @@
       } else if (acceptString("applymapping")) {
         configurationBuilder.setApplyMappingFile(parseFileName());
       } else if (acceptString("assumenosideeffects")) {
-        ProguardAssumeNoSideEffectRule rule = parseAssumeNoSideEffectsRule();
+        ProguardAssumeNoSideEffectRule rule = parseAssumeNoSideEffectsRule(optionStart);
         configurationBuilder.addRule(rule);
       } else if (acceptString("assumevalues")) {
-        ProguardAssumeValuesRule rule = parseAssumeValuesRule();
+        ProguardAssumeValuesRule rule = parseAssumeValuesRule(optionStart);
         configurationBuilder.addRule(rule);
       } else if (acceptString("include")) {
         // Collect the parsed configuration until the include.
@@ -348,16 +348,16 @@
       } else if (acceptString("packageobfuscationdictionary")) {
         configurationBuilder.setPackageObfuscationDictionary(parseFileName());
       } else if (acceptString("alwaysinline")) {
-        InlineRule rule = parseInlineRule(Type.ALWAYS);
+        InlineRule rule = parseInlineRule(Type.ALWAYS, optionStart);
         configurationBuilder.addRule(rule);
       } else if (allowTestOptions && acceptString("forceinline")) {
-        InlineRule rule = parseInlineRule(Type.FORCE);
+        InlineRule rule = parseInlineRule(Type.FORCE, optionStart);
         configurationBuilder.addRule(rule);
         // Insert a matching -checkdiscard rule to ensure force inlining happens.
         ProguardCheckDiscardRule ruled = rule.asProguardCheckDiscardRule();
         configurationBuilder.addRule(ruled);
       } else if (allowTestOptions && acceptString("neverinline")) {
-        InlineRule rule = parseInlineRule(Type.NEVER);
+        InlineRule rule = parseInlineRule(Type.NEVER, optionStart);
         configurationBuilder.addRule(rule);
       } else if (acceptString("useuniqueclassmembernames")) {
         configurationBuilder.setUseUniqueClassMemberNames(true);
@@ -376,7 +376,7 @@
         }
         parsePathFilter(configurationBuilder::addAdaptResourceFilecontents);
       } else if (acceptString("identifiernamestring")) {
-        configurationBuilder.addRule(parseIdentifierNameStringRule());
+        configurationBuilder.addRule(parseIdentifierNameStringRule(optionStart));
       } else if (acceptString("if")) {
         configurationBuilder.addRule(parseIfRule(optionStart));
       } else {
@@ -545,9 +545,11 @@
       }
     }
 
-    private ProguardKeepRule parseKeepRule()
+    private ProguardKeepRule parseKeepRule(Position start)
         throws ProguardRuleParserException {
-      ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
+      ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder()
+          .setOrigin(origin)
+          .setStart(start);
       parseRuleTypeAndModifiers(keepRuleBuilder);
       parseClassSpec(keepRuleBuilder, false);
       if (keepRuleBuilder.getMemberRules().isEmpty()) {
@@ -560,13 +562,21 @@
         defaultRuleBuilder.setArguments(Collections.emptyList());
         keepRuleBuilder.getMemberRules().add(defaultRuleBuilder.build());
       }
+      Position end = getPosition();
+      keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+      keepRuleBuilder.setEnd(end);
       return keepRuleBuilder.build();
     }
 
-    private ProguardWhyAreYouKeepingRule parseWhyAreYouKeepingRule()
+    private ProguardWhyAreYouKeepingRule parseWhyAreYouKeepingRule(Position start)
         throws ProguardRuleParserException {
-      ProguardWhyAreYouKeepingRule.Builder keepRuleBuilder = ProguardWhyAreYouKeepingRule.builder();
+      ProguardWhyAreYouKeepingRule.Builder keepRuleBuilder = ProguardWhyAreYouKeepingRule.builder()
+          .setOrigin(origin)
+          .setStart(start);
       parseClassSpec(keepRuleBuilder, false);
+      Position end = getPosition();
+      keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+      keepRuleBuilder.setEnd(end);
       return keepRuleBuilder.build();
     }
 
@@ -577,37 +587,56 @@
       return keepRuleBuilder.build();
     }
 
-    private ProguardCheckDiscardRule parseCheckDiscardRule()
+    private ProguardCheckDiscardRule parseCheckDiscardRule(Position start)
         throws ProguardRuleParserException {
-      ProguardCheckDiscardRule.Builder keepRuleBuilder = ProguardCheckDiscardRule.builder();
+      ProguardCheckDiscardRule.Builder keepRuleBuilder = ProguardCheckDiscardRule.builder()
+          .setOrigin(origin)
+          .setStart(start);
       parseClassSpec(keepRuleBuilder, false);
+      Position end = getPosition();
+      keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+      keepRuleBuilder.setEnd(end);
       return keepRuleBuilder.build();
     }
 
-    private InlineRule parseInlineRule(InlineRule.Type type)
+    private InlineRule parseInlineRule(InlineRule.Type type, Position start)
         throws ProguardRuleParserException {
-      InlineRule.Builder keepRuleBuilder = InlineRule.builder().setType(type);
+      InlineRule.Builder keepRuleBuilder = InlineRule.builder()
+          .setOrigin(origin)
+          .setStart(start)
+          .setType(type);
       parseClassSpec(keepRuleBuilder, false);
+      Position end = getPosition();
+      keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+      keepRuleBuilder.setEnd(end);
       return keepRuleBuilder.build();
     }
 
-    private ProguardIdentifierNameStringRule parseIdentifierNameStringRule()
+    private ProguardIdentifierNameStringRule parseIdentifierNameStringRule(Position start)
         throws ProguardRuleParserException {
       ProguardIdentifierNameStringRule.Builder keepRuleBuilder =
-          ProguardIdentifierNameStringRule.builder();
+          ProguardIdentifierNameStringRule.builder()
+              .setOrigin(origin)
+              .setStart(start);
       parseClassSpec(keepRuleBuilder, false);
+      Position end = getPosition();
+      keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+      keepRuleBuilder.setEnd(end);
       return keepRuleBuilder.build();
     }
 
     private ProguardIfRule parseIfRule(TextPosition optionStart)
         throws ProguardRuleParserException {
-      ProguardIfRule.Builder ifRuleBuilder = ProguardIfRule.builder();
+      ProguardIfRule.Builder ifRuleBuilder = ProguardIfRule.builder()
+          .setOrigin(origin)
+          .setStart(optionStart);
       parseClassSpec(ifRuleBuilder, false);
 
       // Required a subsequent keep rule.
       skipWhitespace();
+      Position keepStart = getPosition();
       if (acceptString("-keep")) {
-        ProguardKeepRule subsequentRule = parseKeepRule();
+        ProguardKeepRule subsequentRule = parseKeepRule(keepStart);
         ifRuleBuilder.setSubsequentRule(subsequentRule);
         ProguardIfRule ifRule = ifRuleBuilder.build();
         verifyAndLinkBackReferences(ifRule.getWildcards());
@@ -636,8 +665,11 @@
       }
     }
 
-    private void parseClassSpec(
-        ProguardConfigurationRule.Builder builder, boolean allowValueSpecification)
+    private
+    <C extends ProguardClassSpecification, B extends ProguardClassSpecification.Builder<C, B>>
+    void parseClassSpec(
+        ProguardClassSpecification.Builder<C, B> builder,
+        boolean allowValueSpecification)
         throws ProguardRuleParserException {
       parseClassFlagsAndAnnotations(builder);
       parseClassType(builder);
@@ -646,8 +678,7 @@
       parseMemberRules(builder, allowValueSpecification);
     }
 
-    private void parseRuleTypeAndModifiers(ProguardKeepRule.Builder builder)
-        throws ProguardRuleParserException {
+    private void parseRuleTypeAndModifiers(ProguardKeepRule.Builder builder) {
       if (acceptString("names")) {
         builder.setType(ProguardKeepRuleType.KEEP);
         builder.getModifiersBuilder().setAllowsShrinking(true);
@@ -800,12 +831,15 @@
           ClassOrType.CLASS, dexItemFactory));
     }
 
-    private void parseMemberRules(ProguardClassSpecification.Builder classSpecificationBuilder,
+    private
+    <C extends ProguardClassSpecification, B extends ProguardClassSpecification.Builder<C, B>>
+    void parseMemberRules(
+        ProguardClassSpecification.Builder<C, B> classSpecificationBuilder,
         boolean allowValueSpecification)
         throws ProguardRuleParserException {
       skipWhitespace();
       if (!eof() && acceptChar('{')) {
-        ProguardMemberRule rule = null;
+        ProguardMemberRule rule;
         while ((rule = parseMemberRule(allowValueSpecification)) != null) {
           classSpecificationBuilder.getMemberRules().add(rule);
         }
@@ -1086,16 +1120,27 @@
       return fileFilter;
     }
 
-    private ProguardAssumeNoSideEffectRule parseAssumeNoSideEffectsRule()
+    private ProguardAssumeNoSideEffectRule parseAssumeNoSideEffectsRule(Position start)
         throws ProguardRuleParserException {
-      ProguardAssumeNoSideEffectRule.Builder builder = ProguardAssumeNoSideEffectRule.builder();
+      ProguardAssumeNoSideEffectRule.Builder builder = ProguardAssumeNoSideEffectRule.builder()
+          .setOrigin(origin)
+          .setStart(start);
       parseClassSpec(builder, true);
+      Position end = getPosition();
+      builder.setSource(getSourceSnippet(contents, start, end));
+      builder.setEnd(end);
       return builder.build();
     }
 
-    private ProguardAssumeValuesRule parseAssumeValuesRule() throws ProguardRuleParserException {
-      ProguardAssumeValuesRule.Builder builder = ProguardAssumeValuesRule.builder();
+    private ProguardAssumeValuesRule parseAssumeValuesRule(Position start)
+        throws ProguardRuleParserException {
+      ProguardAssumeValuesRule.Builder builder = ProguardAssumeValuesRule.builder()
+          .setOrigin(origin)
+          .setStart(start);
       parseClassSpec(builder, true);
+      Position end = getPosition();
+      builder.setSource(getSourceSnippet(contents, start, end));
+      builder.setEnd(end);
       return builder.build();
     }
 
@@ -1549,7 +1594,24 @@
     private int getColumn() {
       return position - lineStartPosition + 1 /* column starts at 1 */;
     }
-  }
+
+    private String getSourceSnippet(String source, Position start, Position end) {
+      return start instanceof TextPosition && end instanceof TextPosition
+          ? getTextSourceSnippet(source, (TextPosition) start, (TextPosition) end)
+          : null;
+      }
+    }
+
+    private String getTextSourceSnippet(String source, TextPosition start, TextPosition end) {
+      long length = end.getOffset() - start.getOffset();
+      if (start.getOffset() < 0 || end.getOffset() < 0
+          || start.getOffset() >= source.length() || end.getOffset() > source.length()
+          || length <= 0) {
+        return null;
+      } else {
+        return source.substring((int) start.getOffset(), (int) end.getOffset());
+      }
+    }
 
   static class IdentifierPatternWithWildcards {
     final String pattern;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index e4c5691..f7282ff 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.Iterables;
 import java.util.Collections;
@@ -11,6 +13,9 @@
 
 public abstract class ProguardConfigurationRule extends ProguardClassSpecification {
   ProguardConfigurationRule(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -21,8 +26,9 @@
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules);
   }
 
   abstract String typeString();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
index 07ffd85..8ff7670 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
@@ -3,22 +3,35 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import java.util.List;
 
 public class ProguardIdentifierNameStringRule extends ProguardConfigurationRule {
 
-  public static class Builder extends ProguardConfigurationRule.Builder {
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<ProguardIdentifierNameStringRule, Builder> {
     private Builder() {
+      super();
     }
 
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
     public ProguardIdentifierNameStringRule build() {
-      return new ProguardIdentifierNameStringRule(classAnnotation, classAccessFlags,
-          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
-          inheritanceClassName, inheritanceIsExtends, memberRules);
+      return new ProguardIdentifierNameStringRule(origin, getPosition(), source, classAnnotation,
+          classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
+          inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
     }
   }
 
   private ProguardIdentifierNameStringRule(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -29,8 +42,9 @@
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules);
   }
 
   public static Builder builder() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index 47f26ed..54a3f2f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -3,18 +3,29 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import com.google.common.collect.Iterables;
 import java.util.List;
 import java.util.stream.Collectors;
 
-public class ProguardIfRule extends ProguardKeepRule {
+public class ProguardIfRule extends ProguardKeepRuleBase {
 
   final ProguardKeepRule subsequentRule;
 
-  public static class Builder extends ProguardKeepRule.Builder {
+  public static class Builder extends ProguardKeepRuleBase.Builder<ProguardIfRule, Builder> {
 
     ProguardKeepRule subsequentRule = null;
 
+    protected Builder() {
+      super();
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
     public void setSubsequentRule(ProguardKeepRule rule) {
       subsequentRule = rule;
     }
@@ -22,13 +33,17 @@
     @Override
     public ProguardIfRule build() {
       assert subsequentRule != null : "Option -if without a subsequent rule.";
-      return new ProguardIfRule(classAnnotation, classAccessFlags,
+      return new ProguardIfRule(origin, getPosition(), source, classAnnotation, classAccessFlags,
           negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
           inheritanceClassName, inheritanceIsExtends, memberRules, subsequentRule);
     }
   }
 
-  private ProguardIfRule(ProguardTypeMatcher classAnnotation,
+  private ProguardIfRule(
+      Origin origin,
+      Position position,
+      String source,
+      ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags, boolean classTypeNegated,
       ProguardClassType classType, ProguardClassNameList classNames,
@@ -36,8 +51,9 @@
       ProguardTypeMatcher inheritanceClassName, boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules,
       ProguardKeepRule subsequentRule) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules,
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules,
         ProguardKeepRuleType.CONDITIONAL, ProguardKeepRuleModifiers.builder().build());
     this.subsequentRule = subsequentRule;
   }
@@ -51,9 +67,11 @@
     return Iterables.concat(super.getWildcards(), subsequentRule.getWildcards());
   }
 
-  @Override
   protected ProguardIfRule materialize() {
     return new ProguardIfRule(
+        Origin.unknown(),
+        Position.UNKNOWN,
+        null,
         getClassAnnotation(),
         getClassAccessFlags(),
         getNegatedClassAccessFlags(),
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java
index 66fdd50..767ecf7 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java
@@ -3,23 +3,36 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import java.util.List;
 
 public class ProguardKeepPackageNamesRule extends ProguardConfigurationRule {
 
-  public static class Builder extends ProguardConfigurationRule.Builder {
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<ProguardKeepPackageNamesRule, Builder> {
 
     private Builder() {
+      super();
     }
 
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
     public ProguardKeepPackageNamesRule build() {
-      return new ProguardKeepPackageNamesRule(classAnnotation, classAccessFlags,
-          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
-          inheritanceClassName, inheritanceIsExtends, memberRules);
+      return new ProguardKeepPackageNamesRule(origin, getPosition(), source, classAnnotation,
+          classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
+          inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
     }
   }
 
   private ProguardKeepPackageNamesRule(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -30,12 +43,13 @@
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules);
   }
 
-  public static ProguardKeepPackageNamesRule.Builder builder() {
-    return new ProguardKeepPackageNamesRule.Builder();
+  public static Builder builder() {
+    return new Builder();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
index 6651dae..3c1fb2d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
@@ -3,39 +3,37 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
-public class ProguardKeepRule extends ProguardConfigurationRule {
+public class ProguardKeepRule extends ProguardKeepRuleBase {
 
-  public static class Builder extends ProguardClassSpecification.Builder {
+  public static class Builder extends ProguardKeepRuleBase.Builder<ProguardKeepRule, Builder> {
 
-    private ProguardKeepRuleType type;
-    private final ProguardKeepRuleModifiers.Builder modifiersBuilder =
-        ProguardKeepRuleModifiers.builder();
-
-    protected Builder() {}
-
-    public void setType(ProguardKeepRuleType type) {
-      this.type = type;
+    protected Builder() {
+      super();
     }
 
-    public ProguardKeepRuleModifiers.Builder getModifiersBuilder() {
-      return modifiersBuilder;
+    @Override
+    public Builder self() {
+      return this;
     }
 
+    @Override
     public ProguardKeepRule build() {
-      return new ProguardKeepRule(classAnnotation, classAccessFlags, negatedClassAccessFlags,
-          classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
-          inheritanceIsExtends, memberRules, type, modifiersBuilder.build());
+      return new ProguardKeepRule(origin, getPosition(), source, classAnnotation, classAccessFlags,
+          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
+          inheritanceClassName, inheritanceIsExtends, memberRules, type, modifiersBuilder.build());
     }
   }
 
-  private final ProguardKeepRuleType type;
-  private final ProguardKeepRuleModifiers modifiers;
-
   protected ProguardKeepRule(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -48,10 +46,9 @@
       List<ProguardMemberRule> memberRules,
       ProguardKeepRuleType type,
       ProguardKeepRuleModifiers modifiers) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
-    this.type = type;
-    this.modifiers = modifiers;
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules, type, modifiers);
   }
 
   /**
@@ -61,16 +58,11 @@
     return new Builder();
   }
 
-  public ProguardKeepRuleType getType() {
-    return type;
-  }
-
-  public ProguardKeepRuleModifiers getModifiers() {
-    return modifiers;
-  }
-
   protected ProguardKeepRule materialize() {
     return new ProguardKeepRule(
+        Origin.unknown(),
+        Position.UNKNOWN,
+        null,
         getClassAnnotation(),
         getClassAccessFlags(),
         getNegatedClassAccessFlags(),
@@ -93,25 +85,9 @@
       return false;
     }
     ProguardKeepRule that = (ProguardKeepRule) o;
-
-    if (type != that.type) {
-      return false;
-    }
-    if (!modifiers.equals(that.modifiers)) {
-      return false;
-    }
     return super.equals(that);
   }
 
-  @Override
-  public int hashCode() {
-    // Used multiplier 3 to avoid too much overflow when computing hashCode.
-    int result = type.hashCode();
-    result = 3 * result + modifiers.hashCode();
-    result = 3 * result + super.hashCode();
-    return result;
-  }
-
   static void appendNonEmpty(StringBuilder builder, String pre, Object item, String post) {
     if (item == null) {
       return;
@@ -128,16 +104,6 @@
     }
   }
 
-  @Override
-  String typeString() {
-    return type.toString();
-  }
-
-  @Override
-  String modifierString() {
-    return modifiers.toString();
-  }
-
   public static ProguardKeepRule defaultKeepAllRule(
       Consumer<ProguardKeepRuleModifiers.Builder> modifiers) {
     ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
new file mode 100644
index 0000000..f67634e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2016, 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;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class ProguardKeepRuleBase extends ProguardConfigurationRule {
+
+  public static abstract class Builder<C extends ProguardKeepRuleBase, B extends Builder<C, B>>
+      extends ProguardConfigurationRule.Builder<C, B> {
+
+    protected ProguardKeepRuleType type;
+    protected final ProguardKeepRuleModifiers.Builder modifiersBuilder =
+        ProguardKeepRuleModifiers.builder();
+
+    protected Builder() {
+      super();
+    }
+
+    public void setType(ProguardKeepRuleType type) {
+      this.type = type;
+    }
+
+    public ProguardKeepRuleModifiers.Builder getModifiersBuilder() {
+      return modifiersBuilder;
+    }
+  }
+
+  private final ProguardKeepRuleType type;
+  private final ProguardKeepRuleModifiers modifiers;
+
+  protected ProguardKeepRuleBase(
+      Origin origin,
+      Position position,
+      String source,
+      ProguardTypeMatcher classAnnotation,
+      ProguardAccessFlags classAccessFlags,
+      ProguardAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      ProguardClassNameList classNames,
+      ProguardTypeMatcher inheritanceAnnotation,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      List<ProguardMemberRule> memberRules,
+      ProguardKeepRuleType type,
+      ProguardKeepRuleModifiers modifiers) {
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules);
+    this.type = type;
+    this.modifiers = modifiers;
+  }
+
+  public ProguardKeepRuleType getType() {
+    return type;
+  }
+
+  public ProguardKeepRuleModifiers getModifiers() {
+    return modifiers;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof ProguardKeepRuleBase)) {
+      return false;
+    }
+    ProguardKeepRuleBase that = (ProguardKeepRuleBase) o;
+
+    if (type != that.type) {
+      return false;
+    }
+    if (!modifiers.equals(that.modifiers)) {
+      return false;
+    }
+    return super.equals(that);
+  }
+
+  @Override
+  public int hashCode() {
+    // Used multiplier 3 to avoid too much overflow when computing hashCode.
+    int result = type.hashCode();
+    result = 3 * result + modifiers.hashCode();
+    result = 3 * result + super.hashCode();
+    return result;
+  }
+
+  static void appendNonEmpty(StringBuilder builder, String pre, Object item, String post) {
+    if (item == null) {
+      return;
+    }
+    String text = item.toString();
+    if (!text.isEmpty()) {
+      if (pre != null) {
+        builder.append(pre);
+      }
+      builder.append(text);
+      if (post != null) {
+        builder.append(post);
+      }
+    }
+  }
+
+  @Override
+  String typeString() {
+    return type.toString();
+  }
+
+  @Override
+  String modifierString() {
+    return modifiers.toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
index 73b5538..87200fb 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
@@ -3,23 +3,36 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import java.util.List;
 
 public class ProguardWhyAreYouKeepingRule extends ProguardConfigurationRule {
 
-  public static class Builder extends ProguardConfigurationRule.Builder {
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<ProguardWhyAreYouKeepingRule, Builder> {
 
     private Builder() {
+      super();
     }
 
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
     public ProguardWhyAreYouKeepingRule build() {
-      return new ProguardWhyAreYouKeepingRule(classAnnotation, classAccessFlags,
-          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
-          inheritanceClassName, inheritanceIsExtends, memberRules);
+      return new ProguardWhyAreYouKeepingRule(origin, getPosition(), source, classAnnotation,
+          classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
+          inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
     }
   }
 
   private ProguardWhyAreYouKeepingRule(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -30,12 +43,13 @@
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules);
   }
 
-  public static ProguardWhyAreYouKeepingRule.Builder builder() {
-    return new ProguardWhyAreYouKeepingRule.Builder();
+  public static Builder builder() {
+    return new Builder();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index a19c0d5..9973121 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -773,6 +773,7 @@
                   directMethod.isClassInitializer() ? Rename.NEVER : Rename.IF_NEEDED);
           add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get());
           deferredRenamings.map(directMethod.method, resultingDirectMethod.method);
+          deferredRenamings.recordMove(directMethod.method, resultingDirectMethod.method);
 
           if (!directMethod.isStaticMethod()) {
             blockRedirectionOfSuperCalls(resultingDirectMethod.method);
@@ -802,6 +803,7 @@
             DexEncodedMethod resultingVirtualMethod =
                 renameMethod(virtualMethod, availableMethodSignatures, Rename.NEVER);
             deferredRenamings.map(virtualMethod.method, resultingVirtualMethod.method);
+            deferredRenamings.recordMove(virtualMethod.method, resultingVirtualMethod.method);
             add(virtualMethods, resultingVirtualMethod, MethodSignatureEquivalence.get());
             continue;
           }
@@ -856,6 +858,7 @@
         }
 
         deferredRenamings.map(virtualMethod.method, shadowedBy.method);
+        deferredRenamings.recordMove(virtualMethod.method, resultingDirectMethod.method);
       }
 
       if (abortMerge) {
@@ -1155,6 +1158,7 @@
       DexEncodedMethod result = method.toTypeSubstitutedMethod(newSignature);
       result.markForceInline();
       deferredRenamings.map(method.method, result.method);
+      deferredRenamings.recordMove(method.method, result.method);
       // Renamed constructors turn into ordinary private functions. They can be private, as
       // they are only references from their direct subclass, which they were merged into.
       result.accessFlags.unsetConstructor();
@@ -1510,6 +1514,16 @@
     }
 
     @Override
+    public DexField getOriginalFieldSignature(DexField field) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public DexMethod getOriginalMethodSignature(DexMethod method) {
+      throw new Unreachable();
+    }
+
+    @Override
     public DexType lookupType(DexType type) {
       return type == source ? target : mergedClasses.getOrDefault(type, type);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index 5974e60..e074c25 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -14,6 +14,9 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.ir.code.Invoke.Type;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import java.util.HashMap;
@@ -57,8 +60,17 @@
       Map<DexMethod, DexMethod> methodMap,
       Set<DexMethod> mergedMethods,
       Map<DexType, Map<DexMethod, GraphLenseLookupResult>> contextualVirtualToDirectMethodMaps,
+      BiMap<DexField, DexField> originalFieldSignatures,
+      BiMap<DexMethod, DexMethod> originalMethodSignatures,
       GraphLense previousLense) {
-    super(ImmutableMap.of(), methodMap, fieldMap, previousLense, appInfo.dexItemFactory);
+    super(
+        ImmutableMap.of(),
+        methodMap,
+        fieldMap,
+        originalFieldSignatures,
+        originalMethodSignatures,
+        previousLense,
+        appInfo.dexItemFactory);
     this.appInfo = appInfo;
     this.mergedMethods = mergedMethods;
     this.contextualVirtualToDirectMethodMaps = contextualVirtualToDirectMethodMaps;
@@ -138,12 +150,14 @@
   public static class Builder {
     private final AppInfo appInfo;
 
-    protected final Map<DexField, DexField> fieldMap = new HashMap<>();
+    protected final BiMap<DexField, DexField> fieldMap = HashBiMap.create();
     protected final Map<DexMethod, DexMethod> methodMap = new HashMap<>();
     private final ImmutableSet.Builder<DexMethod> mergedMethodsBuilder = ImmutableSet.builder();
     private final Map<DexType, Map<DexMethod, GraphLenseLookupResult>>
         contextualVirtualToDirectMethodMaps = new HashMap<>();
 
+    private final Map<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
+
     private Builder(AppInfo appInfo) {
       this.appInfo = appInfo;
     }
@@ -157,39 +171,91 @@
           && contextualVirtualToDirectMethodMaps.isEmpty()) {
         return previousLense;
       }
+      Map<DexProto, DexProto> cache = new HashMap<>();
+      BiMap<DexField, DexField> originalFieldSignatures = fieldMap.inverse();
       return new VerticalClassMergerGraphLense(
           appInfo,
           fieldMap,
           methodMap,
           getMergedMethodSignaturesAfterClassMerging(
-              mergedMethodsBuilder.build(), mergedClasses, dexItemFactory),
+              mergedMethodsBuilder.build(), mergedClasses, dexItemFactory, cache),
           contextualVirtualToDirectMethodMaps,
+          getOriginalFieldSignaturesAfterClassMerging(
+              originalFieldSignatures, mergedClasses, dexItemFactory),
+          getOriginalMethodSignaturesAfterClassMerging(
+              originalMethodSignatures, mergedClasses, dexItemFactory, cache),
           previousLense);
     }
 
     // After we have recorded that a method "a.b.c.Foo;->m(A, B, C)V" was merged into another class,
     // it could be that the class B was merged into its subclass B'. In that case we update the
     // signature to "a.b.c.Foo;->m(A, B', C)V".
-    private Set<DexMethod> getMergedMethodSignaturesAfterClassMerging(
+    private static Set<DexMethod> getMergedMethodSignaturesAfterClassMerging(
         Set<DexMethod> mergedMethods,
         Map<DexType, DexType> mergedClasses,
-        DexItemFactory dexItemFactory) {
+        DexItemFactory dexItemFactory,
+        Map<DexProto, DexProto> cache) {
       ImmutableSet.Builder<DexMethod> result = ImmutableSet.builder();
-      Map<DexProto, DexProto> cache = new HashMap<>();
       for (DexMethod signature : mergedMethods) {
-        DexType newHolder = mergedClasses.getOrDefault(signature.holder, signature.holder);
-        DexProto newProto =
-            dexItemFactory.applyClassMappingToProto(
-                signature.proto, type -> mergedClasses.getOrDefault(type, type), cache);
-        if (signature.holder.equals(newHolder) && signature.proto.equals(newProto)) {
-          result.add(signature);
-        } else {
-          result.add(dexItemFactory.createMethod(newHolder, newProto, signature.name));
-        }
+        result.add(
+            getMethodSignatureAfterClassMerging(signature, mergedClasses, dexItemFactory, cache));
       }
       return result.build();
     }
 
+    private static BiMap<DexField, DexField> getOriginalFieldSignaturesAfterClassMerging(
+        Map<DexField, DexField> originalFieldSignatures,
+        Map<DexType, DexType> mergedClasses,
+        DexItemFactory dexItemFactory) {
+      ImmutableBiMap.Builder<DexField, DexField> result = ImmutableBiMap.builder();
+      for (Map.Entry<DexField, DexField> entry : originalFieldSignatures.entrySet()) {
+        result.put(
+            getFieldSignatureAfterClassMerging(entry.getKey(), mergedClasses, dexItemFactory),
+            entry.getValue());
+      }
+      return result.build();
+    }
+
+    private static BiMap<DexMethod, DexMethod> getOriginalMethodSignaturesAfterClassMerging(
+        Map<DexMethod, DexMethod> originalMethodSignatures,
+        Map<DexType, DexType> mergedClasses,
+        DexItemFactory dexItemFactory,
+        Map<DexProto, DexProto> cache) {
+      ImmutableBiMap.Builder<DexMethod, DexMethod> result = ImmutableBiMap.builder();
+      for (Map.Entry<DexMethod, DexMethod> entry : originalMethodSignatures.entrySet()) {
+        result.put(
+            getMethodSignatureAfterClassMerging(
+                entry.getKey(), mergedClasses, dexItemFactory, cache),
+            entry.getValue());
+      }
+      return result.build();
+    }
+
+    private static DexField getFieldSignatureAfterClassMerging(
+        DexField signature, Map<DexType, DexType> mergedClasses, DexItemFactory dexItemFactory) {
+      DexType newClass = mergedClasses.getOrDefault(signature.clazz, signature.clazz);
+      DexType newType = mergedClasses.getOrDefault(signature.type, signature.type);
+      if (signature.clazz.equals(newClass) && signature.type.equals(newType)) {
+        return signature;
+      }
+      return dexItemFactory.createField(newClass, newType, signature.name);
+    }
+
+    private static DexMethod getMethodSignatureAfterClassMerging(
+        DexMethod signature,
+        Map<DexType, DexType> mergedClasses,
+        DexItemFactory dexItemFactory,
+        Map<DexProto, DexProto> cache) {
+      DexType newHolder = mergedClasses.getOrDefault(signature.holder, signature.holder);
+      DexProto newProto =
+          dexItemFactory.applyClassMappingToProto(
+              signature.proto, type -> mergedClasses.getOrDefault(type, type), cache);
+      if (signature.holder.equals(newHolder) && signature.proto.equals(newProto)) {
+        return signature;
+      }
+      return dexItemFactory.createMethod(newHolder, newProto, signature.name);
+    }
+
     public boolean hasMappingForSignatureInContext(DexType context, DexMethod signature) {
       Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap =
           contextualVirtualToDirectMethodMaps.get(context);
@@ -211,6 +277,10 @@
       methodMap.put(from, to);
     }
 
+    public void recordMove(DexMethod from, DexMethod to) {
+      originalMethodSignatures.put(to, from);
+    }
+
     public void mapVirtualMethodToDirectInType(
         DexMethod from, GraphLenseLookupResult to, DexType type) {
       Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap =
@@ -222,6 +292,7 @@
       fieldMap.putAll(builder.fieldMap);
       methodMap.putAll(builder.methodMap);
       mergedMethodsBuilder.addAll(builder.mergedMethodsBuilder.build());
+      originalMethodSignatures.putAll(builder.originalMethodSignatures);
       for (DexType context : builder.contextualVirtualToDirectMethodMaps.keySet()) {
         Map<DexMethod, GraphLenseLookupResult> current =
             contextualVirtualToDirectMethodMaps.get(context);
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
index b4e6ae2..d177b36 100644
--- a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
+++ b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
@@ -153,14 +153,9 @@
     addRule(clazz, feature, 0);
   }
 
-  public void addNonClassMapping(String name, String feature) throws FeatureMappingException {
-    if (parseNonClassRules.containsKey(name)) {
-      throw new FeatureMappingException(
-          "Non-code files with the same name present in multiple feature splits. " +
-          "File '" + name + "' present in both '" + feature + "' and '" +
-          parseNonClassRules.get(name) + "'.");
-    }
-    parseNonClassRules.put(name, feature);
+  public void addNonClassMapping(String name, String feature) {
+    // If a non-class file is present in multiple features put the resource in the base.
+    parseNonClassRules.put(name, parseNonClassRules.containsKey(name) ? baseName : feature);
   }
 
   FeatureClassMapping(List<String> lines) throws FeatureMappingException {
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
index bc8bd8c..5848396 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
@@ -39,8 +39,6 @@
 import java.util.zip.ZipOutputStream;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.internal.runners.statements.ExpectException;
-import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 
 public class DexSplitterTests {
@@ -206,19 +204,6 @@
     }
   }
 
-  private void validateNonClassOutput(Path base, Path feature) throws IOException {
-    byte[] contents = Files.readAllBytes(Paths.get(TEXT_FILE));
-    byte[] contents2 = new byte[contents.length * 2];
-    System.arraycopy(contents, 0, contents2, 0, contents.length);
-    System.arraycopy(contents, 0, contents2, contents.length, contents.length);
-    Path baseTextFile = base.resolve("dexsplitsample/TextFile.txt");
-    Path featureTextFile = feature.resolve("dexsplitsample/TextFile2.txt");
-    assert Files.exists(baseTextFile);
-    assert Files.exists(featureTextFile);
-    assert Arrays.equals(Files.readAllBytes(baseTextFile), contents);
-    assert Arrays.equals(Files.readAllBytes(featureTextFile), contents2);
-  }
-
   private Path createSplitSpec() throws FileNotFoundException, UnsupportedEncodingException {
     Path splitSpec = temp.getRoot().toPath().resolve("split_spec");
     try (PrintWriter out = new PrintWriter(splitSpec.toFile(), "UTF-8")) {
@@ -392,12 +377,13 @@
     ZipOutputStream inputZipStream = new ZipOutputStream(Files.newOutputStream(inputZip));
     String name = "dexsplitsample/TextFile.txt";
     inputZipStream.putNextEntry(new ZipEntry(name));
-    inputZipStream.write(Files.readAllBytes(Paths.get(TEXT_FILE)));
+    byte[] fileBytes = Files.readAllBytes(Paths.get(TEXT_FILE));
+    inputZipStream.write(fileBytes);
     inputZipStream.closeEntry();
     name = "dexsplitsample/TextFile2.txt";
     inputZipStream.putNextEntry(new ZipEntry(name));
-    inputZipStream.write(Files.readAllBytes(Paths.get(TEXT_FILE)));
-    inputZipStream.write(Files.readAllBytes(Paths.get(TEXT_FILE)));
+    inputZipStream.write(fileBytes);
+    inputZipStream.write(fileBytes);
     inputZipStream.closeEntry();
     inputZipStream.close();
     Path output = temp.newFolder().toPath().resolve("output");
@@ -407,14 +393,14 @@
     ZipOutputStream baseStream = new ZipOutputStream(Files.newOutputStream(baseJar));
     name = "dexsplitsample/TextFile.txt";
     baseStream.putNextEntry(new ZipEntry(name));
-    baseStream.write(Files.readAllBytes(Paths.get(TEXT_FILE)));
+    baseStream.write(fileBytes);
     baseStream.closeEntry();
     baseStream.close();
     ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
     name = "dexsplitsample/TextFile2.txt";
     featureStream.putNextEntry(new ZipEntry(name));
-    featureStream.write(Files.readAllBytes(Paths.get(TEXT_FILE)));
-    featureStream.write(Files.readAllBytes(Paths.get(TEXT_FILE)));
+    featureStream.write(fileBytes);
+    featureStream.write(fileBytes);
     featureStream.closeEntry();
     featureStream.close();
     Options options = new Options();
@@ -426,33 +412,43 @@
     DexSplitter.run(options);
     Path baseDir = output.resolve("base");
     Path featureDir = output.resolve("feature1");
-    validateNonClassOutput(baseDir, featureDir);
+    byte[] contents = fileBytes;
+    byte[] contents2 = new byte[contents.length * 2];
+    System.arraycopy(contents, 0, contents2, 0, contents.length);
+    System.arraycopy(contents, 0, contents2, contents.length, contents.length);
+    Path baseTextFile = baseDir.resolve("dexsplitsample/TextFile.txt");
+    Path featureTextFile = featureDir.resolve("dexsplitsample/TextFile2.txt");
+    assert Files.exists(baseTextFile);
+    assert Files.exists(featureTextFile);
+    assert Arrays.equals(Files.readAllBytes(baseTextFile), contents);
+    assert Arrays.equals(Files.readAllBytes(featureTextFile), contents2);
   }
 
-  @Test(expected = FeatureMappingException.class)
+  @Test
   public void splitDuplicateNonClassFiles()
       throws IOException, CompilationFailedException, FeatureMappingException {
     Path inputZip = temp.getRoot().toPath().resolve("input-zip-with-non-class-files.jar");
     ZipOutputStream inputZipStream = new ZipOutputStream(Files.newOutputStream(inputZip));
     String name = "dexsplitsample/TextFile.txt";
     inputZipStream.putNextEntry(new ZipEntry(name));
-    inputZipStream.write(Files.readAllBytes(Paths.get(TEXT_FILE)));
+    byte[] fileBytes = Files.readAllBytes(Paths.get(TEXT_FILE));
+    inputZipStream.write(fileBytes);
     inputZipStream.closeEntry();
     inputZipStream.close();
     Path output = temp.newFolder().toPath().resolve("output");
     Files.createDirectory(output);
     Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
     Path feature2Jar = temp.getRoot().toPath().resolve("feature2.jar");
-    ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(feature2Jar));
+    ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
     name = "dexsplitsample/TextFile.txt";
     featureStream.putNextEntry(new ZipEntry(name));
-    featureStream.write(Files.readAllBytes(Paths.get(TEXT_FILE)));
+    featureStream.write(fileBytes);
     featureStream.closeEntry();
     featureStream.close();
-    ZipOutputStream feature2Stream = new ZipOutputStream(Files.newOutputStream(featureJar));
+    ZipOutputStream feature2Stream = new ZipOutputStream(Files.newOutputStream(feature2Jar));
     name = "dexsplitsample/TextFile.txt";
     feature2Stream.putNextEntry(new ZipEntry(name));
-    feature2Stream.write(Files.readAllBytes(Paths.get(TEXT_FILE)));
+    feature2Stream.write(fileBytes);
     feature2Stream.closeEntry();
     feature2Stream.close();
     Options options = new Options();
@@ -462,5 +458,15 @@
     options.addFeatureJar(featureJar.toString());
     options.setSplitNonClassResources(true);
     DexSplitter.run(options);
+    Path baseDir = output.resolve("base");
+    Path feature1Dir = output.resolve("feature1");
+    Path feature2Dir = output.resolve("feature2");
+    Path baseTextFile = baseDir.resolve("dexsplitsample/TextFile.txt");
+    Path feature1TextFile = feature1Dir.resolve("dexsplitsample/TextFile2.txt");
+    Path feature2TextFile = feature2Dir.resolve("dexsplitsample/TextFile2.txt");
+    assert !Files.exists(feature1TextFile);
+    assert !Files.exists(feature2TextFile);
+    assert Files.exists(baseTextFile);
+    assert Arrays.equals(Files.readAllBytes(baseTextFile), fileBytes);
   }
 }
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 748866e..7325be3 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -41,6 +41,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.function.Function;
+import java.util.stream.Collectors;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -1813,6 +1814,66 @@
     testNotSupported("-adaptresourcefilecontents");
   }
 
+  private void checkRulesSourceSnippet(List<String> sourceRules) {
+    checkRulesSourceSnippet(sourceRules, sourceRules, false);
+  }
+
+  private void checkRulesSourceSnippet(
+      List<String> sourceRules, List<String> expected, boolean trim) {
+    reset();
+    parser.parse(createConfigurationForTesting(sourceRules));
+    verifyParserEndsCleanly();
+    List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
+    assertEquals(expected.size(), rules.size());
+    for (int i = 0; i < expected.size(); i++) {
+      assertEquals(trim ? expected.get(i).trim() : expected.get(i), rules.get(i).getSource());
+    }
+  }
+
+  @Test
+  public void accurateSourceSnippet() {
+    String rule1 = String.join(System.lineSeparator(), ImmutableList.of("-keep class A  {  *;  }"));
+    String rule2 =
+        String.join(System.lineSeparator(), ImmutableList.of("-keep class A  ", "{  *;  ", "}"));
+    String rule3 =
+        String.join(
+            System.lineSeparator(), ImmutableList.of("-checkdiscard class A  ", "{  *;  ", "}"));
+
+    checkRulesSourceSnippet(ImmutableList.of(rule1));
+    checkRulesSourceSnippet(ImmutableList.of(rule2));
+    checkRulesSourceSnippet(ImmutableList.of(rule3));
+    checkRulesSourceSnippet(ImmutableList.of(rule1, rule2, rule3));
+  }
+
+  @Test
+  public void accurateSourceSnippet_withWhitespace() {
+    Iterable<String> nonEmptyWhiteSpace =
+        whiteSpace.stream().filter(space -> !space.equals("")).collect(Collectors.toList());
+    for (String space : nonEmptyWhiteSpace) {
+      String rule1 =
+          String.join(System.lineSeparator(), ImmutableList.of("  -keep class A  {  *;  }  "))
+              .replaceAll(" {2}", space);
+      String rule2 =
+          String.join(
+                  System.lineSeparator(), ImmutableList.of("-keep class A  ", "{  *;  ", "}", ""))
+              .replaceAll(" {2}", space);
+
+      checkRulesSourceSnippet(ImmutableList.of(rule1), ImmutableList.of(rule1), true);
+      checkRulesSourceSnippet(
+          ImmutableList.of("#Test comment ", "", rule1), ImmutableList.of(rule1), true);
+      checkRulesSourceSnippet(
+          ImmutableList.of("#Test comment ", "", rule1, "", "#Test comment ", ""),
+          ImmutableList.of(rule1),
+          true);
+      checkRulesSourceSnippet(ImmutableList.of(rule2), ImmutableList.of(rule2), true);
+      checkRulesSourceSnippet(ImmutableList.of(rule1, rule2), ImmutableList.of(rule1, rule2), true);
+      checkRulesSourceSnippet(
+          ImmutableList.of(
+              "#Test comment ", "", rule1, " ", "#Test comment ", "", rule2, "#Test comment ", ""),
+          ImmutableList.of(rule1, rule2),
+          true);
+    }
+  }
 
   private ProguardConfiguration parseAndVerifyParserEndsCleanly(List<String> config) {
     parser.parse(createConfigurationForTesting(config));