Reproduce illegal member value propagation in presence of feature splits

Bug: 155249941
Change-Id: Id554e9797da82921c4ab9811e901f5e624d19d17
diff --git a/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java b/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
new file mode 100644
index 0000000..0979b27
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, 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.utils;
+
+public class ConsumerUtils {
+
+  public static <T> ThrowingConsumer<T, RuntimeException> emptyThrowingConsumer() {
+    return ignore -> {};
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
index 14d1e63..8f6a2d6 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
@@ -4,7 +4,10 @@
 
 package com.android.tools.r8.dexsplitter;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
@@ -16,13 +19,13 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
-import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -46,15 +49,11 @@
 
   @Test
   public void testInliningFromFeature() throws Exception {
-    Predicate<R8TestCompileResult> ensureGetFromFeatureGone =
+    ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone =
         r8TestCompileResult -> {
           // Ensure that getFromFeature from FeatureClass is inlined into the run method.
-          try {
-            ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class);
-            return clazz.uniqueMethodWithName("getFromFeature").isAbsent();
-          } catch (IOException | ExecutionException ex) {
-            throw new RuntimeException("Found getFromFeature in FeatureClass");
-          }
+          ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class);
+          assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent()));
         };
     Consumer<R8FullTestBuilder> configurator =
         r8FullTestBuilder -> r8FullTestBuilder.enableMergeAnnotations().noMinification();
@@ -83,8 +82,7 @@
             ImmutableSet.of(BaseSuperClass.class),
             ImmutableSet.of(FeatureClass.class),
             FeatureClass.class,
-            EXPECTED,
-            a -> true,
+            ConsumerUtils.emptyThrowingConsumer(),
             configurator);
 
     assertEquals(processResult.exitCode, 0);
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
new file mode 100644
index 0000000..b177200
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.dexsplitter;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.ConsumerUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DexSplitterMemberValuePropagationRegression extends SplitterTestBase {
+
+  public static final String EXPECTED = StringUtils.lines("42");
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  private final TestParameters parameters;
+
+  public DexSplitterMemberValuePropagationRegression(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testPropagationFromFeature() throws Exception {
+    ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone =
+        r8TestCompileResult -> {
+          // Ensure that getFromFeature from FeatureClass is inlined into the run method.
+          ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class);
+          assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent()));
+        };
+    ProcessResult processResult =
+        testDexSplitter(
+            parameters,
+            ImmutableSet.of(BaseSuperClass.class),
+            ImmutableSet.of(FeatureClass.class, FeatureEnum.class),
+            FeatureClass.class,
+            EXPECTED,
+            ensureGetFromFeatureGone,
+            builder -> builder.enableInliningAnnotations().noMinification());
+    // We expect art to fail on this with the dex splitter, see b/122902374
+    assertNotEquals(processResult.exitCode, 0);
+    assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
+  }
+
+  @Test
+  public void testOnR8Splitter() throws IOException, CompilationFailedException {
+    assumeTrue(parameters.isDexRuntime());
+    ProcessResult processResult =
+        testR8Splitter(
+            parameters,
+            ImmutableSet.of(BaseSuperClass.class),
+            ImmutableSet.of(FeatureClass.class, FeatureEnum.class),
+            FeatureClass.class,
+            ConsumerUtils.emptyThrowingConsumer(),
+            R8TestBuilder::enableInliningAnnotations);
+    // TODO(b/155249941): Should succeed with `EXPECTED` as output.
+    assertNotEquals(processResult.exitCode, 0);
+  }
+
+  public abstract static class BaseSuperClass implements RunInterface {
+
+    @NeverInline
+    @Override
+    public void run() {
+      System.out.println(getFromFeature());
+    }
+
+    public abstract Enum<?> getFromFeature();
+  }
+
+  public static class FeatureClass extends BaseSuperClass {
+
+    @NeverInline
+    @Override
+    public Enum<?> getFromFeature() {
+      return FeatureEnum.A;
+    }
+  }
+
+  public enum FeatureEnum {
+    A;
+
+    @Override
+    public String toString() {
+      return "42";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
index 25cbe9a..067f920 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -4,13 +4,14 @@
 
 package com.android.tools.r8.dexsplitter;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.R8FullTestBuilder;
@@ -18,13 +19,13 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
-import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -49,16 +50,12 @@
   @Test
   public void testInliningFromFeature() throws Exception {
     // Static merging is based on sorting order, we assert that we merged to the feature.
-    Predicate<R8TestCompileResult> ensureMergingToFeature =
+    ThrowingConsumer<R8TestCompileResult, Exception> ensureMergingToFeature =
         r8TestCompileResult -> {
-          try {
-            ClassSubject clazz = r8TestCompileResult.inspector().clazz(AFeatureWithStatic.class);
-            return clazz.allMethods().size() == 2
-                && clazz.uniqueMethodWithName("getBase42").isPresent()
-                && clazz.uniqueMethodWithName("getFoobar").isPresent();
-          } catch (IOException | ExecutionException ex) {
-            throw new RuntimeException("Failed lookup up AFeatureWithStatic");
-          }
+          ClassSubject clazz = r8TestCompileResult.inspector().clazz(AFeatureWithStatic.class);
+          assertEquals(2, clazz.allMethods().size());
+          assertThat(clazz.uniqueMethodWithName("getBase42"), isPresent());
+          assertThat(clazz.uniqueMethodWithName("getFoobar"), isPresent());
         };
     Consumer<R8FullTestBuilder> configurator =
         r8FullTestBuilder ->
@@ -82,8 +79,7 @@
   }
 
   @Test
-  public void testOnR8Splitter() throws IOException, CompilationFailedException,
-      ExecutionException {
+  public void testOnR8Splitter() throws IOException, CompilationFailedException {
     assumeTrue(parameters.isDexRuntime());
     Consumer<R8FullTestBuilder> configurator =
         r8FullTestBuilder -> r8FullTestBuilder.enableMergeAnnotations().noMinification();
@@ -93,8 +89,7 @@
             ImmutableSet.of(BaseClass.class, BaseWithStatic.class),
             ImmutableSet.of(FeatureClass.class, AFeatureWithStatic.class),
             FeatureClass.class,
-            EXPECTED,
-            a -> true,
+            ConsumerUtils.emptyThrowingConsumer(),
             configurator);
 
     assertEquals(processResult.exitCode, 0);
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java b/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
index dae7746..331403a 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
@@ -4,10 +4,12 @@
 
 package com.android.tools.r8.dexsplitter;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
@@ -15,12 +17,10 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.google.common.collect.ImmutableSet;
-import java.io.IOException;
-import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -47,19 +47,15 @@
   }
 
   @Test
-  public void testInlining() throws IOException, CompilationFailedException {
+  public void testInlining() throws Exception {
     assumeTrue(parameters.isDexRuntime());
     Consumer<R8FullTestBuilder> configurator =
         r8FullTestBuilder -> r8FullTestBuilder.enableMergeAnnotations().noMinification();
-    Predicate<R8TestCompileResult> ensureInlined =
+    ThrowingConsumer<R8TestCompileResult, Exception> ensureInlined =
         r8TestCompileResult -> {
           // Ensure that isEarly from BaseUtilClass is inlined into the feature
-          try {
-            ClassSubject clazz = r8TestCompileResult.inspector().clazz(BaseUtilClass.class);
-            return clazz.uniqueMethodWithName("isEarly").isAbsent();
-          } catch (IOException | ExecutionException ex) {
-            throw new RuntimeException("Found isEarly in BaseUtilClass");
-          }
+          ClassSubject clazz = r8TestCompileResult.inspector().clazz(BaseUtilClass.class);
+          assertThat(clazz.uniqueMethodWithName("isEarly"), not(isPresent()));
         };
     ProcessResult processResult =
         testR8Splitter(
@@ -67,7 +63,6 @@
             ImmutableSet.of(BaseSuperClass.class, BaseUtilClass.class),
             ImmutableSet.of(FeatureClass.class),
             FeatureClass.class,
-            EXPECTED,
             ensureInlined,
             configurator);
 
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index 1e31022..16291c4 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.dexsplitter.DexSplitter.Options;
 import com.android.tools.r8.utils.ArchiveResourceProvider;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
@@ -32,7 +33,6 @@
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
@@ -121,20 +121,19 @@
       TemporaryFolder temp,
       Collection<String> nonJavaFiles,
       boolean ensureClassesInOutput,
-      Class... classes) {
+      Class<?>... classes) {
     addConsumers(builder, outputPath, temp, nonJavaFiles, true, Arrays.asList(classes));
     return builder.build();
   }
 
-  protected ProcessResult testR8Splitter(
+  protected <E extends Throwable> ProcessResult testR8Splitter(
       TestParameters parameters,
       Set<Class<?>> baseClasses,
       Set<Class<?>> featureClasses,
-      Class toRun,
-      String expectedOutput,
-      Predicate<R8TestCompileResult> predicate,
+      Class<?> toRun,
+      ThrowingConsumer<R8TestCompileResult, E> compileResultConsumer,
       Consumer<R8FullTestBuilder> r8TestConfigurator)
-      throws IOException, CompilationFailedException {
+      throws IOException, CompilationFailedException, E {
     Path featureOutput = temp.newFile("feature.zip").toPath();
 
     R8FullTestBuilder r8FullTestBuilder = testForR8(parameters.getBackend());
@@ -158,7 +157,7 @@
     r8TestConfigurator.accept(r8FullTestBuilder);
 
     R8TestCompileResult r8TestCompileResult = r8FullTestBuilder.compile();
-    assertTrue(predicate.test(r8TestCompileResult));
+    compileResultConsumer.accept(r8TestCompileResult);
     Path baseOutput = r8TestCompileResult.writeToZip();
 
     return runFeatureOnArt(toRun, baseOutput, featureOutput, parameters.getRuntime());
@@ -166,15 +165,15 @@
 
   // Compile the passed in classes plus RunInterface and SplitRunner using R8, then split
   // based on the base/feature sets. toRun must implement the BaseRunInterface
-  protected ProcessResult testDexSplitter(
+  protected <E extends Throwable> ProcessResult testDexSplitter(
       TestParameters parameters,
       Set<Class<?>> baseClasses,
       Set<Class<?>> featureClasses,
-      Class toRun,
+      Class<?> toRun,
       String expectedOutput,
-      Predicate<R8TestCompileResult> predicate,
+      ThrowingConsumer<R8TestCompileResult, E> compileResultConsumer,
       Consumer<R8FullTestBuilder> r8TestConfigurator)
-      throws Exception {
+      throws Exception, E {
     List<Class<?>> baseClassesWithRunner =
         ImmutableList.<Class<?>>builder()
             .add(RunInterface.class, SplitRunner.class)
@@ -229,7 +228,7 @@
             .addKeepClassRules(toRun);
     r8TestConfigurator.accept(r8FullTestBuilder);
     R8TestCompileResult r8TestCompileResult = r8FullTestBuilder.compile();
-    assertTrue(predicate.test(r8TestCompileResult));
+    compileResultConsumer.accept(r8TestCompileResult);
     Path fullFiles = r8TestCompileResult.writeToZip();
 
     // Ensure that we can run the program as a unit (i.e., without splitting)