Merge "Add precise source information to Proguard rule parser"
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 283716b..7ea7379 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -97,7 +97,7 @@
         break;
       }
       // TODO(b/111080693): we may need to collect all meaningful constraints.
-      result = ConstraintWithTarget.min(result, state, appInfo);
+      result = ConstraintWithTarget.meet(result, state, appInfo);
     }
     return result;
   }
@@ -179,11 +179,17 @@
    */
   public enum Constraint {
     // The ordinal values are important so please do not reorder.
-    NEVER,     // Never inline this.
-    SAMECLASS, // Only inline this into methods with same holder.
-    PACKAGE,   // Only inline this into methods with holders from the same package.
-    SUBCLASS,  // Only inline this into methods with holders from a subclass in a different package.
-    ALWAYS;    // No restrictions for inlining this.
+    NEVER(1),     // Never inline this.
+    SAMECLASS(2), // Inlineable into methods with same holder.
+    PACKAGE(4),   // Inlineable into methods with holders from the same package.
+    SUBCLASS(8),  // Inlineable into methods with holders from a subclass in a different package.
+    ALWAYS(16);   // No restrictions for inlining this.
+
+    int value;
+
+    Constraint(int value) {
+      this.value = value;
+    }
 
     static {
       assert NEVER.ordinal() < SAMECLASS.ordinal();
@@ -192,8 +198,8 @@
       assert SUBCLASS.ordinal() < ALWAYS.ordinal();
     }
 
-    static Constraint min(Constraint one, Constraint other) {
-      return one.ordinal() < other.ordinal() ? one : other;
+    boolean isSet(int value) {
+      return (this.value & value) != 0;
     }
   }
 
@@ -292,26 +298,26 @@
           : deriveConstraint(context, clazz, definition.accessFlags, appInfo);
     }
 
-    public static ConstraintWithTarget min(
+    public static ConstraintWithTarget meet(
         ConstraintWithTarget one, ConstraintWithTarget other, AppInfoWithSubtyping appInfo) {
       if (one.equals(other)) {
         return one;
       }
-      if (one == NEVER || other == NEVER) {
-        return NEVER;
-      }
       if (other.constraint.ordinal() < one.constraint.ordinal()) {
-        return min(other, one, appInfo);
+        return meet(other, one, appInfo);
       }
       // From now on, one.constraint.ordinal() <= other.constraint.ordinal()
+      if (one == NEVER) {
+        return NEVER;
+      }
       if (other == ALWAYS) {
         return one;
       }
-      Constraint minConstraint = Constraint.min(one.constraint, other.constraint);
-      assert minConstraint != Constraint.NEVER;
-      assert minConstraint != Constraint.ALWAYS;
+      int constraint = one.constraint.value | other.constraint.value;
+      assert !Constraint.NEVER.isSet(constraint);
+      assert !Constraint.ALWAYS.isSet(constraint);
       // SAMECLASS <= SAMECLASS, PACKAGE, SUBCLASS
-      if (minConstraint == Constraint.SAMECLASS) {
+      if (Constraint.SAMECLASS.isSet(constraint)) {
         assert one.constraint == Constraint.SAMECLASS;
         if (other.constraint == Constraint.SAMECLASS) {
           assert one.targetHolder != other.targetHolder;
@@ -330,14 +336,14 @@
         return NEVER;
       }
       // PACKAGE <= PACKAGE, SUBCLASS
-      if (minConstraint == Constraint.PACKAGE) {
+      if (Constraint.PACKAGE.isSet(constraint)) {
         assert one.constraint == Constraint.PACKAGE;
         if (other.constraint == Constraint.PACKAGE) {
           assert one.targetHolder != other.targetHolder;
           if (one.targetHolder.isSamePackage(other.targetHolder)) {
             return one;
           }
-          // PACKAGE of x and PACKAGE of y can be satisfied together.
+          // PACKAGE of x and PACKAGE of y cannot be satisfied together.
           return NEVER;
         }
         assert other.constraint == Constraint.SUBCLASS;
@@ -346,12 +352,12 @@
           return one;
         }
         // TODO(b/111080693): towards finer-grained constraints, we need both.
-        // Even though they're in different package, it is still inlinable to a class that is
-        // in the same package of one's context and a sub type of other's context.
+        // The target method is still inlineable to methods with a holder from the same package of
+        // one's holder and a subtype of other's holder.
         return NEVER;
       }
       // SUBCLASS <= SUBCLASS
-      assert minConstraint == Constraint.SUBCLASS;
+      assert Constraint.SUBCLASS.isSet(constraint);
       assert one.constraint == other.constraint;
       assert one.targetHolder != other.targetHolder;
       if (one.targetHolder.isSubtypeOf(other.targetHolder, appInfo)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index fe10637..129e9cb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -254,7 +254,7 @@
       ConstraintWithTarget classConstraintWithTarget =
           ConstraintWithTarget.deriveConstraint(
               invocationContext, fieldHolder, fieldClass.accessFlags, appInfo);
-      return ConstraintWithTarget.min(
+      return ConstraintWithTarget.meet(
           fieldConstraintWithTarget, classConstraintWithTarget, appInfo);
     }
     return ConstraintWithTarget.NEVER;
@@ -276,7 +276,7 @@
         ConstraintWithTarget classConstraintWithTarget =
             ConstraintWithTarget.deriveConstraint(
                 invocationContext, methodHolder, methodClass.accessFlags, appInfo);
-        return ConstraintWithTarget.min(
+        return ConstraintWithTarget.meet(
             methodConstraintWithTarget, classConstraintWithTarget, appInfo);
       }
     }
@@ -316,7 +316,7 @@
         ConstraintWithTarget.deriveConstraint(
             invocationContext, methodHolder, methodClass.accessFlags, appInfo);
     ConstraintWithTarget result =
-        ConstraintWithTarget.min(methodConstraintWithTarget, classConstraintWithTarget, appInfo);
+        ConstraintWithTarget.meet(methodConstraintWithTarget, classConstraintWithTarget, appInfo);
     if (result == ConstraintWithTarget.NEVER) {
       return result;
     }
@@ -329,7 +329,7 @@
       methodConstraintWithTarget =
           ConstraintWithTarget.deriveConstraint(
               invocationContext, methodHolder, target.accessFlags, appInfo);
-      result = ConstraintWithTarget.min(result, methodConstraintWithTarget, appInfo);
+      result = ConstraintWithTarget.meet(result, methodConstraintWithTarget, appInfo);
       if (result == ConstraintWithTarget.NEVER) {
         return result;
       }
diff --git a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
index dbc48a3..c707968 100644
--- a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
@@ -67,7 +67,7 @@
   }
 
   private void updateConstraint(ConstraintWithTarget other) {
-    constraint = ConstraintWithTarget.min(constraint, other, appInfo);
+    constraint = ConstraintWithTarget.meet(constraint, other, appInfo);
   }
 
   // Used to signal that the result is ready, such that we do not need to visit all instructions of
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/ir/optimize/ConstraintWithTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
index f3dbe46..33994b1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
@@ -52,7 +52,7 @@
   }
 
   private ConstraintWithTarget meet(ConstraintWithTarget e1, ConstraintWithTarget e2) {
-    return ConstraintWithTarget.min(e1, e2, appInfo);
+    return ConstraintWithTarget.meet(e1, e2, appInfo);
   }
 
   @Test