Merge "Keep more annotations/attributes for inner/outer class relationship"
diff --git a/.gitignore b/.gitignore
index d9cb978..f15c3c1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,6 +33,8 @@
third_party/benchmarks/santa-tracker.tar.gz
third_party/chrome.tar.gz
third_party/chrome
+third_party/dart-sdk
+third_party/dart-sdk.tar.gz
third_party/desugar/desugar_*/
third_party/desugar/desugar_*.tar.gz
third_party/gmail/*
diff --git a/build.gradle b/build.gradle
index f5a3337..cfaacdb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1310,17 +1310,23 @@
if (project.property('tool') == 'r8') {
exclude "com/android/tools/r8/art/*/d8/**"
exclude "com/android/tools/r8/jctf/d8/**"
- } else {
- assert(project.property('tool') == 'd8')
+ exclude "com/android/tools/r8/jctf/r8cf/**"
+ } else if (project.property('tool') == 'd8') {
exclude "com/android/tools/r8/art/*/r8/**"
exclude "com/android/tools/r8/jctf/r8/**"
+ exclude "com/android/tools/r8/jctf/r8cf/**"
+ } else {
+ assert(project.property('tool') == 'r8cf')
+ exclude "com/android/tools/r8/art/*/d8/**"
+ exclude "com/android/tools/r8/art/*/r8/**"
+ exclude "com/android/tools/r8/jctf/d8/**"
+ exclude "com/android/tools/r8/jctf/r8/**"
}
}
if (!project.hasProperty('all_tests')) {
exclude "com/android/tools/r8/art/dx/**"
exclude "com/android/tools/r8/art/jack/**"
}
- // TODO(tamaskenez) enable jctf on all_tests when consolidated
if (!project.hasProperty('jctf') && !project.hasProperty('only_jctf')) {
exclude "com/android/tools/r8/jctf/**"
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index d1bfc9a..5424cc2 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -191,7 +191,7 @@
break;
}
// Type check.
- if (!getType().matches(originalSignature.type)) {
+ if (!getType().matches(originalSignature.type, appView)) {
break;
}
// Annotations check
@@ -228,9 +228,7 @@
return RootSetBuilder.containsAnnotation(annotation, method.annotations);
case METHOD:
// Check return type.
- // TODO(b/110141157): The name of the return type may have changed as a result of vertical
- // class merging. We should use the original type name.
- if (!type.matches(originalSignature.proto.returnType)) {
+ if (!type.matches(originalSignature.proto.returnType, appView)) {
break;
}
// Fall through for access flags, name and arguments.
@@ -261,9 +259,7 @@
break;
}
for (int i = 0; i < parameters.length; i++) {
- // TODO(b/110141157): The names of the parameter types may have changed as a result of
- // vertical class merging. We should use the original type names.
- if (!arguments.get(i).matches(parameters[i])) {
+ if (!arguments.get(i).matches(parameters[i], appView)) {
return false;
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
index 34ee554..747dea9 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
@@ -5,6 +5,8 @@
import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
@@ -31,8 +33,21 @@
TYPE
}
+ // Evaluates this matcher on the given type.
public abstract boolean matches(DexType type);
+ // Evaluates this matcher on the given type, and on all types that have been merged into the given
+ // type, if any.
+ public final boolean matches(DexType type, AppView<? extends AppInfo> appView) {
+ if (matches(type)) {
+ return true;
+ }
+ if (appView.verticallyMergedClasses() != null) {
+ return appView.verticallyMergedClasses().getSourcesFor(type).stream().anyMatch(this::matches);
+ }
+ return false;
+ }
+
protected Iterable<ProguardWildcard> getWildcards() {
return Collections::emptyIterator;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index d5d5422..e117462 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -107,7 +107,10 @@
// TODO(herhut): Warn about broken supertype chain?
return false;
}
- if (name.matches(clazz.type) && containsAnnotation(annotation, clazz.annotations)) {
+ // TODO(b/110141157): Should the vertical class merger move annotations from the source to
+ // the target class? If so, it is sufficient only to apply the annotation-matcher to the
+ // annotations of `class`.
+ if (name.matches(clazz.type, appView) && containsAnnotation(annotation, clazz.annotations)) {
return true;
}
type = clazz.superType;
@@ -386,16 +389,17 @@
if (!satisfyAnnotation(rule, sourceClass)) {
return;
}
- // TODO(b/110141157): Handle the situation where the class in the extends/implements clause
- // has been merged.
- if (rule.hasInheritanceClassName()
- && !satisfyInheritanceRule(sourceClass, this::definitionForWithLiveTypes, rule)) {
- // Try another live type since the current one doesn't satisfy the inheritance rule.
- return;
- }
if (!rule.getClassNames().matches(sourceClass.type)) {
return;
}
+ if (rule.hasInheritanceClassName()) {
+ // Note that, in presence of vertical class merging, we check if the resulting class
+ // (i.e., the target class) satisfies the implements/extends-matcher.
+ if (!satisfyInheritanceRule(targetClass, this::definitionForWithLiveTypes, rule)) {
+ // Try another live type since the current one doesn't satisfy the inheritance rule.
+ return;
+ }
+ }
Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
if (memberKeepRules.isEmpty()) {
materializeIfRule(rule);
@@ -456,6 +460,8 @@
}
private DexClass definitionForWithLiveTypes(DexType type) {
+ assert appView.verticallyMergedClasses() == null
+ || !appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(type);
return liveTypes.contains(type) ? appView.appInfo().definitionFor(type) : null;
}
}
@@ -592,8 +598,7 @@
ProguardTypeMatcher inheritanceClassName = rule.getInheritanceClassName();
ProguardTypeMatcher inheritanceAnnotation = rule.getInheritanceAnnotation();
boolean extendsExpected =
- anySuperTypeMatches(
- clazz.superType, definitionFor, inheritanceClassName, inheritanceAnnotation);
+ satisfyExtendsRule(clazz, definitionFor, inheritanceClassName, inheritanceAnnotation);
boolean implementsExpected = false;
if (!extendsExpected) {
implementsExpected =
@@ -618,6 +623,27 @@
return false;
}
+ private boolean satisfyExtendsRule(
+ DexClass clazz,
+ Function<DexType, DexClass> definitionFor,
+ ProguardTypeMatcher inheritanceClassName,
+ ProguardTypeMatcher inheritanceAnnotation) {
+ if (anySuperTypeMatches(
+ clazz.superType, definitionFor, inheritanceClassName, inheritanceAnnotation)) {
+ return true;
+ }
+
+ // It is possible that this class used to inherit from another class X, but no longer does it,
+ // because X has been merged into `clazz`.
+ if (appView.verticallyMergedClasses() != null) {
+ // TODO(b/110141157): Figure out what to do with annotations. Should the annotations of
+ // the DexClass corresponding to `sourceType` satisfy the `annotation`-matcher?
+ return appView.verticallyMergedClasses().getSourcesFor(clazz.type).stream()
+ .anyMatch(inheritanceClassName::matches);
+ }
+ return false;
+ }
+
private boolean allRulesSatisfied(Collection<ProguardMemberRule> memberKeepRules,
DexClass clazz) {
for (ProguardMemberRule rule : memberKeepRules) {
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index a4f55c3..ed12177 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -60,6 +60,58 @@
private final List<StringResource> mainDexListResources;
private final List<String> mainDexClasses;
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ try {
+ if (!programResourceProviders.isEmpty()) {
+ builder.append(" Program resources:").append(System.lineSeparator());
+ printProgramResourceProviders(builder, programResourceProviders);
+ }
+ if (!classpathResourceProviders.isEmpty()) {
+ builder.append(" Classpath resources:").append(System.lineSeparator());
+ printClassFileProviders(builder, classpathResourceProviders);
+ }
+ if (!libraryResourceProviders.isEmpty()) {
+ builder.append(" Library resources:").append(System.lineSeparator());
+ printClassFileProviders(builder, libraryResourceProviders);
+ }
+ } catch (ResourceException e) {
+ e.printStackTrace();
+ }
+ return builder.toString();
+ }
+
+ private static void printProgramResourceProviders(
+ StringBuilder builder, Collection<ProgramResourceProvider> providers)
+ throws ResourceException {
+ for (ProgramResourceProvider provider : providers) {
+ for (ProgramResource resource : provider.getProgramResources()) {
+ printProgramResource(builder, resource);
+ }
+ }
+ }
+
+ private static void printClassFileProviders(
+ StringBuilder builder, Collection<ClassFileResourceProvider> providers) {
+ for (ClassFileResourceProvider provider : providers) {
+ for (String descriptor : provider.getClassDescriptors()) {
+ ProgramResource resource = provider.getProgramResource(descriptor);
+ printProgramResource(builder, resource);
+ }
+ }
+ }
+
+ private static void printProgramResource(StringBuilder builder, ProgramResource resource) {
+ builder.append(" ").append(resource.getOrigin());
+ Set<String> descriptors = resource.getClassDescriptors();
+ if (descriptors != null && !descriptors.isEmpty()) {
+ builder.append(" contains ");
+ StringUtils.append(builder, descriptors);
+ }
+ builder.append(System.lineSeparator());
+ }
+
// See factory methods and AndroidApp.Builder below.
private AndroidApp(
ImmutableList<ProgramResourceProvider> programResourceProviders,
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
new file mode 100644
index 0000000..368e8e4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -0,0 +1,51 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.D8Command.Builder;
+import com.android.tools.r8.TestBase.Backend;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+
+public class D8TestBuilder extends TestCompilerBuilder<D8Command, Builder, D8TestBuilder> {
+
+ private final D8Command.Builder builder;
+
+ private D8TestBuilder(TestState state, D8Command.Builder builder) {
+ super(state, builder, Backend.DEX);
+ this.builder = builder;
+ }
+
+ public static D8TestBuilder create(TestState state) {
+ return new D8TestBuilder(state, D8Command.builder());
+ }
+
+ @Override
+ D8TestBuilder self() {
+ return this;
+ }
+
+ @Override
+ void internalCompile(Builder builder) throws CompilationFailedException {
+ D8.run(builder.build());
+ }
+
+ public D8TestBuilder addClasspathClasses(Class<?>... classes) {
+ return addClasspathClasses(Arrays.asList(classes));
+ }
+
+ public D8TestBuilder addClasspathClasses(Collection<Class<?>> classes) {
+ return addClasspathFiles(getFilesForClasses(classes));
+ }
+
+ public D8TestBuilder addClasspathFiles(Path... files) {
+ return addClasspathFiles(Arrays.asList(files));
+ }
+
+ public D8TestBuilder addClasspathFiles(Collection<Path> files) {
+ builder.addClasspathFiles(files);
+ return self();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
new file mode 100644
index 0000000..484adee
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -0,0 +1,158 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ListUtils;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+public class JvmTestBuilder extends TestBuilder<JvmTestBuilder> {
+
+ private static class ClassFileResource implements ProgramResource {
+
+ private final Path file;
+ private final String descriptor;
+ private final Origin origin;
+
+ ClassFileResource(Class<?> clazz) {
+ this(
+ ToolHelper.getClassFileForTestClass(clazz),
+ DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName()));
+ }
+
+ ClassFileResource(Path file, String descriptor) {
+ this.file = file;
+ this.descriptor = descriptor;
+ origin = new PathOrigin(file);
+ }
+
+ @Override
+ public Kind getKind() {
+ return Kind.CF;
+ }
+
+ @Override
+ public InputStream getByteStream() throws ResourceException {
+ try {
+ return Files.newInputStream(file);
+ } catch (IOException e) {
+ throw new ResourceException(getOrigin(), e);
+ }
+ }
+
+ @Override
+ public Set<String> getClassDescriptors() {
+ return Collections.singleton(descriptor);
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin;
+ }
+ }
+
+ private static class ClassFileResourceProvider implements ProgramResourceProvider {
+
+ private final List<ProgramResource> resources;
+
+ public ClassFileResourceProvider(List<ProgramResource> resources) {
+ this.resources = resources;
+ }
+
+ @Override
+ public Collection<ProgramResource> getProgramResources() throws ResourceException {
+ return resources;
+ }
+
+ @Override
+ public DataResourceProvider getDataResourceProvider() {
+ return null;
+ }
+ }
+
+ // Ordered list of classpath entries.
+ private List<Path> classpath = new ArrayList<>();
+
+ private AndroidApp.Builder builder = AndroidApp.builder();
+
+ private JvmTestBuilder(TestState state) {
+ super(state);
+ }
+
+ public static JvmTestBuilder create(TestState state) {
+ return new JvmTestBuilder(state);
+ }
+
+ @Override
+ JvmTestBuilder self() {
+ return this;
+ }
+
+ @Override
+ public TestRunResult run(String mainClass) throws IOException {
+ ProcessResult result = ToolHelper.runJava(classpath, mainClass);
+ return new TestRunResult(builder.build(), result);
+ }
+
+ @Override
+ public JvmTestBuilder addLibraryFiles(Collection<Path> files) {
+ throw new Unimplemented("No support for changing the Java runtime library.");
+ }
+
+ @Override
+ public JvmTestBuilder addProgramClasses(Collection<Class<?>> classes) {
+ // Adding a collection of classes will build a jar of exactly those classes so that no other
+ // classes are made available via a too broad classpath directory.
+ List<ProgramResource> resources = ListUtils.map(classes, ClassFileResource::new);
+ AndroidApp build = AndroidApp.builder()
+ .addProgramResourceProvider(new ClassFileResourceProvider(resources)).build();
+ Path out;
+ try {
+ out = getState().getNewTempFolder().resolve("out.zip");
+ build.writeToZip(out, OutputMode.ClassFile);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ classpath.add(out);
+ builder.addProgramFiles(out);
+ return self();
+ }
+
+ @Override
+ public JvmTestBuilder addProgramFiles(Collection<Path> files) {
+ throw new Unimplemented(
+ "No support for adding paths directly (we need to compute the descriptor)");
+ }
+
+ public JvmTestBuilder addClasspath(Path... paths) {
+ return addClasspath(Arrays.asList(paths));
+ }
+
+ public JvmTestBuilder addClasspath(List<Path> paths) {
+ for (Path path : paths) {
+ assert Files.isDirectory(path) || FileUtils.isArchive(path);
+ classpath.add(path);
+ }
+ return self();
+ }
+
+ public JvmTestBuilder addTestClasspath() {
+ return addClasspath(ToolHelper.getClassPathForTests());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
new file mode 100644
index 0000000..50e659c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+public class R8TestBuilder extends TestCompilerBuilder<R8Command, Builder, R8TestBuilder> {
+
+ private final R8Command.Builder builder;
+
+ private R8TestBuilder(TestState state, Builder builder, Backend backend) {
+ super(state, builder, backend);
+ this.builder = builder;
+ }
+
+ public static R8TestBuilder create(TestState state, Backend backend) {
+ return new R8TestBuilder(state, R8Command.builder(), backend);
+ }
+
+ private boolean enableInliningAnnotations = false;
+
+ @Override
+ R8TestBuilder self() {
+ return this;
+ }
+
+ @Override
+ public void internalCompile(Builder builder) throws CompilationFailedException {
+ if (enableInliningAnnotations) {
+ ToolHelper.allowTestProguardOptions(builder);
+ }
+ R8.run(builder.build());
+ }
+
+ public R8TestBuilder addKeepRules(String... rules) {
+ return addKeepRules(Arrays.asList(rules));
+ }
+
+ public R8TestBuilder addKeepRules(Collection<String> rules) {
+ builder.addProguardConfiguration(new ArrayList<>(rules), Origin.unknown());
+ return self();
+ }
+
+ public R8TestBuilder addKeepAllClassesRule() {
+ addKeepRules("-keep class ** { *; }");
+ return self();
+ }
+
+ public R8TestBuilder addKeepClassRules(Class<?>... classes) {
+ for (Class<?> clazz : classes) {
+ addKeepRules("-keep class " + clazz.getTypeName());
+ }
+ return self();
+ }
+
+ public R8TestBuilder addKeepPackageRules(Package pkg) {
+ return addKeepRules("-keep class " + pkg.getName() + ".*");
+ }
+
+ public R8TestBuilder addKeepMainRule(Class<?> mainClass) {
+ return addKeepRules(
+ StringUtils.joinLines(
+ "-keep class " + mainClass.getTypeName() + " {",
+ " public static void main(java.lang.String[]);",
+ "}"));
+ }
+
+ public R8TestBuilder enableInliningAnnotations() {
+ if (!enableInliningAnnotations) {
+ enableInliningAnnotations = true;
+ addKeepRules(
+ "-forceinline class * { @com.android.tools.r8.ForceInline *; }",
+ "-neverinline class * { @com.android.tools.r8.NeverInline *; }");
+ }
+ return self();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 94cc54b..efec188 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -59,6 +59,19 @@
import org.objectweb.asm.ClassVisitor;
public class TestBase {
+
+ public R8TestBuilder testForR8(Backend backend) {
+ return R8TestBuilder.create(new TestState(temp), backend);
+ }
+
+ public D8TestBuilder testForD8() {
+ return D8TestBuilder.create(new TestState(temp));
+ }
+
+ public JvmTestBuilder testForJvm() {
+ return JvmTestBuilder.create(new TestState(temp));
+ }
+
public enum Backend {
CF,
DEX
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
new file mode 100644
index 0000000..6d5a158
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+
+import com.android.tools.r8.utils.ListUtils;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+public abstract class TestBuilder<T extends TestBuilder<T>> {
+
+ private final TestState state;
+
+ public TestBuilder(TestState state) {
+ this.state = state;
+ }
+
+ public TestState getState() {
+ return state;
+ }
+
+ abstract T self();
+
+ public abstract TestRunResult run(String mainClass)
+ throws IOException, CompilationFailedException;
+
+ public TestRunResult run(Class mainClass) throws IOException, CompilationFailedException {
+ return run(mainClass.getTypeName());
+ }
+
+ public abstract T addProgramFiles(Collection<Path> files);
+
+ public T addProgramClasses(Class<?>... classes) {
+ return addProgramClasses(Arrays.asList(classes));
+ }
+
+ public T addProgramClasses(Collection<Class<?>> classes) {
+ return addProgramFiles(getFilesForClasses(classes));
+ }
+
+ public T addProgramFiles(Path... files) {
+ return addProgramFiles(Arrays.asList(files));
+ }
+
+ public T addProgramClassesAndInnerClasses(Class<?>... classes) throws IOException {
+ return addProgramClassesAndInnerClasses(Arrays.asList(classes));
+ }
+
+ public T addProgramClassesAndInnerClasses(Collection<Class<?>> classes) throws IOException {
+ return addProgramFiles(getFilesForClassesAndInnerClasses(classes));
+ }
+
+ public abstract T addLibraryFiles(Collection<Path> files);
+
+ public T addLibraryClasses(Class<?>... classes) {
+ return addLibraryClasses(Arrays.asList(classes));
+ }
+
+ public T addLibraryClasses(Collection<Class<?>> classes) {
+ return addLibraryFiles(getFilesForClasses(classes));
+ }
+
+ public T addLibraryFiles(Path... files) {
+ return addLibraryFiles(Arrays.asList(files));
+ }
+
+ static Collection<Path> getFilesForClasses(Collection<Class<?>> classes) {
+ return ListUtils.map(classes, ToolHelper::getClassFileForTestClass);
+ }
+
+ static Collection<Path> getFilesForClassesAndInnerClasses(Collection<Class<?>> classes)
+ throws IOException {
+ Set<Path> paths = new HashSet<>();
+ for (Class clazz : classes) {
+ Path path = ToolHelper.getClassFileForTestClass(clazz);
+ String prefix = path.toString().replace(CLASS_EXTENSION, "$");
+ paths.addAll(
+ ToolHelper.getClassFilesForTestDirectory(
+ path.getParent(), p -> p.equals(path) || p.toString().startsWith(prefix)));
+ }
+ return paths;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
new file mode 100644
index 0000000..9996dde
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.AndroidApp;
+import java.io.IOException;
+import java.nio.file.Path;
+
+public class TestCompileResult {
+ private final TestState state;
+ private final Backend backend;
+ private final AndroidApp app;
+
+ public TestCompileResult(TestState state, Backend backend, AndroidApp app) {
+ this.state = state;
+ this.backend = backend;
+ this.app = app;
+ }
+
+ public TestRunResult run(String mainClass) throws IOException {
+ switch (backend) {
+ case DEX:
+ return runArt(mainClass);
+ case CF:
+ return runJava(mainClass);
+ default:
+ throw new Unreachable();
+ }
+ }
+
+ private TestRunResult runJava(String mainClass) throws IOException {
+ Path out = state.getNewTempFolder().resolve("out.zip");
+ app.writeToZip(out, OutputMode.ClassFile);
+ ProcessResult result = ToolHelper.runJava(out, mainClass);
+ return new TestRunResult(app, result);
+ }
+
+ private TestRunResult runArt(String mainClass) throws IOException {
+ Path out = state.getNewTempFolder().resolve("out.zip");
+ app.writeToZip(out, OutputMode.DexIndexed);
+ ProcessResult result = ToolHelper.runArtRaw(out.toString(), mainClass);
+ return new TestRunResult(app, result);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
new file mode 100644
index 0000000..c6af2c7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidAppConsumers;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+
+public abstract class TestCompilerBuilder<
+ C extends BaseCompilerCommand,
+ B extends BaseCompilerCommand.Builder<C, B>,
+ T extends TestCompilerBuilder<C, B, T>>
+ extends TestBuilder<T> {
+
+ private final B builder;
+ private final Backend backend;
+
+ // Default initialized setup. Can be overwritten if needed.
+ private Path defaultLibrary;
+ private ProgramConsumer programConsumer;
+ private AndroidApiLevel defaultMinApiLevel = ToolHelper.getMinApiLevelForDexVm();
+
+ TestCompilerBuilder(TestState state, B builder, Backend backend) {
+ super(state);
+ this.builder = builder;
+ this.backend = backend;
+ defaultLibrary = TestBase.runtimeJar(backend);
+ programConsumer = TestBase.emptyConsumer(backend);
+ }
+
+ abstract T self();
+
+ abstract void internalCompile(B builder) throws CompilationFailedException;
+
+ public TestCompileResult compile() throws CompilationFailedException {
+ AndroidAppConsumers sink = new AndroidAppConsumers();
+ builder.setProgramConsumer(sink.wrapProgramConsumer(programConsumer));
+ if (defaultLibrary != null) {
+ builder.addLibraryFiles(defaultLibrary);
+ }
+ if (backend == Backend.DEX && defaultMinApiLevel != null) {
+ builder.setMinApiLevel(defaultMinApiLevel.getLevel());
+ }
+ internalCompile(builder);
+ return new TestCompileResult(getState(), backend, sink.build());
+ }
+
+ @Override
+ public TestRunResult run(String mainClass) throws IOException, CompilationFailedException {
+ return compile().run(mainClass);
+ }
+
+ public T setMode(CompilationMode mode) {
+ builder.setMode(mode);
+ return self();
+ }
+
+ public T debug() {
+ return setMode(CompilationMode.DEBUG);
+ }
+
+ public T release() {
+ return setMode(CompilationMode.RELEASE);
+ }
+
+ public T setMinApi(AndroidApiLevel minApiLevel) {
+ // Should we ignore min-api calls when backend == CF?
+ this.defaultMinApiLevel = null;
+ builder.setMinApiLevel(minApiLevel.getLevel());
+ return self();
+ }
+
+ public T setProgramConsumer(ProgramConsumer programConsumer) {
+ assert programConsumer != null;
+ this.programConsumer = programConsumer;
+ return self();
+ }
+
+ @Override
+ public T addProgramFiles(Collection<Path> files) {
+ builder.addProgramFiles(files);
+ return self();
+ }
+
+ @Override
+ public T addLibraryFiles(Collection<Path> files) {
+ defaultLibrary = null;
+ builder.addLibraryFiles(files);
+ return self();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
new file mode 100644
index 0000000..462ca1f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+
+public class TestRunResult {
+ private final AndroidApp app;
+ private final ProcessResult result;
+
+ public TestRunResult(AndroidApp app, ProcessResult result) {
+ this.app = app;
+ this.result = result;
+ }
+
+ public TestRunResult assertSuccess() {
+ assertEquals(errorMessage("Expected run to succeed."), 0, result.exitCode);
+ return this;
+ }
+
+ public TestRunResult assertFailure() {
+ assertNotEquals(errorMessage("Expected run to fail."), 0, result.exitCode);
+ return this;
+ }
+
+ public void assertSuccessWithOutput(String expected) {
+ assertSuccess();
+ assertEquals(errorMessage("Run std output incorrect."), expected, result.stdout);
+ }
+
+ public CodeInspector inspector() throws IOException, ExecutionException {
+ // Inspection post run implies success. If inspection of an invalid program is needed it should
+ // be done on the compilation result or on the input.
+ assertSuccess();
+ assertNotNull(app);
+ return new CodeInspector(app);
+ }
+
+ private String errorMessage(String message) {
+ StringBuilder builder = new StringBuilder(message).append('\n');
+ printInfo(builder);
+ return builder.toString();
+ }
+
+ private void printInfo(StringBuilder builder) {
+ builder.append("APPLICATION: ");
+ printApplication(builder);
+ builder.append('\n');
+ printProcessResult(builder);
+ }
+
+ private void printApplication(StringBuilder builder) {
+ builder.append(app == null ? "<default>" : app.toString());
+ }
+
+ private void printProcessResult(StringBuilder builder) {
+ builder.append("COMMAND: ").append(result.command).append('\n').append(result);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestState.java b/src/test/java/com/android/tools/r8/TestState.java
new file mode 100644
index 0000000..d516c7c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestState.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import org.junit.rules.TemporaryFolder;
+
+public class TestState {
+
+ private final TemporaryFolder temp;
+
+ public TestState(TemporaryFolder temp) {
+ this.temp = temp;
+ }
+
+ public Path getNewTempFolder() throws IOException {
+ return temp.newFolder().toPath();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 515d191..8ae0dad 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1531,11 +1531,17 @@
public final int exitCode;
public final String stdout;
public final String stderr;
+ public final String command;
- ProcessResult(int exitCode, String stdout, String stderr) {
+ ProcessResult(int exitCode, String stdout, String stderr, String command) {
this.exitCode = exitCode;
this.stdout = stdout;
this.stderr = stderr;
+ this.command = command;
+ }
+
+ ProcessResult(int exitCode, String stdout, String stderr) {
+ this(exitCode, stdout, stderr, null);
}
@Override
@@ -1574,7 +1580,8 @@
}
public static ProcessResult runProcess(ProcessBuilder builder) throws IOException {
- System.out.println(String.join(" ", builder.command()));
+ String command = String.join(" ", builder.command());
+ System.out.println(command);
Process p = builder.start();
// Drain stdout and stderr so that the process does not block. Read stdout and stderr
// in parallel to make sure that neither buffer can get filled up which will cause the
@@ -1592,7 +1599,8 @@
} catch (InterruptedException e) {
throw new RuntimeException("Execution interrupted", e);
}
- return new ProcessResult(p.exitValue(), stdoutReader.getResult(), stderrReader.getResult());
+ return new ProcessResult(
+ p.exitValue(), stdoutReader.getResult(), stderrReader.getResult(), command);
}
public static R8Command.Builder addProguardConfigurationConsumer(
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java b/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java
index 9552a61..977b925 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java
@@ -3,17 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.memberrebinding;
-import static org.junit.Assert.assertEquals;
-
import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.R8Command.Builder;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import java.util.List;
import org.junit.Test;
@@ -149,38 +141,23 @@
@Test
public void test() throws Exception {
- String expected =
- String.join(
- System.lineSeparator(),
- "A::method -> InterfaceA::method",
- "B::method -> InterfaceB::method",
- "C::method -> InterfaceC::method",
- "D::method -> InterfaceD::method");
- assertEquals(expected, runOnJava(TestClass.class));
+ String expected = StringUtils.joinLines(
+ "A::method -> InterfaceA::method",
+ "B::method -> InterfaceB::method",
+ "C::method -> InterfaceC::method",
+ "D::method -> InterfaceD::method");
- AndroidAppConsumers sink = new AndroidAppConsumers();
- Builder builder = R8Command.builder();
- for (Class<?> clazz : CLASSES) {
- builder.addClassProgramData(ToolHelper.getClassAsBytes(clazz), Origin.unknown());
- }
- builder
- .setProgramConsumer(sink.wrapProgramConsumer(emptyConsumer(backend)))
- .addLibraryFiles(runtimeJar(backend))
- .addProguardConfiguration(
- ImmutableList.of(
- // Keep all classes to prevent changes to the class hierarchy (e.g., due to
- // vertical class merging).
- "-keep class " + InterfaceA.class.getPackage().getName() + ".*",
- keepMainProguardConfigurationWithInliningAnnotation(TestClass.class)),
- Origin.unknown());
- ToolHelper.allowTestProguardOptions(builder);
- if (backend == Backend.DEX) {
- builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
- }
- R8.run(builder.build());
+ testForJvm()
+ .addTestClasspath()
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expected);
- ProcessResult result = runOnVMRaw(sink.build(), TestClass.class, backend);
- assertEquals(result.toString(), 0, result.exitCode);
- assertEquals(expected, result.stdout);
+ testForR8(backend)
+ .addProgramClasses(CLASSES)
+ .addKeepPackageRules(TestClass.class.getPackage())
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expected);
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAttributesTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAttributesTest.java
index 96ce54a..6992f97 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAttributesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAttributesTest.java
@@ -9,15 +9,8 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.R8;
-import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.forceproguardcompatibility.keepattributes.TestKeepAttributes;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.AndroidAppConsumers;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
@@ -70,7 +63,6 @@
public void keepLineNumberTable()
throws CompilationFailedException, IOException, ExecutionException {
List<String> keepRules = ImmutableList.of(
- "-keep class ** { *; }",
"-keepattributes " + ProguardKeepAttributes.LINE_NUMBER_TABLE
);
MethodSubject mainMethod = compileRunAndGetMain(keepRules, CompilationMode.RELEASE);
@@ -82,7 +74,6 @@
public void keepLineNumberTableAndLocalVariableTable()
throws CompilationFailedException, IOException, ExecutionException {
List<String> keepRules = ImmutableList.of(
- "-keep class ** { *; }",
"-keepattributes "
+ ProguardKeepAttributes.LINE_NUMBER_TABLE
+ ", "
@@ -97,7 +88,6 @@
@Test
public void keepLocalVariableTable() throws IOException, ExecutionException {
List<String> keepRules = ImmutableList.of(
- "-keep class ** { *; }",
"-keepattributes " + ProguardKeepAttributes.LOCAL_VARIABLE_TABLE
);
// Compiling with a keep rule for locals but no line results in an error in R8.
@@ -113,21 +103,14 @@
private MethodSubject compileRunAndGetMain(List<String> keepRules, CompilationMode mode)
throws CompilationFailedException, IOException, ExecutionException {
- AndroidAppConsumers sink = new AndroidAppConsumers();
- R8.run(
- R8Command.builder()
- .setMode(mode)
- .addProgramFiles(
- ToolHelper.getClassFilesForTestDirectory(
- ToolHelper.getClassFileForTestClass(CLASS).getParent()))
- .addLibraryFiles(runtimeJar(backend))
- .addProguardConfiguration(keepRules, Origin.unknown())
- .setProgramConsumer(sink.wrapProgramConsumer(emptyConsumer(backend)))
- .build());
- AndroidApp app = sink.build();
- CodeInspector codeInspector = new CodeInspector(app);
- runOnVM(app, CLASS.getTypeName(), backend);
- return codeInspector.clazz(CLASS).mainMethod();
+ return testForR8(backend)
+ .setMode(mode)
+ .addProgramClassesAndInnerClasses(CLASS)
+ .addKeepAllClassesRule()
+ .addKeepRules(keepRules)
+ .run(CLASS)
+ .inspector()
+ .clazz(CLASS)
+ .mainMethod();
}
-
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeDirectlyTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeDirectlyTest.java
new file mode 100644
index 0000000..2523363
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeDirectlyTest.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2018, 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.ifrule.verticalclassmerging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+
+public class ExtendsMergedTypeDirectlyTest extends MergedTypeBaseTest {
+
+ static class TestClass extends C {
+
+ public static void main(String[] args) {
+ System.out.print("Hello world");
+ }
+ }
+
+ public ExtendsMergedTypeDirectlyTest(Backend backend, boolean enableVerticalClassMerging) {
+ super(backend, enableVerticalClassMerging);
+ }
+
+ @Override
+ public Class<?> getTestClass() {
+ return TestClass.class;
+ }
+
+ @Override
+ public String getConditionForProguardIfRule() {
+ // After class merging, TestClass will no longer extend C, but we should still keep the class
+ // Unused in the output.
+ return "-if class **$TestClass extends **$C";
+ }
+
+ @Override
+ public String getExpectedStdout() {
+ return "Hello world";
+ }
+
+ public void inspect(CodeInspector inspector) {
+ super.inspect(inspector);
+
+ if (enableVerticalClassMerging) {
+ // Check that TestClass no longer extends C.
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
+ assertEquals("java.lang.Object", testClassSubject.getDexClass().superType.toSourceString());
+
+ // Check that C is no longer present.
+ assertThat(inspector.clazz(C.class), not(isPresent()));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeIndirectlyTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeIndirectlyTest.java
new file mode 100644
index 0000000..10cff1e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeIndirectlyTest.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2018, 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.ifrule.verticalclassmerging;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+
+public class ExtendsMergedTypeIndirectlyTest extends MergedTypeBaseTest {
+
+ static class TestClass extends B {
+
+ public static void main(String[] args) {
+ // The instantiation of B prevents it from being merged into TestClass.
+ System.out.print(new B().getClass().getName());
+ }
+ }
+
+ public ExtendsMergedTypeIndirectlyTest(Backend backend, boolean enableVerticalClassMerging) {
+ super(backend, enableVerticalClassMerging);
+ }
+
+ @Override
+ public Class<?> getTestClass() {
+ return TestClass.class;
+ }
+
+ @Override
+ public String getConditionForProguardIfRule() {
+ // After class merging, B will no longer extend A (and therefore, TestClass will no longer
+ // extend A indirectly), but we should still keep the class Unused in the output.
+ return "-if class **$TestClass extends **$A";
+ }
+
+ @Override
+ public String getExpectedStdout() {
+ return B.class.getName();
+ }
+
+ public void inspect(CodeInspector inspector) {
+ super.inspect(inspector);
+
+ // Verify that TestClass still inherits from B.
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertEquals(B.class.getTypeName(), testClassSubject.getDexClass().superType.toSourceString());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
index 9d2e352..18783750 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
@@ -59,11 +59,9 @@
}
// TODO(b/110141157):
-// - Add tests where the return type of a kept method changes.
-// - Add tests where the parameter type of a kept method changes.
-// - Add tests where the type of a kept field changes.
// - Add tests where fields and methods get renamed due to naming conflicts.
-// - Add tests where the type in a implements/extends clause has changed.
+// - Add tests where the type in a implements clause has changed.
+// - Add tests where the then-clause of an -if rule keeps a class that has been merged into another.
@RunWith(Parameterized.class)
public class IfRuleWithVerticalClassMerging extends TestBase {
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
new file mode 100644
index 0000000..a877fec
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2018, 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.ifrule.verticalclassmerging;
+
+public class MergedFieldTypeTest extends MergedTypeBaseTest {
+
+ static class TestClass {
+
+ private static A field = new B();
+
+ public static void main(String[] args) {
+ System.out.print(field.getClass().getName());
+ }
+ }
+
+ public MergedFieldTypeTest(Backend backend, boolean enableVerticalClassMerging) {
+ super(backend, enableVerticalClassMerging);
+ }
+
+ @Override
+ public Class<?> getTestClass() {
+ return TestClass.class;
+ }
+
+ @Override
+ public String getConditionForProguardIfRule() {
+ return "-if class **$TestClass { **$A field; }";
+ }
+
+ @Override
+ public String getExpectedStdout() {
+ return B.class.getName();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
new file mode 100644
index 0000000..7447efd1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, 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.ifrule.verticalclassmerging;
+
+public class MergedParameterTypeTest extends MergedTypeBaseTest {
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ method(new B());
+ }
+
+ public static void method(A obj) {
+ System.out.print(obj.getClass().getName());
+ }
+ }
+
+ public MergedParameterTypeTest(Backend backend, boolean enableVerticalClassMerging) {
+ super(backend, enableVerticalClassMerging);
+ }
+
+ @Override
+ public Class<?> getTestClass() {
+ return TestClass.class;
+ }
+
+ @Override
+ public String getConditionForProguardIfRule() {
+ return "-if class **$TestClass { void method(**$A); }";
+ }
+
+ @Override
+ public String getExpectedStdout() {
+ return B.class.getName();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java
new file mode 100644
index 0000000..da4a9f1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, 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.ifrule.verticalclassmerging;
+
+public class MergedReturnTypeTest extends MergedTypeBaseTest {
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.print(method().getClass().getName());
+ }
+
+ public static A method() {
+ return new B();
+ }
+ }
+
+ public MergedReturnTypeTest(Backend backend, boolean enableVerticalClassMerging) {
+ super(backend, enableVerticalClassMerging);
+ }
+
+ @Override
+ public Class<?> getTestClass() {
+ return TestClass.class;
+ }
+
+ @Override
+ public String getConditionForProguardIfRule() {
+ return "-if class **$TestClass { **$A method(); }";
+ }
+
+ @Override
+ public String getExpectedStdout() {
+ return B.class.getName();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
new file mode 100644
index 0000000..d2ce210
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2018, 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.ifrule.verticalclassmerging;
+
+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.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public abstract class MergedTypeBaseTest extends TestBase {
+
+ private final List<Class> CLASSES =
+ ImmutableList.of(A.class, B.class, C.class, Unused.class, getTestClass());
+
+ static class A {}
+
+ static class B extends A {}
+
+ static class C {}
+
+ static class Unused {}
+
+ final Backend backend;
+ final boolean enableVerticalClassMerging;
+
+ public MergedTypeBaseTest(Backend backend, boolean enableVerticalClassMerging) {
+ this.backend = backend;
+ this.enableVerticalClassMerging = enableVerticalClassMerging;
+ }
+
+ @Parameters(name = "Backend: {0}, vertical class merging: {1}")
+ public static Collection<Object[]> data() {
+ // We don't run this on Proguard, as Proguard does not merge A into B.
+ return ImmutableList.of(
+ new Object[] {Backend.DEX, true},
+ new Object[] {Backend.DEX, false},
+ new Object[] {Backend.CF, true},
+ new Object[] {Backend.CF, false});
+ }
+
+ public abstract Class<?> getTestClass();
+
+ public abstract String getConditionForProguardIfRule();
+
+ public abstract String getExpectedStdout();
+
+ public void inspect(CodeInspector inspector) {
+ assertThat(inspector.clazz(Unused.class), isPresent());
+
+ // Verify that A is no longer present when vertical class merging is enabled.
+ if (enableVerticalClassMerging) {
+ assertThat(inspector.clazz(A.class), not(isPresent()));
+ }
+ }
+
+ @Test
+ public void testIfRule() throws Exception {
+ String expected = getExpectedStdout();
+ assertEquals(expected, runOnJava(getTestClass()));
+
+ String config =
+ StringUtils.joinLines(
+ "-keep class " + getTestClass().getTypeName() + " {",
+ " public static void main(java.lang.String[]);",
+ "}",
+ getConditionForProguardIfRule(),
+ "-keep class " + Unused.class.getTypeName());
+ AndroidApp output = compileWithR8(readClasses(CLASSES), config, this::configure, backend);
+ assertEquals(expected, runOnVM(output, getTestClass(), backend));
+ inspect(new CodeInspector(output));
+ }
+
+ private void configure(InternalOptions options) {
+ options.enableMinification = false;
+ options.enableVerticalClassMerging = enableVerticalClassMerging;
+
+ // TODO(b/110148109): Allow ordinary method inlining when -if rules work with inlining.
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ }
+}
diff --git a/third_party/dart-sdk.tar.gz.sha1 b/third_party/dart-sdk.tar.gz.sha1
new file mode 100644
index 0000000..f8b4a60
--- /dev/null
+++ b/third_party/dart-sdk.tar.gz.sha1
@@ -0,0 +1 @@
+0f15c6a81827ce4979b6fcf6183ef35df335d9dc
\ No newline at end of file
diff --git a/tools/disasm.py b/tools/disasm.py
index 0d2599a..a9a5a0a 100755
--- a/tools/disasm.py
+++ b/tools/disasm.py
@@ -7,4 +7,4 @@
import toolhelper
if __name__ == '__main__':
- sys.exit(toolhelper.run('disasm', sys.argv[1:]))
+ sys.exit(toolhelper.run('disasm', sys.argv[1:]), debug=False)
diff --git a/tools/performance_try.py b/tools/performance_try.py
new file mode 100755
index 0000000..5e6404d
--- /dev/null
+++ b/tools/performance_try.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+# Copyright (c) 2018, 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.
+
+import os
+import subprocess
+import sys
+import utils
+
+SCRIPT = '/google/data/ro/teams/dart/golem/bin/golem4.dart'
+DART = os.path.join(utils.THIRD_PARTY, 'dart-sdk', 'bin', 'dart')
+
+def Main():
+ args = sys.argv[1:]
+ if len(args) != 1:
+ print('Performance tracking takes exactly one argument, the name for display')
+ subprocess.check_call([DART, SCRIPT, args[0]])
+
+if __name__ == '__main__':
+ sys.exit(Main())
diff --git a/tools/test.py b/tools/test.py
index 72e7e01..cd9b70f 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -52,14 +52,14 @@
help='Print a line before a tests starts and after it ends to stdout.',
default=False, action='store_true')
result.add_option('--tool',
- help='Tool to run ART tests with: "r8" (default) or "d8". Ignored if'
- ' "--all_tests" enabled.',
- default=None, choices=["r8", "d8"])
+ help='Tool to run ART tests with: "r8" (default) or "d8" or "r8cf"'
+ ' (r8 w/CF-backend). Ignored if "--all_tests" enabled.',
+ default=None, choices=["r8", "d8", "r8cf"])
result.add_option('--jctf',
- help='Run JCTF tests with: "r8" (default) or "d8".',
+ help='Run JCTF tests with: "r8" (default) or "d8" or "r8cf".',
default=False, action='store_true')
result.add_option('--only-jctf', '--only_jctf',
- help='Run only JCTF tests with: "r8" (default) or "d8".',
+ help='Run only JCTF tests with: "r8" (default) or "d8" or "r8cf".',
default=False, action='store_true')
result.add_option('--jctf-compile-only', '--jctf_compile_only',
help="Don't run, only compile JCTF tests.",