Add reproduction of b/148525512
Bug: 148525512
Change-Id: I30e7ce81c7ca10b16e50fcd330788235f12d48dd
diff --git a/src/test/java/com/android/tools/r8/KotlinTestBase.java b/src/test/java/com/android/tools/r8/KotlinTestBase.java
index c3ce381..1a6a428 100644
--- a/src/test/java/com/android/tools/r8/KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/KotlinTestBase.java
@@ -39,6 +39,12 @@
.collect(Collectors.toList());
}
+ protected static Path getKotlinFileInTestPackage(Package pkg, String fileName)
+ throws IOException {
+ String folder = DescriptorUtils.getBinaryNameFromJavaType(pkg.getName());
+ return getKotlinFileInTest(folder, fileName);
+ }
+
protected static Path getKotlinFileInTest(String folder, String fileName) {
return Paths.get(ToolHelper.TESTS_DIR, "java", folder, fileName + FileUtils.KT_EXTENSION);
}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 5094ec5..9b2f89b 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1189,8 +1189,11 @@
consumer.finished(null);
}
- protected static void writeClassFileDataToJar(Path output, Collection<byte[]> classes)
- throws IOException {
+ protected static void writeClassesToJar(Path output, Class<?>... classes) throws IOException {
+ writeClassesToJar(output, Arrays.asList(classes));
+ }
+
+ protected static void writeClassFileDataToJar(Path output, Collection<byte[]> classes) {
ClassFileConsumer consumer = new ArchiveConsumer(output);
for (byte[] clazz : classes) {
consumer.accept(ByteDataView.of(clazz), extractClassDescriptor(clazz), null);
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java
new file mode 100644
index 0000000..405d65c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java
@@ -0,0 +1,162 @@
+// 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.kotlin.lambda.b148525512;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.ArchiveResourceProvider;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class B148525512 extends KotlinTestBase {
+
+ private static final Package pkg = B148525512.class.getPackage();
+ private static final String kotlinTestClassesPackage = pkg.getName();
+ private static final String baseKtClassName = kotlinTestClassesPackage + ".BaseKt";
+ private static final String featureKtClassNamet = kotlinTestClassesPackage + ".FeatureKt";
+ private static final String baseClassName = kotlinTestClassesPackage + ".Base";
+
+ private static final Map<KotlinTargetVersion, Path> kotlinBaseClasses = new HashMap<>();
+ private static final Map<KotlinTargetVersion, Path> kotlinFeatureClasses = new HashMap<>();
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0},{1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+ KotlinTargetVersion.values());
+ }
+
+ public B148525512(TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ @ClassRule public static TemporaryFolder classTemp = new TemporaryFolder();
+
+ @BeforeClass
+ public static void compileKotlin() throws Exception {
+ // Compile the base Kotlin with the FeatureAPI Java class on classpath.
+ Path featureApiJar = classTemp.newFile("feature_api.jar").toPath();
+ writeClassesToJar(featureApiJar, FeatureAPI.class);
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ Path ktBaseClasses =
+ kotlinc(KOTLINC, targetVersion)
+ .addClasspathFiles(featureApiJar)
+ .addSourceFiles(getKotlinFileInTestPackage(pkg, "base"))
+ .compile();
+ kotlinBaseClasses.put(targetVersion, ktBaseClasses);
+ // Compile the feature Kotlin code with the base classes on classpath.
+ Path ktFeatureClasses =
+ kotlinc(KOTLINC, targetVersion)
+ .addClasspathFiles(ktBaseClasses)
+ .addSourceFiles(getKotlinFileInTestPackage(pkg, "feature"))
+ .compile();
+ kotlinFeatureClasses.put(targetVersion, ktFeatureClasses);
+ }
+ }
+
+ private void checkLambdaGroups(CodeInspector inspector) {
+ List<FoundClassSubject> lambdaGroups =
+ inspector.allClasses().stream()
+ .filter(clazz -> clazz.getOriginalName().contains("LambdaGroup"))
+ .collect(Collectors.toList());
+ assertEquals(1, lambdaGroups.size());
+ MethodSubject invokeMethod = lambdaGroups.get(0).uniqueMethodWithName("invoke");
+ assertThat(invokeMethod, isPresent());
+ // The lambda group has 2 captures which capture "Base".
+ assertEquals(
+ 2,
+ invokeMethod
+ .streamInstructions()
+ .filter(InstructionSubject::isCheckCast)
+ .filter(
+ instruction ->
+ instruction.asCheckCast().getType().toSourceString().contains("Base"))
+ .count());
+ // TODO(b/148525512): The lambda group has 2 captures which capture "Feature".
+ assertEquals(
+ 2,
+ invokeMethod
+ .streamInstructions()
+ .filter(InstructionSubject::isCheckCast)
+ .filter(
+ instruction ->
+ instruction.asCheckCast().getType().toSourceString().contains("Feature"))
+ .count());
+ }
+
+ @Test
+ public void test() throws Exception {
+ Path featureCode = temp.newFile("feature.zip").toPath();
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(kotlinBaseClasses.get(targetVersion))
+ .addProgramClasses(FeatureAPI.class)
+ .addKeepMainRule(baseKtClassName)
+ .addKeepClassAndMembersRules(baseClassName)
+ .addKeepClassAndMembersRules(featureKtClassNamet)
+ .addKeepClassAndMembersRules(FeatureAPI.class)
+ .setMinApi(parameters.getApiLevel())
+ .noMinification() // The check cast inspection above relies on original names.
+ .addFeatureSplit(
+ builder ->
+ builder
+ .addProgramResourceProvider(
+ ArchiveResourceProvider.fromArchive(
+ kotlinFeatureClasses.get(targetVersion), true))
+ .setProgramConsumer(new ArchiveConsumer(featureCode, false))
+ .build())
+ .allowDiagnosticWarningMessages()
+ .compile()
+ .assertAllWarningMessagesMatch(
+ equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+ .inspect(this::checkLambdaGroups);
+
+ // TODO(b/148525512): Fails on 7.0.0 and 8.1.0.
+ if (parameters.getRuntime().asDex().getVm().getVersion() == Version.V7_0_0
+ || parameters.getRuntime().asDex().getVm().getVersion() == Version.V8_1_0) {
+ return;
+ }
+
+ // Run the code without the feature code present.
+ compileResult
+ .run(parameters.getRuntime(), baseKtClassName)
+ .assertSuccessWithOutputLines("1", "2");
+
+ // Run the code with the feature code present.
+ compileResult
+ .addRunClasspathFiles(featureCode)
+ .run(parameters.getRuntime(), baseKtClassName)
+ .assertSuccessWithOutputLines("1", "2", "3", "4");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/FeatureAPI.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/FeatureAPI.java
new file mode 100644
index 0000000..95bc790
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/FeatureAPI.java
@@ -0,0 +1,27 @@
+// 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.kotlin.lambda.b148525512;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class FeatureAPI {
+ public static boolean hasFeature() {
+ try {
+ Class.forName(FeatureAPI.class.getPackage().getName() + ".FeatureKt");
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ return true;
+ }
+
+ public static void feature(int i)
+ throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
+ IllegalAccessException {
+ Class<?> featureKtClass = Class.forName(FeatureAPI.class.getPackage().getName() + ".FeatureKt");
+ Method featureMethod = featureKtClass.getMethod("feature", int.class);
+ featureMethod.invoke(null, i);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/base.kt b/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/base.kt
new file mode 100644
index 0000000..aced216
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/base.kt
@@ -0,0 +1,18 @@
+// 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.kotlin.lambda.b148525512
+
+fun printInt(l: () -> Int ) = println(l())
+
+open class Base(@JvmField var x: Int, @JvmField var y: Int)
+
+fun main(args: Array<String>) {
+ val base = Base(args.size + 1, args.size + 2)
+ printInt { base.x }
+ printInt { base.y }
+ if (FeatureAPI.hasFeature()) {
+ FeatureAPI.feature(args.size + 3)
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/feature.kt b/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/feature.kt
new file mode 100644
index 0000000..3480225
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/feature.kt
@@ -0,0 +1,13 @@
+// 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.kotlin.lambda.b148525512
+
+class Feature(x: Int, y: Int) : Base(x, y)
+
+fun feature(i: Int) {
+ val f = Feature(i, i + 1)
+ printInt { f.x }
+ printInt { f.y }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CheckCastCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CheckCastCfInstructionSubject.java
new file mode 100644
index 0000000..0f5c42c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CheckCastCfInstructionSubject.java
@@ -0,0 +1,27 @@
+// 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.codeinspector;
+
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.graph.DexType;
+
+public class CheckCastCfInstructionSubject extends CfInstructionSubject
+ implements CheckCastInstructionSubject {
+ public CheckCastCfInstructionSubject(CfInstruction instruction, MethodSubject method) {
+ super(instruction, method);
+ assert isCheckCast();
+ }
+
+ @Override
+ public DexType getType() {
+ return ((CfCheckCast) instruction).getType();
+ }
+
+ @Override
+ public CheckCastInstructionSubject asCheckCast() {
+ return this;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CheckCastDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CheckCastDexInstructionSubject.java
new file mode 100644
index 0000000..ea10ad4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CheckCastDexInstructionSubject.java
@@ -0,0 +1,27 @@
+// 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.codeinspector;
+
+import com.android.tools.r8.code.CheckCast;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.graph.DexType;
+
+public class CheckCastDexInstructionSubject extends DexInstructionSubject
+ implements CheckCastInstructionSubject {
+ public CheckCastDexInstructionSubject(Instruction instruction, MethodSubject method) {
+ super(instruction, method);
+ assert isCheckCast();
+ }
+
+ @Override
+ public DexType getType() {
+ return ((CheckCast) instruction).getType();
+ }
+
+ @Override
+ public CheckCastInstructionSubject asCheckCast() {
+ return this;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CheckCastInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CheckCastInstructionSubject.java
new file mode 100644
index 0000000..e7fa916
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CheckCastInstructionSubject.java
@@ -0,0 +1,11 @@
+// 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.codeinspector;
+
+import com.android.tools.r8.graph.DexType;
+
+public interface CheckCastInstructionSubject extends InstructionSubject {
+ DexType getType();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 5e1e668..63c85d5 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -354,6 +354,8 @@
return new NewInstanceDexInstructionSubject(instruction, method);
} else if (dexInst.isConstString(JumboStringMode.ALLOW)) {
return new ConstStringDexInstructionSubject(instruction, method);
+ } else if (dexInst.isCheckCast()) {
+ return new CheckCastDexInstructionSubject(instruction, method);
} else {
return dexInst;
}
@@ -369,6 +371,8 @@
return new NewInstanceCfInstructionSubject(instruction, method);
} else if (cfInst.isConstString(JumboStringMode.ALLOW)) {
return new ConstStringCfInstructionSubject(instruction, method);
+ } else if (cfInst.isCheckCast()) {
+ return new CheckCastCfInstructionSubject(instruction, method);
} else {
return cfInst;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 9a48dfa..6eba32b 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -84,6 +84,10 @@
boolean isCheckCast(String type);
+ default CheckCastInstructionSubject asCheckCast() {
+ return null;
+ }
+
boolean isInstanceOf();
boolean isInstanceOf(String type);