Merge "Add Command API to disable desugaring"
diff --git a/build.gradle b/build.gradle
index 6be6012..7b83fcf 100644
--- a/build.gradle
+++ b/build.gradle
@@ -88,6 +88,12 @@
}
output.resourcesDir = 'build/classes/examplesAndroidO'
}
+ examplesAndroidP {
+ java {
+ srcDirs = ['src/test/examplesAndroidP']
+ }
+ output.resourcesDir = 'build/classes/examplesAndroidP'
+ }
jctfCommon {
java {
srcDirs = [
@@ -145,6 +151,7 @@
jctfTestsCompile 'junit:junit:4.12'
jctfTestsCompile sourceSets.jctfCommon.output
examplesAndroidOCompile group: 'org.ow2.asm', name: 'asm', version: '6.0_BETA'
+ examplesAndroidPCompile group: 'org.ow2.asm', name: 'asm', version: '6.0_BETA'
examplesCompile 'com.google.protobuf:protobuf-lite:3.0.0'
examplesRuntime 'com.google.protobuf:protobuf-lite:3.0.0'
supportLibs 'com.android.support:support-v4:25.4.0'
@@ -887,6 +894,51 @@
}
}
+task buildExampleAndroidPJars {
+ dependsOn downloadDeps
+ def examplesDir = file("src/test/examplesAndroidP")
+
+ task "compile_examplesAndroidP"(type: JavaCompile) {
+ source = fileTree(dir: examplesDir, include: '**/*.java')
+ destinationDir = file("build/test/examplesAndroidP/classes")
+ classpath = sourceSets.main.compileClasspath
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ options.compilerArgs += ["-Xlint:-options"]
+ }
+ examplesDir.eachDir { dir ->
+ def name = dir.getName();
+ def destinationDir = file("build/test/examplesAndroidP/classes");
+ if (file("src/test/examplesAndroidP/" + name + "/TestGenerator.java").isFile()) {
+ task "generate_examplesAndroidP_${name}"(type: JavaExec,
+ dependsOn: "compile_examplesAndroidP") {
+ main = name + ".TestGenerator"
+ classpath = files(destinationDir, sourceSets.main.compileClasspath)
+ args destinationDir
+ }
+ } else {
+ task "generate_examplesAndroidP_${name}" () {}
+ }
+ }
+ examplesDir.eachDir { dir ->
+ def name = dir.getName();
+ def exampleOutputDir = file("build/test/examplesAndroidP");
+ def jarName = "${name}.jar"
+ dependsOn "jar_examplesAndroidP_${name}"
+ task "jar_examplesAndroidP_${name}"(type: Jar,
+ dependsOn: ["compile_examplesAndroidP",
+ "generate_examplesAndroidP_${name}"]) {
+ archiveName = jarName
+ destinationDir = exampleOutputDir
+ from "build/test/examplesAndroidP/classes" // Java 1.8 classes
+ include "**/" + name + "/**/*.class"
+ // Do not include generator into the test runtime jar, it is not useful.
+ // Otherwise, shrinking will need ASM jars.
+ exclude "**/TestGenerator*"
+ }
+ }
+}
+
task buildExamples {
if (OperatingSystem.current().isMacOsX() || OperatingSystem.current().isWindows()) {
logger.lifecycle("WARNING: Testing (including building examples) is only partially supported on your " +
@@ -900,6 +952,7 @@
dependsOn buildExampleJars
dependsOn buildExampleAndroidNJars
dependsOn buildExampleAndroidOJars
+ dependsOn buildExampleAndroidPJars
def examplesDir = file("src/test/examples")
def noDexTests = [
"multidex",
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 296507f..a885bbb 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -42,7 +42,8 @@
AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
RootSet mainDexRootSet =
new RootSetBuilder(application, appInfo, options.mainDexKeepRules, options).run(executor);
- Set<DexType> mainDexBaseClasses = new Enqueuer(appInfo).traceMainDex(mainDexRootSet, timing);
+ Set<DexType> mainDexBaseClasses =
+ new Enqueuer(appInfo, options).traceMainDex(mainDexRootSet, timing);
Set<DexType> mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
List<String> result = mainDexClasses.stream()
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 39e0342..cbb7f0b 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -221,7 +221,7 @@
new RootSetBuilder(
application, appInfo, options.proguardConfiguration.getRules(), options)
.run(executorService);
- Enqueuer enqueuer = new Enqueuer(appInfo);
+ Enqueuer enqueuer = new Enqueuer(appInfo, options);
enqueuer.addExtension(new ProtoLiteExtension(appInfo));
appInfo = enqueuer.traceApplication(rootSet, timing);
if (options.proguardConfiguration.isPrintSeeds()) {
@@ -282,7 +282,7 @@
if (!options.mainDexKeepRules.isEmpty()) {
appInfo = new AppInfoWithSubtyping(application);
- Enqueuer enqueuer = new Enqueuer(appInfo);
+ Enqueuer enqueuer = new Enqueuer(appInfo, options);
// Lets find classes which may have code executed before secondary dex files installation.
RootSet mainDexRootSet =
new RootSetBuilder(application, appInfo, options.mainDexKeepRules, options)
@@ -301,7 +301,7 @@
if (options.useTreeShaking || !options.skipMinification) {
timing.begin("Post optimization code stripping");
try {
- Enqueuer enqueuer = new Enqueuer(appInfo);
+ Enqueuer enqueuer = new Enqueuer(appInfo, options);
appInfo = enqueuer.traceApplication(rootSet, timing);
if (options.useTreeShaking) {
TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 502dd3c..9529093 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -36,12 +36,18 @@
private Optional<Boolean> discardedChecker = Optional.empty();
private Optional<Boolean> minification = Optional.empty();
private boolean ignoreMissingClasses = false;
+ private boolean forceProguardCompatibility = false;
private Path proguardMapOutput = null;
private Builder() {
super(CompilationMode.RELEASE);
}
+ protected Builder(boolean forceProguardCompatibility) {
+ super(CompilationMode.RELEASE);
+ this.forceProguardCompatibility = forceProguardCompatibility;
+ }
+
private Builder(AndroidApp app) {
super(app, CompilationMode.RELEASE);
}
@@ -243,6 +249,7 @@
useDiscardedChecker,
useMinification,
ignoreMissingClasses,
+ forceProguardCompatibility,
proguardMapOutput);
}
}
@@ -283,6 +290,7 @@
private final boolean useDiscardedChecker;
private final boolean useMinification;
private final boolean ignoreMissingClasses;
+ private final boolean forceProguardCompatibility;
private final Path proguardMapOutput;
public static Builder builder() {
@@ -400,6 +408,7 @@
boolean useDiscardedChecker,
boolean useMinification,
boolean ignoreMissingClasses,
+ boolean forceProguardCompatibility,
Path proguardMapOutput) {
super(inputApp, outputPath, outputMode, mode, minApiLevel, diagnosticsHandler,
enableDesugaring);
@@ -413,6 +422,7 @@
this.useDiscardedChecker = useDiscardedChecker;
this.useMinification = useMinification;
this.ignoreMissingClasses = ignoreMissingClasses;
+ this.forceProguardCompatibility = forceProguardCompatibility;
this.proguardMapOutput = proguardMapOutput;
}
@@ -425,6 +435,7 @@
useDiscardedChecker = false;
useMinification = false;
ignoreMissingClasses = false;
+ forceProguardCompatibility = false;
proguardMapOutput = null;
}
public boolean useTreeShaking() {
@@ -477,6 +488,11 @@
internal.inlineAccessors = false;
}
internal.proguardMapOutput = proguardMapOutput;
+
+ // EXPERIMENTAL flags.
+ assert !internal.forceProguardCompatibility;
+ internal.forceProguardCompatibility = forceProguardCompatibility;
+
return internal;
}
}
diff --git a/src/main/java/com/android/tools/r8/code/ConstClass.java b/src/main/java/com/android/tools/r8/code/ConstClass.java
index 04fda4b..a5755e9 100644
--- a/src/main/java/com/android/tools/r8/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/code/ConstClass.java
@@ -36,7 +36,7 @@
@Override
public void registerUse(UseRegistry registry) {
- registry.registerTypeReference(getType());
+ registry.registerConstClass(getType());
}
public DexType getType() {
diff --git a/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
new file mode 100644
index 0000000..e2c084b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2017, 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.code;
+
+import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.errors.InternalCompilerError;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.naming.ClassNameMapper;
+import java.nio.ShortBuffer;
+
+public class ConstMethodHandle extends Format21c {
+
+ public static final int OPCODE = 0xfe;
+ public static final String NAME = "ConstMethodHandle";
+ public static final String SMALI_NAME = "const-method-handle";
+
+ ConstMethodHandle(int high, BytecodeStream stream, OffsetToObjectMapping mapping) {
+ super(high, stream, mapping.getMethodHandleMap());
+ }
+
+ public ConstMethodHandle(int register, DexMethodHandle methodHandle) {
+ super(register, methodHandle);
+ }
+
+ public DexMethodHandle getMethodHandle() {
+ return (DexMethodHandle) BBBB;
+ }
+
+ public String getName() {
+ return NAME;
+ }
+
+ public String getSmaliName() {
+ return SMALI_NAME;
+ }
+
+ public int getOpcode() {
+ return OPCODE;
+ }
+
+ public String toString(ClassNameMapper naming) {
+ return formatString("v" + AA + ", \"" + BBBB.toString() + "\"");
+ }
+
+ public String toSmaliString(ClassNameMapper naming) {
+ return formatSmaliString("v" + AA + ", \"" + BBBB.toString() + "\"");
+ }
+
+ @Override
+ public void registerUse(UseRegistry registry) {
+ registry.registerMethodHandle(getMethodHandle());
+ }
+
+ @Override
+ public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+ int index = BBBB.getOffset(mapping);
+ if (index != (index & 0xffff)) {
+ throw new InternalCompilerError("MethodHandle-index overflow.");
+ }
+ super.write(dest, mapping);
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder) throws ApiLevelException {
+ builder.addConstMethodHandle(AA, (DexMethodHandle) BBBB);
+ }
+
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/code/InvokeCustom.java
index 3c0b67c..c4a16b5 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeCustom.java
@@ -4,8 +4,6 @@
package com.android.tools.r8.code;
import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
import com.android.tools.r8.graph.DexValue.DexValueType;
@@ -61,54 +59,15 @@
}
static void registerCallSite(UseRegistry registry, DexCallSite callSite) {
- InvokeCustom.registerMethodHandle(registry, callSite.bootstrapMethod);
+ registry.registerMethodHandle(callSite.bootstrapMethod);
// Register bootstrap method arguments, only Type and MethodHandle need to be register.
for (DexValue arg : callSite.bootstrapArgs) {
if (arg instanceof DexValueType) {
registry.registerTypeReference(((DexValueType) arg).value);
} else if (arg instanceof DexValueMethodHandle) {
- InvokeCustom.registerMethodHandle(registry, ((DexValueMethodHandle) arg).value);
+ registry.registerMethodHandle(((DexValueMethodHandle) arg).value);
}
}
}
-
- static void registerMethodHandle(UseRegistry registry, DexMethodHandle methodHandle) {
- switch (methodHandle.type) {
- case INSTANCE_GET:
- registry.registerInstanceFieldRead(methodHandle.asField());
- break;
- case INSTANCE_PUT:
- registry.registerInstanceFieldWrite(methodHandle.asField());
- break;
- case STATIC_GET:
- registry.registerStaticFieldRead(methodHandle.asField());
- break;
- case STATIC_PUT:
- registry.registerStaticFieldWrite(methodHandle.asField());
- break;
- case INVOKE_INSTANCE:
- registry.registerInvokeVirtual(methodHandle.asMethod());
- break;
- case INVOKE_STATIC:
- registry.registerInvokeStatic(methodHandle.asMethod());
- break;
- case INVOKE_CONSTRUCTOR:
- DexMethod method = methodHandle.asMethod();
- registry.registerNewInstance(method.getHolder());
- registry.registerInvokeDirect(method);
- break;
- case INVOKE_INTERFACE:
- registry.registerInvokeInterface(methodHandle.asMethod());
- break;
- case INVOKE_SUPER:
- registry.registerInvokeSuper(methodHandle.asMethod());
- break;
- case INVOKE_DIRECT:
- registry.registerInvokeDirect(methodHandle.asMethod());
- break;
- default:
- throw new AssertionError();
- }
- }
}
diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
index 1ad589f..9dc5424 100644
--- a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
+++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
@@ -26,17 +26,21 @@
public static class CompatProguardOptions {
public final String output;
public final int minApi;
+ public final boolean forceProguardCompatibility;
public final List<String> proguardConfig;
- CompatProguardOptions(List<String> proguardConfig, String output, int minApi) {
+ CompatProguardOptions(List<String> proguardConfig, String output, int minApi,
+ boolean forceProguardCompatibility) {
this.output = output;
this.minApi = minApi;
+ this.forceProguardCompatibility = forceProguardCompatibility;
this.proguardConfig = proguardConfig;
}
public static CompatProguardOptions parse(String[] args) throws CompilationException {
String output = null;
int minApi = 1;
+ boolean forceProguardCompatibility = false;
ImmutableList.Builder<String> builder = ImmutableList.builder();
if (args.length > 0) {
StringBuilder currentLine = new StringBuilder(args[0]);
@@ -45,6 +49,8 @@
if (arg.charAt(0) == '-') {
if (arg.equals("--min-api")) {
minApi = Integer.valueOf(args[++i]);
+ } else if (arg.equals("--force-proguard-compatibility")) {
+ forceProguardCompatibility = true;
} else if (arg.equals("--output")) {
output = args[++i];
} else if (arg.equals("-outjars")) {
@@ -60,7 +66,7 @@
}
builder.add(currentLine.toString());
}
- return new CompatProguardOptions(builder.build(), output, minApi);
+ return new CompatProguardOptions(builder.build(), output, minApi, forceProguardCompatibility);
}
}
@@ -68,11 +74,12 @@
System.out.println("CompatProguard " + String.join(" ", args));
// Run R8 passing all the options from the command line as a Proguard configuration.
CompatProguardOptions options = CompatProguardOptions.parse(args);
- R8.run(R8Command.builder()
- .setOutputPath(Paths.get(options.output))
+ R8Command.Builder builder =
+ new CompatProguardCommandBuilder(options.forceProguardCompatibility);
+ builder.setOutputPath(Paths.get(options.output))
.addProguardConfiguration(options.proguardConfig)
- .setMinApiLevel(options.minApi)
- .build());
+ .setMinApiLevel(options.minApi);
+ R8.run(builder.build());
}
public static void main(String[] args) throws IOException {
diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguardCommandBuilder.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguardCommandBuilder.java
new file mode 100644
index 0000000..df2298c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguardCommandBuilder.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2017, 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.compatproguard;
+
+import com.android.tools.r8.R8Command;
+
+public class CompatProguardCommandBuilder extends R8Command.Builder {
+ CompatProguardCommandBuilder(boolean forceProguardCompatibility) {
+ super(forceProguardCompatibility);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 5de6538..e454d89 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -239,6 +239,19 @@
return true;
}
+ public boolean hasDefaultInitializer() {
+ return getDefaultInitializer() != null;
+ }
+
+ public DexEncodedMethod getDefaultInitializer() {
+ for (DexEncodedMethod method : directMethods()) {
+ if (method.isDefaultInitializer()) {
+ return method;
+ }
+ }
+ return null;
+ }
+
public boolean defaultValuesForStaticFieldsMayTriggerAllocation() {
return Arrays.stream(staticFields())
.anyMatch(field -> !field.staticValue.mayTriggerAllocation());
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 7766a94..a541f5a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -204,6 +204,10 @@
return accessFlags.isConstructor() && !accessFlags.isStatic();
}
+ public boolean isDefaultInitializer() {
+ return isInstanceInitializer() && method.proto.parameters.isEmpty();
+ }
+
public boolean isClassInitializer() {
return accessFlags.isConstructor() && accessFlags.isStatic();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
index eeba4e8..d64a893 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -4,8 +4,10 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.naming.NamingLens;
-public class DexMethodHandle extends IndexedDexItem {
+public class DexMethodHandle extends IndexedDexItem implements
+ PresortedComparable<DexMethodHandle> {
public enum MethodHandleType {
STATIC_PUT((short) 0x00),
@@ -190,4 +192,51 @@
assert isFieldHandle();
return (DexField) fieldOrMethod;
}
+
+ @Override
+ public int slowCompareTo(DexMethodHandle other) {
+ int result = type.getValue() - other.type.getValue();
+ if (result == 0) {
+ if (isFieldHandle()) {
+ result = asField().slowCompareTo(other.asField());
+ } else {
+ assert isMethodHandle();
+ result = asMethod().slowCompareTo(other.asMethod());
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public int slowCompareTo(DexMethodHandle other, NamingLens namingLens) {
+ int result = type.getValue() - other.type.getValue();
+ if (result == 0) {
+ if (isFieldHandle()) {
+ result = asField().slowCompareTo(other.asField(), namingLens);
+ } else {
+ assert isMethodHandle();
+ result = asMethod().slowCompareTo(other.asMethod(), namingLens);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public int layeredCompareTo(DexMethodHandle other, NamingLens namingLens) {
+ int result = type.getValue() - other.type.getValue();
+ if (result == 0) {
+ if (isFieldHandle()) {
+ result = asField().layeredCompareTo(other.asField(), namingLens);
+ } else {
+ assert isMethodHandle();
+ result = asMethod().layeredCompareTo(other.asMethod(), namingLens);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public int compareTo(DexMethodHandle other) {
+ return sortedCompareTo(other.getSortedIndex());
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 45824c2..f6736fa 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -26,4 +26,47 @@
public abstract boolean registerStaticFieldWrite(DexField field);
public abstract boolean registerTypeReference(DexType type);
+
+ public void registerMethodHandle(DexMethodHandle methodHandle) {
+ switch (methodHandle.type) {
+ case INSTANCE_GET:
+ registerInstanceFieldRead(methodHandle.asField());
+ break;
+ case INSTANCE_PUT:
+ registerInstanceFieldWrite(methodHandle.asField());
+ break;
+ case STATIC_GET:
+ registerStaticFieldRead(methodHandle.asField());
+ break;
+ case STATIC_PUT:
+ registerStaticFieldWrite(methodHandle.asField());
+ break;
+ case INVOKE_INSTANCE:
+ registerInvokeVirtual(methodHandle.asMethod());
+ break;
+ case INVOKE_STATIC:
+ registerInvokeStatic(methodHandle.asMethod());
+ break;
+ case INVOKE_CONSTRUCTOR:
+ DexMethod method = methodHandle.asMethod();
+ registerNewInstance(method.getHolder());
+ registerInvokeDirect(method);
+ break;
+ case INVOKE_INTERFACE:
+ registerInvokeInterface(methodHandle.asMethod());
+ break;
+ case INVOKE_SUPER:
+ registerInvokeSuper(methodHandle.asMethod());
+ break;
+ case INVOKE_DIRECT:
+ registerInvokeDirect(methodHandle.asMethod());
+ break;
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ public boolean registerConstClass(DexType type) {
+ return registerTypeReference(type);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
new file mode 100644
index 0000000..108eb32
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2017, 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.ir.code;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.conversion.DexBuilder;
+
+public class ConstMethodHandle extends ConstInstruction {
+
+ private final DexMethodHandle methodHandle;
+
+ public ConstMethodHandle(Value dest, DexMethodHandle methodHandle) {
+ super(dest);
+ dest.markNeverNull();
+ this.methodHandle = methodHandle;
+ }
+
+ public Value dest() {
+ return outValue;
+ }
+
+ public DexMethodHandle getValue() {
+ return methodHandle;
+ }
+
+ @Override
+ public void buildDex(DexBuilder builder) {
+ int dest = builder.allocatedRegister(dest(), getNumber());
+ builder.add(this, new com.android.tools.r8.code.ConstMethodHandle(dest, methodHandle));
+ }
+
+ @Override
+ public boolean identicalNonValueParts(Instruction other) {
+ return other.asConstMethodHandle().methodHandle == methodHandle;
+ }
+
+ @Override
+ public int compareNonValueParts(Instruction other) {
+ return methodHandle.slowCompareTo(other.asConstMethodHandle().methodHandle);
+ }
+
+ @Override
+ public int maxInValueRegister() {
+ assert false : "ConstMethodHandle has no register arguments.";
+ return 0;
+ }
+
+ @Override
+ public int maxOutValueRegister() {
+ return Constants.U8BIT_MAX;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " \"" + methodHandle + "\"";
+ }
+
+ @Override
+ public boolean instructionTypeCanThrow() {
+ return true;
+ }
+
+ @Override
+ public boolean isOutConstant() {
+ return true;
+ }
+
+ @Override
+ public boolean isConstMethodHandle() {
+ return true;
+ }
+
+ @Override
+ public ConstMethodHandle asConstMethodHandle() {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
index 90a9b29..511293f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
@@ -10,10 +10,9 @@
import java.util.List;
public class DominatorTree {
-
private BasicBlock[] sorted;
private BasicBlock[] doms;
- private final BasicBlock normalExitBlock = new BasicBlock();
+ private BasicBlock normalExitBlock = new BasicBlock();
public DominatorTree(IRCode code) {
this(code, Collections.emptyList());
@@ -23,13 +22,14 @@
DominatorTree(IRCode code, List<BasicBlock> blocksToIgnore) {
BasicBlock[] blocks = code.topologicallySortedBlocks(blocksToIgnore);
// Add the internal exit block to the block list.
+ for (BasicBlock block : blocks) {
+ if (block.exit().isReturn()) {
+ normalExitBlock.getPredecessors().add(block);
+ }
+ }
sorted = new BasicBlock[blocks.length + 1];
System.arraycopy(blocks, 0, sorted, 0, blocks.length);
sorted[blocks.length] = normalExitBlock;
- // Link internal exit block to each actual exit block.
- for (BasicBlock block : code.computeNormalExitBlocks()) {
- normalExitBlock.getPredecessors().add(block);
- }
numberBlocks();
build();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 21c2abf..24c3925 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -450,6 +450,14 @@
return null;
}
+ public boolean isConstMethodHandle() {
+ return false;
+ }
+
+ public ConstMethodHandle asConstMethodHandle() {
+ return null;
+ }
+
public boolean isConstString() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 35cd7fa..c84adcb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -32,6 +32,7 @@
import com.android.tools.r8.ir.code.Cmp;
import com.android.tools.r8.ir.code.Cmp.Bias;
import com.android.tools.r8.ir.code.ConstClass;
+import com.android.tools.r8.ir.code.ConstMethodHandle;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.ConstString;
import com.android.tools.r8.ir.code.ConstType;
@@ -799,6 +800,19 @@
add(instruction);
}
+ public void addConstMethodHandle(int dest, DexMethodHandle methodHandle)
+ throws ApiLevelException {
+ if (!options.canUseConstantMethodHandle()) {
+ throw new ApiLevelException(
+ AndroidApiLevel.P,
+ "Const-method-handle",
+ null /* sourceString */);
+ }
+ Value out = writeRegister(dest, MoveType.OBJECT, ThrowingInfo.CAN_THROW);
+ ConstMethodHandle instruction = new ConstMethodHandle(out, methodHandle);
+ add(instruction);
+ }
+
public void addConstString(int dest, DexString string) {
Value out = writeRegister(dest, MoveType.OBJECT, ThrowingInfo.CAN_THROW);
ConstString instruction = new ConstString(out, string);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index e44ee80..a4203a4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -142,6 +142,7 @@
static final Type STRING_TYPE = Type.getObjectType("java/lang/String");
static final Type INT_ARRAY_TYPE = Type.getObjectType(INT_ARRAY_DESC);
static final Type THROWABLE_TYPE = Type.getObjectType("java/lang/Throwable");
+ static final Type METHOD_HANDLE_TYPE = Type.getObjectType("java/lang/invoke/MethodHandle");
private static final int[] NO_TARGETS = {};
@@ -619,7 +620,7 @@
case Opcodes.LDC: {
// const-class and const-string* may throw in dex.
LdcInsnNode ldc = (LdcInsnNode) insn;
- return ldc.cst instanceof String || ldc.cst instanceof Type;
+ return ldc.cst instanceof String || ldc.cst instanceof Type || ldc.cst instanceof Handle;
}
default:
return false;
@@ -1727,6 +1728,8 @@
state.push(Type.INT_TYPE);
} else if (insn.cst instanceof Float) {
state.push(Type.FLOAT_TYPE);
+ } else if (insn.cst instanceof Handle) {
+ state.push(METHOD_HANDLE_TYPE);
} else {
throw new CompilationError("Unsupported constant: " + insn.cst.toString());
}
@@ -2724,7 +2727,7 @@
}
}
- private void build(LdcInsnNode insn, IRBuilder builder) {
+ private void build(LdcInsnNode insn, IRBuilder builder) throws ApiLevelException {
if (insn.cst instanceof Type) {
Type type = (Type) insn.cst;
int dest = state.push(type);
@@ -2744,6 +2747,10 @@
} else if (insn.cst instanceof Float) {
int dest = state.push(Type.FLOAT_TYPE);
builder.addFloatConst(dest, Float.floatToRawIntBits((Float) insn.cst));
+ } else if (insn.cst instanceof Handle) {
+ Handle handle = (Handle) insn.cst;
+ int dest = state.push(METHOD_HANDLE_TYPE);
+ builder.addConstMethodHandle(dest, getMethodHandle(application, handle));
} else {
throw new CompilationError("Unsupported constant: " + insn.cst.toString());
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index 8b7a05e..1921cba 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -45,7 +45,7 @@
void finish() {
if (Log.ENABLED) {
- System.out.println(info.toString());
+ Log.debug(getClass(), info.toString());
}
}
diff --git a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
index 2d38bf0..e1599b9 100644
--- a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
@@ -48,7 +48,9 @@
@Override
public void visitLdcInsn(Object cst) {
if (cst instanceof Type) {
- registry.registerTypeReference(application.getType((Type) cst));
+ registry.registerConstClass(application.getType((Type) cst));
+ } else if (cst instanceof Handle) {
+ registerMethodHandleType((Handle) cst);
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/DictionaryReader.java b/src/main/java/com/android/tools/r8/naming/DictionaryReader.java
index 1d05d25..0b26acd 100644
--- a/src/main/java/com/android/tools/r8/naming/DictionaryReader.java
+++ b/src/main/java/com/android/tools/r8/naming/DictionaryReader.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
+import com.android.tools.r8.CompilationException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import java.io.BufferedReader;
@@ -51,7 +52,7 @@
}
}
- public static ImmutableList<String> readAllNames(Path path) {
+ public static ImmutableList<String> readAllNames(Path path) throws CompilationException {
if (path != null) {
Builder<String> namesBuilder = new ImmutableList.Builder<String>();
try (DictionaryReader reader = new DictionaryReader(path);) {
@@ -61,7 +62,8 @@
name = reader.readName();
}
} catch (IOException e) {
- System.err.println("Unable to create dictionary from file " + path.toString());
+ throw new CompilationException(
+ "Unable to create dictionary from file " + path.toString(), e);
}
return namesBuilder.build();
} else {
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index 1537f05..762bdb6 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
+import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
@@ -280,6 +281,10 @@
method.proto.returnType.toSourceString(), paramNames);
}
+ public static MethodSignature initializer(String[] parameters) {
+ return new MethodSignature(Constants.INSTANCE_INITIALIZER_NAME, "void", parameters);
+ }
+
@Override
Signature asRenamed(String renamedName) {
return new MethodSignature(renamedName, type, parameters);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index ff8adee..358c1d3 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -28,6 +28,7 @@
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.Timing;
import com.google.common.base.Equivalence.Wrapper;
@@ -69,6 +70,7 @@
public class Enqueuer {
private final AppInfoWithSubtyping appInfo;
+ private final InternalOptions options;
private RootSet rootSet;
private Map<DexType, Set<DexMethod>> virtualInvokes = Maps.newIdentityHashMap();
@@ -156,8 +158,9 @@
*/
private final Map<DexType, Set<DexAnnotation>> deferredAnnotations = new IdentityHashMap<>();
- public Enqueuer(AppInfoWithSubtyping appInfo) {
+ public Enqueuer(AppInfoWithSubtyping appInfo, InternalOptions options) {
this.appInfo = appInfo;
+ this.options = options;
}
public void addExtension(SemanticsProvider extension) {
@@ -332,6 +335,30 @@
}
return false;
}
+
+ @Override
+ public boolean registerConstClass(DexType type) {
+ boolean result = registerTypeReference(type);
+ // For Proguard compatibility mark default initializer for live type as live.
+ if (options.forceProguardCompatibility) {
+ if (type.isArrayType()) {
+ return result;
+ }
+ assert type.isClassType();
+ DexClass holder = appInfo.definitionFor(type);
+ if (holder == null) {
+ // Don't call reportMissingClass(type) here. That already happened in the call to
+ // registerTypeReference(type).
+ return result;
+ }
+ if (holder.hasDefaultInitializer()) {
+ registerNewInstance(type);
+ DexEncodedMethod init = holder.getDefaultInitializer();
+ markDirectStaticOrConstructorMethodAsLive(init, KeepReason.reachableFromLiveType(type));
+ }
+ }
+ return result;
+ }
}
//
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 10daea7..fd08f51 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.CompilationException;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.DictionaryReader;
import com.android.tools.r8.utils.InternalOptions.KeepAttributeOptions;
@@ -170,7 +171,7 @@
return keepParameterNames;
}
- public ProguardConfiguration build() {
+ public ProguardConfiguration build() throws CompilationException {
return new ProguardConfiguration(
dexItemFactory,
injars,
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 06fa9ed..a6e5703 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.CompilationException;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.DexAccessFlags;
import com.android.tools.r8.graph.DexField;
@@ -76,7 +77,8 @@
return configurationBuilder;
}
- public ProguardConfiguration getConfig() throws ProguardRuleParserException {
+ public ProguardConfiguration getConfig()
+ throws ProguardRuleParserException, CompilationException {
if (configurationBuilder.isKeepParameterNames()
&& configurationBuilder.isObfuscating()) {
// The flag -keepparameternames has only effect when minifying, so ignore it if we
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 947abe3..10716bd 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -76,7 +76,7 @@
} else {
newClasses.add(clazz);
if (!appInfo.instantiatedTypes.contains(clazz.type) &&
- (!options.debugKeepRules || !hasDefaultConstructor(clazz))) {
+ (!options.debugKeepRules || !clazz.hasDefaultInitializer())) {
// The class is only needed as a type but never instantiated. Make it abstract to reflect
// this.
if (clazz.accessFlags.isFinal()) {
@@ -102,15 +102,6 @@
return newClasses;
}
- private boolean hasDefaultConstructor(DexProgramClass clazz) {
- for (DexEncodedMethod method : clazz.directMethods()) {
- if (isDefaultConstructor(method)) {
- return true;
- }
- }
- return false;
- }
-
private <S extends PresortedComparable<S>, T extends KeyedDexItem<S>> int firstUnreachableIndex(
T[] items, Set<S> live) {
for (int i = 0; i < items.length; i++) {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index c8308a9..9f883bf 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -103,6 +103,8 @@
public String printCfgFile;
public Path printMainDexListFile;
public boolean ignoreMissingClasses = false;
+ // EXPERIMENTAL flag to get behaviour as close to Proguard as possible.
+ public boolean forceProguardCompatibility = false;
public boolean skipMinification = false;
public boolean disableAssertions = true;
public boolean debugKeepRules = false;
@@ -348,6 +350,10 @@
return minApiLevel >= AndroidApiLevel.O.getLevel();
}
+ public boolean canUseConstantMethodHandle() {
+ return minApiLevel >= AndroidApiLevel.P.getLevel();
+ }
+
public boolean canUseInvokeCustom() {
return minApiLevel >= AndroidApiLevel.O.getLevel();
}
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
index a2bfeda..8fe3af6 100644
--- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -27,11 +27,8 @@
}
public static ExecutorService getExecutorService(int threads) {
- if (threads == 1) {
- return Executors.newSingleThreadExecutor();
- } else {
- return Executors.newWorkStealingPool(threads);
- }
+ // Don't use Executors.newSingleThreadExecutor() when threads == 1, see b/67338394.
+ return Executors.newWorkStealingPool(threads);
}
public static ExecutorService getExecutorService(InternalOptions options) {
@@ -40,7 +37,6 @@
// This heuristic is based on measurements on a 32 core (hyper-threaded) machine.
threads = Integer.min(Runtime.getRuntime().availableProcessors(), 16) / 2;
}
- // Don't use Executors.newSingleThreadExecutor() when threads == 1, see b/67338394.
- return Executors.newWorkStealingPool(threads);
+ return getExecutorService(threads);
}
}
diff --git a/src/test/examplesAndroidP/invokecustom/InvokeCustom.java b/src/test/examplesAndroidP/invokecustom/InvokeCustom.java
new file mode 100644
index 0000000..282b5ae
--- /dev/null
+++ b/src/test/examplesAndroidP/invokecustom/InvokeCustom.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2017, 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 invokecustom;
+
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+
+public class InvokeCustom {
+
+ private static String staticField1 = "StaticField1";
+
+ private static void targetMethodTest1() {
+ System.out.println("Hello World!");
+ }
+
+ private static void targetMethodTest2(MethodHandle mhInvokeStatic, MethodHandle mhGetStatic)
+ throws Throwable {
+ mhInvokeStatic.invokeExact();
+ System.out.println(mhGetStatic.invoke());
+ }
+
+ public static CallSite bsmLookupStatic(MethodHandles.Lookup caller, String name, MethodType type)
+ throws NoSuchMethodException, IllegalAccessException {
+ final MethodHandles.Lookup lookup = MethodHandles.lookup();
+ final MethodHandle targetMH = lookup.findStatic(lookup.lookupClass(), name, type);
+ return new ConstantCallSite(targetMH.asType(type));
+ }
+
+}
diff --git a/src/test/examplesAndroidP/invokecustom/TestGenerator.java b/src/test/examplesAndroidP/invokecustom/TestGenerator.java
new file mode 100644
index 0000000..b9a5126
--- /dev/null
+++ b/src/test/examplesAndroidP/invokecustom/TestGenerator.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2017, 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 invokecustom;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+public class TestGenerator {
+
+ private final Path classNamePath;
+
+ public static void main(String[] args) throws IOException {
+ assert args.length == 1;
+ TestGenerator testGenerator = new TestGenerator(Paths.get(args[0],
+ TestGenerator.class.getPackage().getName(), InvokeCustom.class.getSimpleName() + ".class"));
+ testGenerator.generateTests();
+ }
+
+ public TestGenerator(Path classNamePath) {
+ this.classNamePath = classNamePath;
+ }
+
+ private void generateTests() throws IOException {
+ ClassReader cr = new ClassReader(new FileInputStream(classNamePath.toFile()));
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+ cr.accept(
+ new ClassVisitor(Opcodes.ASM6, cw) {
+ @Override
+ public void visitEnd() {
+ generateMethodTest1(cw);
+ generateMethodMain(cw);
+ super.visitEnd();
+ }
+ }, 0);
+ new FileOutputStream(classNamePath.toFile()).write(cw.toByteArray());
+ }
+
+ /* Generate main method that only call all test methods. */
+ private void generateMethodMain(ClassVisitor cv) {
+ MethodVisitor mv = cv.visitMethod(
+ Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ mv.visitMethodInsn(
+ Opcodes.INVOKESTATIC, Type.getInternalName(InvokeCustom.class), "test1", "()V", false);
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(-1, -1);
+ }
+
+ /**
+ * Generate test with an invokedynamic, a static bootstrap method without extra args and
+ * args to the target method.
+ */
+ private void generateMethodTest1(ClassVisitor cv) {
+ MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test1", "()V",
+ null, null);
+ MethodType mt = MethodType.methodType(
+ CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
+ Handle bootstrap = new Handle( Opcodes.H_INVOKESTATIC, Type.getInternalName(InvokeCustom.class),
+ "bsmLookupStatic", mt.toMethodDescriptorString(), false);
+ mv.visitLdcInsn(new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(InvokeCustom.class),
+ "targetMethodTest1", "()V", false));
+ mv.visitLdcInsn(new Handle(Opcodes.H_GETSTATIC, Type.getInternalName(InvokeCustom.class),
+ "staticField1", "Ljava/lang/String;", false));
+ mv.visitInvokeDynamicInsn("targetMethodTest2",
+ "(Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)V",
+ bootstrap);
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(-1, -1);
+ }
+}
diff --git a/src/test/examplesAndroidP/invokecustom/keep-rules.txt b/src/test/examplesAndroidP/invokecustom/keep-rules.txt
new file mode 100644
index 0000000..dbc21fc
--- /dev/null
+++ b/src/test/examplesAndroidP/invokecustom/keep-rules.txt
@@ -0,0 +1,16 @@
+# Copyright (c) 2017, 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.
+
+# Keep the application entry point and the target methods of invoke-custom because these methods
+# can not be known at compile time. Get rid of everything that is not reachable from there.
+-keep public class invokecustom.InvokeCustom {
+ public static void main(...);
+}
+
+-keepclasseswithmembers class * {
+ *** targetMethodTest*(...);
+}
+
+# allow access modification to enable minification
+-allowaccessmodification
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java
new file mode 100644
index 0000000..dde8a9c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2017, 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.JAR_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.InternalCompilerError;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.OffOrAuto;
+import com.android.tools.r8.utils.OutputMode;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.function.UnaryOperator;
+import org.hamcrest.core.CombinableMatcher;
+import org.hamcrest.core.IsInstanceOf;
+import org.hamcrest.core.StringContains;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.internal.matchers.ThrowableMessageMatcher;
+
+public class D8RunExamplesAndroidPTest extends RunExamplesAndroidPTest<D8Command.Builder> {
+
+ class D8TestRunner extends TestRunner<D8TestRunner> {
+
+ D8TestRunner(String testName, String packageName, String mainClass) {
+ super(testName, packageName, mainClass);
+ }
+
+ @Override
+ D8TestRunner withMinApiLevel(int minApiLevel) {
+ return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel));
+ }
+
+ D8TestRunner withClasspath(Path... classpath) {
+ return withBuilderTransformation(b -> {
+ try {
+ return b.addClasspathFiles(classpath);
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ });
+ }
+
+
+ @Override
+ void build(Path inputFile, Path out) throws Throwable {
+ D8Command.Builder builder = D8Command.builder();
+ for (UnaryOperator<D8Command.Builder> transformation : builderTransformations) {
+ builder = transformation.apply(builder);
+ }
+ // TODO(mikaelpeltier) Add new android.jar build from aosp and use it
+ builder.addLibraryFiles(Paths.get(ToolHelper.getAndroidJar(AndroidApiLevel.O.getLevel())));
+ D8Command command = builder.addProgramFiles(inputFile).setOutputPath(out).build();
+ try {
+ ToolHelper.runD8(command, this::combinedOptionConsumer);
+ } catch (Unimplemented | CompilationError | InternalCompilerError re) {
+ throw re;
+ } catch (RuntimeException re) {
+ throw re.getCause() == null ? re : re.getCause();
+ }
+ }
+
+ D8TestRunner withIntermediate(boolean intermediate) {
+ return withBuilderTransformation(builder -> builder.setIntermediate(intermediate));
+ }
+
+ @Override
+ D8TestRunner self() {
+ return this;
+ }
+ }
+
+ @Override
+ D8TestRunner test(String testName, String packageName, String mainClass) {
+ return new D8TestRunner(testName, packageName, mainClass);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 1468dc1..1444b2b 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -11,9 +11,7 @@
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Kind;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -48,7 +46,6 @@
import java.util.concurrent.ExecutionException;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
-import org.junit.AssumptionViolatedException;
import org.junit.ComparisonFailure;
import org.junit.Rule;
import org.junit.rules.ExpectedException;
@@ -818,7 +815,10 @@
.put("600-verifier-fails", TestCondition.match(TestCondition.R8_COMPILER))
// Contains a method that falls off the end without a return.
.put("606-erroneous-class", TestCondition.match(
- TestCondition.tools(DexTool.DX, DexTool.JACK),
+ TestCondition.tools(DexTool.JACK),
+ TestCondition.R8_NOT_AFTER_D8_COMPILER))
+ .put("606-erroneous-class", TestCondition.match(
+ TestCondition.tools(DexTool.DX),
TestCondition.R8_NOT_AFTER_D8_COMPILER,
LEGACY_RUNTIME))
.build();
@@ -931,10 +931,6 @@
);
private static List<String> failuresToTriage = ImmutableList.of(
- // Contains a method that falls off the end without a return, the test should not be excluded
- // for all configurations but for all using a jar file as input
- "606-erroneous-class",
-
// const-method-handle and const-method-type
"979-const-method-handle",
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
new file mode 100644
index 0000000..0f7386e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2017, 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.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.function.UnaryOperator;
+import org.junit.Test;
+
+public class R8RunExamplesAndroidPTest extends RunExamplesAndroidPTest<R8Command.Builder> {
+
+ private static Map<DexVm.Version, List<String>> alsoFailsOn =
+ ImmutableMap.of(
+ DexVm.Version.V4_4_4, ImmutableList.of(
+ "invokecustom-with-shrinking"
+ ),
+ DexVm.Version.V5_1_1, ImmutableList.of(
+ "invokecustom-with-shrinking"
+ ),
+ DexVm.Version.V6_0_1, ImmutableList.of(
+ "invokecustom-with-shrinking"
+ ),
+ DexVm.Version.V7_0_0, ImmutableList.of(
+ "invokecustom-with-shrinking"
+ ),
+ DexVm.Version.DEFAULT, ImmutableList.of(
+ )
+ );
+
+ @Test
+ public void invokeCustomWithShrinking() throws Throwable {
+ test("invokecustom-with-shrinking", "invokecustom", "InvokeCustom")
+ .withMinApiLevel(AndroidApiLevel.P.getLevel())
+ .withBuilderTransformation(builder ->
+ builder.addProguardConfigurationFiles(
+ Paths.get(ToolHelper.EXAMPLES_ANDROID_P_DIR, "invokecustom/keep-rules.txt")))
+ .run();
+ }
+
+ class R8TestRunner extends TestRunner<R8TestRunner> {
+
+ R8TestRunner(String testName, String packageName, String mainClass) {
+ super(testName, packageName, mainClass);
+ }
+
+ @Override
+ R8TestRunner withMinApiLevel(int minApiLevel) {
+ return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel));
+ }
+
+ @Override
+ void build(Path inputFile, Path out) throws Throwable {
+ try {
+ R8Command.Builder builder = R8Command.builder();
+ for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
+ builder = transformation.apply(builder);
+ }
+ // TODO(mikaelpeltier) Add new android.jar build from aosp and use it
+ builder.addLibraryFiles(Paths.get(ToolHelper.getAndroidJar(AndroidApiLevel.O.getLevel())));
+ R8Command command = builder.addProgramFiles(inputFile).setOutputPath(out).build();
+ ToolHelper.runR8(command, this::combinedOptionConsumer);
+ } catch (ExecutionException e) {
+ throw e.getCause();
+ }
+ }
+
+ @Override
+ R8TestRunner self() {
+ return this;
+ }
+ }
+
+ @Override
+ R8TestRunner test(String testName, String packageName, String mainClass) {
+ return new R8TestRunner(testName, packageName, mainClass);
+ }
+
+ @Override
+ boolean expectedToFail(String name) {
+ return super.expectedToFail(name) || failsOn(alsoFailsOn, name);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
new file mode 100644
index 0000000..06916a8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
@@ -0,0 +1,273 @@
+// Copyright (c) 2017, 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.JAR_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
+import com.android.tools.r8.utils.DexInspector.FoundMethodSubject;
+import com.android.tools.r8.utils.DexInspector.InstructionSubject;
+import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OffOrAuto;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+public abstract class RunExamplesAndroidPTest
+ <B extends BaseCommand.Builder<? extends BaseCommand, B>> {
+ static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_ANDROID_P_BUILD_DIR;
+
+ abstract class TestRunner<C extends TestRunner<C>> {
+ final String testName;
+ final String packageName;
+ final String mainClass;
+
+ Integer androidJarVersion = null;
+
+ final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>();
+ final List<Consumer<DexInspector>> dexInspectorChecks = new ArrayList<>();
+ final List<UnaryOperator<B>> builderTransformations = new ArrayList<>();
+
+ TestRunner(String testName, String packageName, String mainClass) {
+ this.testName = testName;
+ this.packageName = packageName;
+ this.mainClass = mainClass;
+ }
+
+ abstract C self();
+
+ C withDexCheck(Consumer<DexInspector> check) {
+ dexInspectorChecks.add(check);
+ return self();
+ }
+
+ C withClassCheck(Consumer<FoundClassSubject> check) {
+ return withDexCheck(inspector -> inspector.forAllClasses(check));
+ }
+
+ C withMethodCheck(Consumer<FoundMethodSubject> check) {
+ return withClassCheck(clazz -> clazz.forAllMethods(check));
+ }
+
+ <T extends InstructionSubject> C withInstructionCheck(
+ Predicate<InstructionSubject> filter, Consumer<T> check) {
+ return withMethodCheck(method -> {
+ if (method.isAbstract()) {
+ return;
+ }
+ Iterator<T> iterator = method.iterateInstructions(filter);
+ while (iterator.hasNext()) {
+ check.accept(iterator.next());
+ }
+ });
+ }
+
+ C withOptionConsumer(Consumer<InternalOptions> consumer) {
+ optionConsumers.add(consumer);
+ return self();
+ }
+
+ C withMainDexClass(String... classes) {
+ return withBuilderTransformation(builder -> builder.addMainDexClasses(classes));
+ }
+
+ C withInterfaceMethodDesugaring(OffOrAuto behavior) {
+ return withOptionConsumer(o -> o.interfaceMethodDesugaring = behavior);
+ }
+
+ C withTryWithResourcesDesugaring(OffOrAuto behavior) {
+ return withOptionConsumer(o -> o.tryWithResourcesDesugaring = behavior);
+ }
+
+ C withBuilderTransformation(UnaryOperator<B> builderTransformation) {
+ builderTransformations.add(builderTransformation);
+ return self();
+ }
+
+ void combinedOptionConsumer(InternalOptions options) {
+ for (Consumer<InternalOptions> consumer : optionConsumers) {
+ consumer.accept(options);
+ }
+ }
+
+ Path build() throws Throwable {
+ Path inputFile = getInputJar();
+ Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
+
+ build(inputFile, out);
+ return out;
+ }
+
+ Path getInputJar() {
+ return Paths.get(EXAMPLE_DIR, packageName + JAR_EXTENSION);
+ }
+
+ void run() throws Throwable {
+ if (minSdkErrorExpected(testName)) {
+ thrown.expect(ApiLevelException.class);
+ }
+
+ String qualifiedMainClass = packageName + "." + mainClass;
+ Path inputFile = getInputJar();
+ Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
+
+ build(inputFile, out);
+
+ if (!ToolHelper.artSupported()) {
+ return;
+ }
+
+ if (!dexInspectorChecks.isEmpty()) {
+ DexInspector inspector = new DexInspector(out);
+ for (Consumer<DexInspector> check : dexInspectorChecks) {
+ check.accept(inspector);
+ }
+ }
+
+ execute(testName, qualifiedMainClass, new Path[]{inputFile}, new Path[]{out});
+ }
+
+ abstract C withMinApiLevel(int minApiLevel);
+
+ C withAndroidJar(int androidJarVersion) {
+ assert this.androidJarVersion == null;
+ this.androidJarVersion = androidJarVersion;
+ return self();
+ }
+
+ abstract void build(Path inputFile, Path out) throws Throwable;
+ }
+
+ private static List<String> minSdkErrorExpected =
+ ImmutableList.of(
+ "invokepolymorphic-error-due-to-min-sdk", "invokecustom-error-due-to-min-sdk");
+
+ private static Map<DexVm.Version, List<String>> failsOn =
+ ImmutableMap.of(
+ DexVm.Version.V4_4_4, ImmutableList.of(
+ // Dex version not supported
+ "invokecustom"
+ ),
+ DexVm.Version.V5_1_1, ImmutableList.of(
+ // Dex version not supported
+ "invokecustom"
+ ),
+ DexVm.Version.V6_0_1, ImmutableList.of(
+ // Dex version not supported
+ "invokecustom"
+ ),
+ DexVm.Version.V7_0_0, ImmutableList.of(
+ // Dex version not supported
+ "invokecustom"
+ ),
+ DexVm.Version.DEFAULT, ImmutableList.of()
+ );
+
+ @Rule
+ public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ boolean failsOn(Map<DexVm.Version, List<String>> failsOn, String name) {
+ DexVm.Version vmVersion = ToolHelper.getDexVm().getVersion();
+ return failsOn.containsKey(vmVersion)
+ && failsOn.get(vmVersion).contains(name);
+ }
+
+ boolean expectedToFail(String name) {
+ return failsOn(failsOn, name);
+ }
+
+ boolean minSdkErrorExpected(String testName) {
+ return minSdkErrorExpected.contains(testName);
+ }
+
+ @Test
+ public void invokeCustom() throws Throwable {
+ test("invokecustom", "invokecustom", "InvokeCustom")
+ .withMinApiLevel(AndroidApiLevel.P.getLevel())
+ .run();
+ }
+
+ @Test
+ public void invokeCustomErrorDueToMinSdk() throws Throwable {
+ test("invokecustom-error-due-to-min-sdk", "invokecustom", "InvokeCustom")
+ .withMinApiLevel(AndroidApiLevel.O.getLevel())
+ .run();
+ }
+
+ abstract RunExamplesAndroidPTest<B>.TestRunner<?> test(String testName, String packageName,
+ String mainClass);
+
+ void execute(
+ String testName,
+ String qualifiedMainClass, Path[] jars, Path[] dexes)
+ throws IOException {
+
+ boolean expectedToFail = expectedToFail(testName);
+ if (expectedToFail) {
+ thrown.expect(Throwable.class);
+ }
+ String output = ToolHelper.runArtNoVerificationErrors(
+ Arrays.stream(dexes).map(path -> path.toString()).collect(Collectors.toList()),
+ qualifiedMainClass,
+ null);
+ if (!expectedToFail) {
+ ToolHelper.ProcessResult javaResult =
+ ToolHelper.runJava(
+ Arrays.stream(jars).map(path -> path.toString()).collect(Collectors.toList()),
+ qualifiedMainClass);
+ assertEquals("JVM run failed", javaResult.exitCode, 0);
+ assertTrue(
+ "JVM output does not match art output.\n\tjvm: "
+ + javaResult.stdout
+ + "\n\tart: "
+ + output.replace("\r", ""),
+ output.equals(javaResult.stdout.replace("\r", "")));
+ }
+ }
+
+ protected DexInspector getMainDexInspector(Path zip)
+ throws ZipException, IOException, ExecutionException {
+ try (ZipFile zipFile = new ZipFile(zip.toFile())) {
+ try (InputStream in =
+ zipFile.getInputStream(zipFile.getEntry(FileUtils.DEFAULT_DEX_FILENAME))) {
+ return new DexInspector(AndroidApp.fromDexProgramData(ByteStreams.toByteArray(in)));
+ }
+ }
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index e46ff48..984d112 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
import java.io.File;
import java.io.FileInputStream;
@@ -92,6 +93,20 @@
}
/**
+ * Get the class name generated by javac.
+ */
+ protected String getJavacGeneratedClassName(Class clazz) {
+ List<String> parts = Lists.newArrayList(clazz.getCanonicalName().split("\\."));
+ Class enclosing = clazz;
+ while (enclosing.getEnclosingClass() != null) {
+ parts.set(parts.size() - 2, parts.get(parts.size() - 2) + "$" + parts.get(parts.size() - 1));
+ parts.remove(parts.size() - 1);
+ enclosing = clazz.getEnclosingClass();
+ }
+ return String.join(".", parts);
+ }
+
+ /**
* Compile an application with D8.
*/
protected AndroidApp compileWithD8(AndroidApp app)
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 9947e97..38a8d83 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -59,9 +59,11 @@
public static final String BUILD_DIR = "build/";
public static final String EXAMPLES_DIR = "src/test/examples/";
public static final String EXAMPLES_ANDROID_O_DIR = "src/test/examplesAndroidO/";
+ public static final String EXAMPLES_ANDROID_P_DIR = "src/test/examplesAndroidP/";
public static final String EXAMPLES_BUILD_DIR = BUILD_DIR + "test/examples/";
public static final String EXAMPLES_ANDROID_N_BUILD_DIR = BUILD_DIR + "test/examplesAndroidN/";
public static final String EXAMPLES_ANDROID_O_BUILD_DIR = BUILD_DIR + "test/examplesAndroidO/";
+ public static final String EXAMPLES_ANDROID_P_BUILD_DIR = BUILD_DIR + "test/examplesAndroidP/";
public static final String SMALI_BUILD_DIR = BUILD_DIR + "test/smali/";
public static final String LINE_SEPARATOR = StringUtils.LINE_SEPARATOR;
@@ -586,7 +588,7 @@
public static ProguardConfiguration loadProguardConfiguration(
DexItemFactory factory, List<Path> configPaths)
- throws IOException, ProguardRuleParserException {
+ throws IOException, ProguardRuleParserException, CompilationException {
if (configPaths.isEmpty()) {
return ProguardConfiguration.defaultConfiguration(factory);
}
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index f049102..53f0793 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
+import com.android.tools.r8.CompilationException;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.ClassAndMemberPublicizer;
@@ -61,7 +62,7 @@
}
NamingLens runMinifier(List<Path> configPaths)
- throws IOException, ProguardRuleParserException, ExecutionException {
+ throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
ProguardConfiguration configuration =
ToolHelper.loadProguardConfiguration(dexItemFactory, configPaths);
InternalOptions options = new InternalOptions(configuration);
@@ -72,7 +73,7 @@
RootSet rootSet = new RootSetBuilder(program, appInfo, configuration.getRules(), options)
.run(ThreadUtils.getExecutorService(options));
- Enqueuer enqueuer = new Enqueuer(appInfo);
+ Enqueuer enqueuer = new Enqueuer(appInfo, options);
appInfo = enqueuer.traceApplication(rootSet, timing);
return new Minifier(appInfo.withLiveness(), rootSet, options).run(timing);
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 78fbc8e..fe0b6f6 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -105,7 +105,7 @@
VALID_PROGUARD_DIR + "target.flags";
@Test
- public void parse() throws IOException, ProguardRuleParserException {
+ public void parse() throws Exception {
ProguardConfigurationParser parser;
// Parse from file.
@@ -125,7 +125,7 @@
}
@Test
- public void parseMultipleNamePatterns() throws IOException, ProguardRuleParserException {
+ public void parseMultipleNamePatterns() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(MULTIPLE_NAME_PATTERNS_FILE));
List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
@@ -190,7 +190,7 @@
}
@Test
- public void parseAccessFlags() throws IOException, ProguardRuleParserException {
+ public void parseAccessFlags() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(ACCESS_FLAGS_FILE));
List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
@@ -228,7 +228,7 @@
}
@Test
- public void parseWhyAreYouKeeping() throws IOException, ProguardRuleParserException {
+ public void parseWhyAreYouKeeping() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(WHY_ARE_YOU_KEEPING_FILE));
List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
@@ -241,7 +241,7 @@
}
@Test
- public void parseAssumeNoSideEffects() throws IOException, ProguardRuleParserException {
+ public void parseAssumeNoSideEffects() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(ASSUME_NO_SIDE_EFFECTS));
List<ProguardConfigurationRule> assumeNoSideEffects = parser.getConfig().getRules();
@@ -252,8 +252,7 @@
}
@Test
- public void parseAssumeNoSideEffectsWithReturnValue()
- throws IOException, ProguardRuleParserException {
+ public void parseAssumeNoSideEffectsWithReturnValue() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(ASSUME_NO_SIDE_EFFECTS_WITH_RETURN_VALUE));
List<ProguardConfigurationRule> assumeNoSideEffects = parser.getConfig().getRules();
@@ -292,8 +291,7 @@
}
@Test
- public void parseAssumeValuesWithReturnValue()
- throws IOException, ProguardRuleParserException {
+ public void parseAssumeValuesWithReturnValue() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(ASSUME_VALUES_WITH_RETURN_VALUE));
List<ProguardConfigurationRule> assumeValues = parser.getConfig().getRules();
@@ -376,7 +374,7 @@
}
@Test
- public void parseDontobfuscate() throws IOException, ProguardRuleParserException {
+ public void parseDontobfuscate() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(DONT_OBFUSCATE));
ProguardConfiguration config = parser.getConfig();
@@ -384,7 +382,7 @@
}
@Test
- public void parseRepackageClassesEmpty() throws IOException, ProguardRuleParserException {
+ public void parseRepackageClassesEmpty() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(PACKAGE_OBFUSCATION_1));
ProguardConfiguration config = parser.getConfig();
@@ -394,7 +392,7 @@
}
@Test
- public void parseRepackageClassesNonEmpty() throws IOException, ProguardRuleParserException {
+ public void parseRepackageClassesNonEmpty() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(PACKAGE_OBFUSCATION_2));
ProguardConfiguration config = parser.getConfig();
@@ -404,7 +402,7 @@
}
@Test
- public void parseFlattenPackageHierarchyEmpty() throws IOException, ProguardRuleParserException {
+ public void parseFlattenPackageHierarchyEmpty() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(PACKAGE_OBFUSCATION_3));
ProguardConfiguration config = parser.getConfig();
@@ -414,7 +412,7 @@
}
@Test
- public void parseFlattenPackageHierarchyNonEmpty() throws IOException, ProguardRuleParserException {
+ public void parseFlattenPackageHierarchyNonEmpty() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(PACKAGE_OBFUSCATION_4));
ProguardConfiguration config = parser.getConfig();
@@ -425,7 +423,7 @@
@Test
public void flattenPackageHierarchyCannotOverrideRepackageClasses()
- throws IOException, ProguardRuleParserException {
+ throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(PACKAGE_OBFUSCATION_5));
ProguardConfiguration config = parser.getConfig();
@@ -436,7 +434,7 @@
@Test
public void repackageClassesOverridesFlattenPackageHierarchy()
- throws IOException, ProguardRuleParserException {
+ throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(PACKAGE_OBFUSCATION_6));
ProguardConfiguration config = parser.getConfig();
@@ -446,7 +444,7 @@
}
@Test
- public void parseApplyMapping() throws IOException, ProguardRuleParserException {
+ public void parseApplyMapping() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(APPLY_MAPPING));
ProguardConfiguration config = parser.getConfig();
@@ -454,7 +452,7 @@
}
@Test
- public void parseApplyMappingWithoutFile() throws IOException, ProguardRuleParserException {
+ public void parseApplyMappingWithoutFile() throws Exception {
try {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(APPLY_MAPPING_WITHOUT_FILE));
@@ -465,7 +463,7 @@
}
@Test
- public void parseIncluding() throws IOException, ProguardRuleParserException {
+ public void parseIncluding() throws Exception {
new ProguardConfigurationParser(new DexItemFactory()).parse(Paths.get(INCLUDING));
}
@@ -495,7 +493,7 @@
}
@Test
- public void parseLibraryJars() throws IOException, ProguardRuleParserException {
+ public void parseLibraryJars() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
if (!ToolHelper.isLinux() && !ToolHelper.isMac()) {
parser.parse(Paths.get(LIBRARY_JARS_WIN));
@@ -518,7 +516,7 @@
}
@Test
- public void parseSeeds() throws IOException, ProguardRuleParserException {
+ public void parseSeeds() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(SEEDS));
ProguardConfiguration config = parser.getConfig();
@@ -527,7 +525,7 @@
}
@Test
- public void parseSeeds2() throws IOException, ProguardRuleParserException {
+ public void parseSeeds2() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(SEEDS_2));
ProguardConfiguration config = parser.getConfig();
@@ -536,7 +534,7 @@
}
@Test
- public void parseVerbose() throws IOException, ProguardRuleParserException {
+ public void parseVerbose() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(VERBOSE));
ProguardConfiguration config = parser.getConfig();
@@ -544,13 +542,13 @@
}
@Test
- public void parseKeepdirectories() throws IOException, ProguardRuleParserException {
+ public void parseKeepdirectories() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(KEEPDIRECTORIES));
}
@Test
- public void parseDontshrink() throws IOException, ProguardRuleParserException {
+ public void parseDontshrink() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(DONT_SHRINK));
ProguardConfiguration config = parser.getConfig();
@@ -558,49 +556,46 @@
}
@Test
- public void parseDontSkipNonPublicLibraryClasses()
- throws IOException, ProguardRuleParserException {
+ public void parseDontSkipNonPublicLibraryClasses() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES));
}
@Test
- public void parseDontskipnonpubliclibraryclassmembers()
- throws IOException, ProguardRuleParserException {
+ public void parseDontskipnonpubliclibraryclassmembers() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS));
}
@Test
- public void parseOverloadAggressively()
- throws IOException, ProguardRuleParserException {
+ public void parseOverloadAggressively() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(OVERLOAD_AGGRESIVELY));
}
@Test
- public void parseDontOptimize() throws IOException, ProguardRuleParserException {
+ public void parseDontOptimize() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(DONT_OPTIMIZE));
ProguardConfiguration config = parser.getConfig();
}
@Test
- public void parseDontOptimizeOverridesPasses() throws IOException, ProguardRuleParserException {
+ public void parseDontOptimizeOverridesPasses() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(DONT_OPTIMIZE_OVERRIDES_PASSES));
ProguardConfiguration config = parser.getConfig();
}
@Test
- public void parseOptimizationPasses() throws IOException, ProguardRuleParserException {
+ public void parseOptimizationPasses() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(OPTIMIZATION_PASSES));
ProguardConfiguration config = parser.getConfig();
}
@Test
- public void parseOptimizationPassesError() throws IOException, ProguardRuleParserException {
+ public void parseOptimizationPassesError() throws Exception {
try {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(OPTIMIZATION_PASSES_WITHOUT_N));
@@ -622,13 +617,13 @@
}
@Test
- public void parseAndskipSingleArgument() throws IOException, ProguardRuleParserException {
+ public void parseAndskipSingleArgument() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(PARSE_AND_SKIP_SINGLE_ARGUMENT));
}
@Test
- public void parsePrintUsage() throws IOException, ProguardRuleParserException {
+ public void parsePrintUsage() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(PRINT_USAGE));
ProguardConfiguration config = parser.getConfig();
@@ -637,7 +632,7 @@
}
@Test
- public void parsePrintUsageToFile() throws IOException, ProguardRuleParserException {
+ public void parsePrintUsageToFile() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(PRINT_USAGE_TO_FILE));
ProguardConfiguration config = parser.getConfig();
@@ -646,14 +641,13 @@
}
@Test
- public void parseTarget()
- throws IOException, ProguardRuleParserException {
+ public void parseTarget() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(TARGET));
}
@Test
- public void parseInvalidKeepClassOption() throws IOException, ProguardRuleParserException {
+ public void parseInvalidKeepClassOption() throws Exception {
try {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
Path proguardConfig = writeTextToTempFile(
@@ -680,7 +674,7 @@
}
@Test
- public void testRenameSourceFileAttribute() throws IOException, ProguardRuleParserException {
+ public void testRenameSourceFileAttribute() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
String config1 = "-renamesourcefileattribute PG\n";
String config2 = "-keepattributes SourceFile,SourceDir\n";
@@ -692,7 +686,7 @@
}
@Test
- public void testRenameSourceFileAttributeEmpty() throws IOException, ProguardRuleParserException {
+ public void testRenameSourceFileAttributeEmpty() throws Exception {
ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
String config1 = "-renamesourcefileattribute\n";
String config2 = "-keepattributes SourceFile\n";
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
new file mode 100644
index 0000000..4d59f63
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2017, 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.forceproguardcompatibility;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class ForceProguardCompatibilityTest extends TestBase {
+ private void test(Class mainClass, Class mentionedClass, boolean arrayClass,
+ boolean forceProguardCompatibility)
+ throws Exception {
+ String proguardConfig = keepMainProguardConfiguration(mainClass, true, false);
+ DexInspector inspector = new DexInspector(
+ compileWithR8(
+ ImmutableList.of(mainClass, mentionedClass),
+ proguardConfig,
+ options -> options.forceProguardCompatibility = forceProguardCompatibility));
+ assertTrue(inspector.clazz(mainClass.getCanonicalName()).isPresent());
+ ClassSubject clazz = inspector.clazz(getJavacGeneratedClassName(mentionedClass));
+ assertTrue(clazz.isPresent());
+ if (arrayClass) {
+ MethodSubject defaultInitializer = clazz.method(MethodSignature.initializer(new String[]{}));
+ assertFalse(defaultInitializer.isPresent());
+ } else {
+ MethodSubject defaultInitializer = clazz.method(MethodSignature.initializer(new String[]{}));
+ assertEquals(forceProguardCompatibility, defaultInitializer.isPresent());
+ }
+ }
+
+ @Test
+ public void testKeepDefaultInitializer() throws Exception {
+ test(TestMain.class, TestMain.MentionedClass.class, false, true);
+ test(TestMain.class, TestMain.MentionedClass.class, false, false);
+ }
+
+ @Test
+ public void testKeepDefaultInitializerArrayType() throws Exception {
+ test(TestMainArrayType.class, TestMainArrayType.MentionedClass.class, true, true);
+ test(TestMainArrayType.class, TestMainArrayType.MentionedClass.class, true, false);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMain.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMain.java
new file mode 100644
index 0000000..94a9d0a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMain.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2017, 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.forceproguardcompatibility;
+
+public class TestMain {
+
+ public static class MentionedClass {
+ public MentionedClass() {
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println(MentionedClass.class.getCanonicalName());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMainArrayType.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMainArrayType.java
new file mode 100644
index 0000000..8b1acaf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMainArrayType.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2017, 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.forceproguardcompatibility;
+
+public class TestMainArrayType {
+
+ public static class MentionedClass {
+ public MentionedClass() {
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println(MentionedClass[].class.getCanonicalName());
+ }
+}