Merge "CF backend: Implement InvokeCustom (JVM InvokeDynamic)"
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 187333c..39d916a 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -9,6 +9,8 @@
import com.android.tools.r8.cf.code.CfBinop;
import com.android.tools.r8.cf.code.CfCheckCast;
import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstMethodHandle;
+import com.android.tools.r8.cf.code.CfConstMethodType;
import com.android.tools.r8.cf.code.CfConstNull;
import com.android.tools.r8.cf.code.CfConstNumber;
import com.android.tools.r8.cf.code.CfConstString;
@@ -450,6 +452,18 @@
}
}
+ public void print(CfConstMethodHandle handle) {
+ indent();
+ builder.append("ldc ");
+ builder.append(handle.getHandle().toString());
+ }
+
+ public void print(CfConstMethodType type) {
+ indent();
+ builder.append("ldc ");
+ builder.append(type.getType().toString());
+ }
+
private String getLabel(CfLabel label) {
return labels != null ? labels.get(label) : "L?";
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
new file mode 100644
index 0000000..b63b4d5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -0,0 +1,31 @@
+// 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.cf.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.DexMethodHandle;
+import org.objectweb.asm.MethodVisitor;
+
+public class CfConstMethodHandle extends CfInstruction {
+
+ private DexMethodHandle handle;
+
+ public CfConstMethodHandle(DexMethodHandle handle) {
+ this.handle = handle;
+ }
+
+ public DexMethodHandle getHandle() {
+ return handle;
+ }
+
+ @Override
+ public void write(MethodVisitor visitor) {
+ visitor.visitLdcInsn(handle.toAsmHandle());
+ }
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
new file mode 100644
index 0000000..8ec1545
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -0,0 +1,32 @@
+// 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.cf.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.DexProto;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
+
+public class CfConstMethodType extends CfInstruction {
+
+ private DexProto type;
+
+ public CfConstMethodType(DexProto type) {
+ this.type = type;
+ }
+
+ public DexProto getType() {
+ return type;
+ }
+
+ @Override
+ public void write(MethodVisitor visitor) {
+ visitor.visitLdcInsn(Type.getType(type.toDescriptorString()));
+ }
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
+}
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
index 5feecec..7ad397a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -3,10 +3,15 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
+import com.android.tools.r8.cf.code.CfConstMethodHandle;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import java.util.function.Function;
@@ -35,6 +40,11 @@
}
@Override
+ public void buildCf(CfBuilder builder) {
+ builder.add(new CfConstMethodHandle(methodHandle));
+ }
+
+ @Override
public boolean identicalNonValueNonPositionParts(Instruction other) {
return other.asConstMethodHandle().methodHandle == methodHandle;
}
@@ -85,4 +95,14 @@
AppInfo appInfo, Function<Value, TypeLatticeElement> getLatticeElement) {
return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodHandleType, false);
}
+
+ @Override
+ public DexType computeVerificationType(TypeVerificationHelper helper) {
+ return helper.getFactory().methodHandleType;
+ }
+
+ @Override
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ helper.storeOutValue(this, it);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
index b687743..01bfeb6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
@@ -3,10 +3,15 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
+import com.android.tools.r8.cf.code.CfConstMethodType;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import java.util.function.Function;
@@ -35,6 +40,11 @@
}
@Override
+ public void buildCf(CfBuilder builder) {
+ builder.add(new CfConstMethodType(methodType));
+ }
+
+ @Override
public boolean identicalNonValueNonPositionParts(Instruction other) {
return other.asConstMethodType().methodType == methodType;
}
@@ -85,4 +95,14 @@
AppInfo appInfo, Function<Value, TypeLatticeElement> getLatticeElement) {
return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodTypeType, false);
}
+
+ @Override
+ public DexType computeVerificationType(TypeVerificationHelper helper) {
+ return helper.getFactory().methodTypeType;
+ }
+
+ @Override
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ helper.storeOutValue(this, it);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 17ebfb5..1a567ef 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -170,11 +170,11 @@
@Override
public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
helper.loadInValues(this, it);
- if (method.proto.returnType.isVoidType()) {
+ if (getReturnType().isVoidType()) {
return;
}
if (outValue == null) {
- helper.popOutType(method.proto.returnType, this, it);
+ helper.popOutType(getReturnType(), this, it);
} else {
assert outValue.isUsed();
helper.storeOutValue(this, it);
@@ -188,7 +188,7 @@
@Override
public DexType computeVerificationType(TypeVerificationHelper helper) {
- return getInvokedMethod().proto.returnType;
+ return getReturnType();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index c0e1a30..718d23c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -3,19 +3,25 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.code.InvokePolymorphicRange;
+import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.conversion.JarSourceCode;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.InliningOracle;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.Collection;
import java.util.List;
+import org.objectweb.asm.Opcodes;
public class InvokePolymorphic extends InvokeMethod {
@@ -78,6 +84,21 @@
}
@Override
+ public void buildCf(CfBuilder builder) {
+ DexMethod dexMethod = getInvokedMethod();
+ DexItemFactory factory = builder.getFactory();
+
+ if (dexMethod.holder.getInternalName().equals(JarSourceCode.INTERNAL_NAME_METHOD_HANDLE)) {
+ DexMethod method = factory.createMethod(dexMethod.holder, getProto(), dexMethod.name);
+ builder.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method));
+ } else {
+ assert dexMethod.holder.getInternalName().equals(JarSourceCode.INTERNAL_NAME_VAR_HANDLE);
+ // VarHandle is new in Java 9
+ throw new Unimplemented();
+ }
+ }
+
+ @Override
public boolean identicalNonValueNonPositionParts(Instruction other) {
if (!other.isInvokePolymorphic()) {
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index d3ef914..9e9a096 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.code.InvokeSuperRange;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -10,12 +11,14 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
+import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import org.objectweb.asm.Opcodes;
public class InvokeSuper extends InvokeMethodWithReceiver {
@@ -72,6 +75,11 @@
}
@Override
+ public void buildCf(CfBuilder builder) {
+ builder.add(new CfInvoke(Opcodes.INVOKESPECIAL, getInvokedMethod()));
+ }
+
+ @Override
public boolean identicalNonValueNonPositionParts(Instruction other) {
if (!other.isInvokeSuper()) {
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 83995bd..60f9c79 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -102,6 +102,10 @@
this.factory = factory;
}
+ public DexItemFactory getFactory() {
+ return factory;
+ }
+
public Code build(CodeRewriter rewriter, InternalOptions options, AppInfoWithSubtyping appInfo) {
try {
types = new TypeVerificationHelper(code, factory, appInfo).computeVerificationTypes();
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 894ba4f..26a94d2 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
@@ -141,8 +141,8 @@
"([Ljava/lang/Object;)Z";
// Various internal names.
- static final String INTERNAL_NAME_METHOD_HANDLE = "java/lang/invoke/MethodHandle";
- static final String INTERNAL_NAME_VAR_HANDLE = "java/lang/invoke/VarHandle";
+ public static final String INTERNAL_NAME_METHOD_HANDLE = "java/lang/invoke/MethodHandle";
+ public static final String INTERNAL_NAME_VAR_HANDLE = "java/lang/invoke/VarHandle";
// Language types.
static final Type CLASS_TYPE = Type.getObjectType("java/lang/Class");
@@ -2601,6 +2601,8 @@
default:
throw new Unreachable();
}
+ } else {
+ throw new Unreachable();
}
callSiteProto = application.getProto(insn.desc);
}
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 22a4812..5cb4ce8 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -44,58 +44,54 @@
private final Reporter reporter;
- private static final List<String> IGNORED_SINGLE_ARG_OPTIONS = ImmutableList
- .of("protomapping",
- "target"
- );
- private static final List<String> IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS = ImmutableList
- .of("keepdirectories",
- "runtype",
- "laststageoutput"
- );
- private static final List<String> IGNORED_FLAG_OPTIONS = ImmutableList
- .of("forceprocessing",
- "dontusemixedcaseclassnames",
- "dontpreverify",
- "experimentalshrinkunusedprotofields",
- "filterlibraryjarswithorginalprogramjars",
- "dontskipnonpubliclibraryclasses",
- "dontskipnonpubliclibraryclassmembers",
- "invokebasemethod",
- "android"
- );
- private static final List<String> IGNORED_CLASS_DESCRIPTOR_OPTIONS = ImmutableList
- .of("isclassnamestring",
- "whyarenotsimple"
- );
+ private static final List<String> IGNORED_SINGLE_ARG_OPTIONS = ImmutableList.of(
+ "dontnote",
+ "protomapping",
+ "target");
- private static final List<String> WARNED_SINGLE_ARG_OPTIONS = ImmutableList
- .of("dontnote",
- "printconfiguration",
- // TODO -outjars (http://b/37137994) and -adaptresourcefilecontents (http://b/37139570)
- // should be reported as errors, not just as warnings!
- "outjars",
- "adaptresourcefilecontents"
- );
- private static final List<String> WARNED_FLAG_OPTIONS = ImmutableList
- .of(
- // TODO(b/73707846): add support -addconfigurationdebugging
- "addconfigurationdebugging"
- );
- private static final List<String> WARNED_CLASS_DESCRIPTOR_OPTIONS = ImmutableList
- .of(
- // TODO(b/73708157): add support -assumenoexternalsideeffects <class_spec>
- "assumenoexternalsideeffects",
- // TODO(b/73707404): add support -assumenoescapingparameters <class_spec>
- "assumenoescapingparameters",
- // TODO(b/73708085): add support -assumenoexternalreturnvalues <class_spec>
- "assumenoexternalreturnvalues"
- );
+ private static final List<String> IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS = ImmutableList.of(
+ "keepdirectories",
+ "runtype",
+ "laststageoutput");
+
+ private static final List<String> IGNORED_FLAG_OPTIONS = ImmutableList.of(
+ "forceprocessing",
+ "dontusemixedcaseclassnames",
+ "dontpreverify",
+ "experimentalshrinkunusedprotofields",
+ "filterlibraryjarswithorginalprogramjars",
+ "dontskipnonpubliclibraryclasses",
+ "dontskipnonpubliclibraryclassmembers",
+ "invokebasemethod",
+ "android");
+
+ private static final List<String> IGNORED_CLASS_DESCRIPTOR_OPTIONS = ImmutableList.of(
+ "isclassnamestring",
+ "whyarenotsimple");
+
+ private static final List<String> WARNED_SINGLE_ARG_OPTIONS = ImmutableList.of(
+ "printconfiguration",
+ // TODO -outjars (http://b/37137994) and -adaptresourcefilecontents (http://b/37139570)
+ // should be reported as errors, not just as warnings!
+ "outjars",
+ "adaptresourcefilecontents");
+
+ private static final List<String> WARNED_FLAG_OPTIONS = ImmutableList.of(
+ // TODO(b/73707846): add support -addconfigurationdebugging
+ "addconfigurationdebugging");
+
+ private static final List<String> WARNED_CLASS_DESCRIPTOR_OPTIONS = ImmutableList.of(
+ // TODO(b/73708157): add support -assumenoexternalsideeffects <class_spec>
+ "assumenoexternalsideeffects",
+ // TODO(b/73707404): add support -assumenoescapingparameters <class_spec>
+ "assumenoescapingparameters",
+ // TODO(b/73708085): add support -assumenoexternalreturnvalues <class_spec>
+ "assumenoexternalreturnvalues");
// Those options are unsupported and are treated as compilation errors.
// Just ignoring them would produce outputs incompatible with user expectations.
- private static final List<String> UNSUPPORTED_FLAG_OPTIONS = ImmutableList
- .of("skipnonpubliclibraryclasses");
+ private static final List<String> UNSUPPORTED_FLAG_OPTIONS =
+ ImmutableList.of("skipnonpubliclibraryclasses");
public ProguardConfigurationParser(
DexItemFactory dexItemFactory, Reporter reporter) {
@@ -197,28 +193,10 @@
}
TextPosition optionStart = getPosition();
expectChar('-');
- String option;
- if (Iterables.any(IGNORED_SINGLE_ARG_OPTIONS, this::skipOptionWithSingleArg)
- || Iterables.any(
- IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS, this::skipOptionWithOptionalSingleArg)
- || Iterables.any(IGNORED_FLAG_OPTIONS, this::skipFlag)
- || Iterables.any(IGNORED_CLASS_DESCRIPTOR_OPTIONS, this::skipOptionWithClassSpec)
- || parseOptimizationOption()) {
+ if (parseIgnoredOption() ||
+ parseIgnoredOptionAndWarn(optionStart) ||
+ parseUnsupportedOptionAndErr(optionStart)) {
// Intentionally left empty.
- } else if (
- (option = Iterables.find(WARNED_SINGLE_ARG_OPTIONS,
- this::skipOptionWithSingleArg, null)) != null
- || (option = Iterables.find(WARNED_FLAG_OPTIONS,
- this::skipFlag, null)) != null
- || (option = Iterables.find(WARNED_CLASS_DESCRIPTOR_OPTIONS,
- this::skipOptionWithClassSpec, null)) != null) {
- warnIgnoringOptions(option, optionStart);
- } else if (
- (option = Iterables.find(UNSUPPORTED_FLAG_OPTIONS, this::skipFlag, null)) != null) {
- reporter.error(new StringDiagnostic(
- "Unsupported option: -" + option,
- origin,
- getPosition(optionStart)));
} else if (acceptString("renamesourcefileattribute")) {
skipWhitespace();
if (isOptionalArgumentGiven()) {
@@ -368,6 +346,41 @@
return true;
}
+ private boolean parseUnsupportedOptionAndErr(TextPosition optionStart) {
+ String option = Iterables.find(UNSUPPORTED_FLAG_OPTIONS, this::skipFlag, null);
+ if (option != null) {
+ reporter.error(new StringDiagnostic(
+ "Unsupported option: -" + option, origin, getPosition(optionStart)));
+ return true;
+ }
+ return false;
+ }
+
+ private boolean parseIgnoredOptionAndWarn(TextPosition optionStart) {
+ String option =
+ Iterables.find(WARNED_CLASS_DESCRIPTOR_OPTIONS, this::skipOptionWithClassSpec, null);
+ if (option == null) {
+ option = Iterables.find(WARNED_FLAG_OPTIONS, this::skipFlag, null);
+ if (option == null) {
+ option = Iterables.find(WARNED_SINGLE_ARG_OPTIONS, this::skipOptionWithSingleArg, null);
+ if (option == null) {
+ return false;
+ }
+ }
+ }
+ warnIgnoringOptions(option, optionStart);
+ return true;
+ }
+
+ private boolean parseIgnoredOption() {
+ return Iterables.any(IGNORED_SINGLE_ARG_OPTIONS, this::skipOptionWithSingleArg)
+ || Iterables.any(IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS,
+ this::skipOptionWithOptionalSingleArg)
+ || Iterables.any(IGNORED_FLAG_OPTIONS, this::skipFlag)
+ || Iterables.any(IGNORED_CLASS_DESCRIPTOR_OPTIONS, this::skipOptionWithClassSpec)
+ || parseOptimizationOption();
+ }
+
private void parseInclude() throws ProguardRuleParserException {
TextPosition start = getPosition();
Path included = parseFileName();
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index 48a04cd..8ce1c09 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -38,6 +38,7 @@
errorCount++;
}
}
+
public void error(String message) {
error(new StringDiagnostic(message));
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 2202332..f63f4f2 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -443,7 +443,12 @@
private static final String ANGLER_BOOT_IMAGE = ANGLER_DIR + "/system/framework/boot.art";
public static byte[] getClassAsBytes(Class clazz) throws IOException {
- return ByteStreams.toByteArray(clazz.getResourceAsStream(clazz.getSimpleName() + ".class"));
+ String s = clazz.getSimpleName() + ".class";
+ Class outer = clazz.getEnclosingClass();
+ if (outer != null) {
+ s = outer.getSimpleName() + '$' + s;
+ }
+ return ByteStreams.toByteArray(clazz.getResourceAsStream(s));
}
public static String getArtDir(DexVm version) {
@@ -883,13 +888,15 @@
return runJavaNoVerify(path, main);
}
- public static ProcessResult runJava(Path classpath, String mainClass) throws IOException {
- return runJava(ImmutableList.of(classpath), mainClass);
+ public static ProcessResult runJava(Path classpath, String... args) throws IOException {
+ return runJava(ImmutableList.of(classpath), args);
}
- public static ProcessResult runJava(List<Path> classpath, String mainClass) throws IOException {
+ public static ProcessResult runJava(List<Path> classpath, String... args) throws IOException {
String cp = classpath.stream().map(Path::toString).collect(Collectors.joining(PATH_SEPARATOR));
- ProcessBuilder builder = new ProcessBuilder(getJavaExecutable(), "-cp", cp, mainClass);
+ List<String> cmdline = new ArrayList<String>(Arrays.asList(getJavaExecutable(), "-cp", cp));
+ cmdline.addAll(Arrays.asList(args));
+ ProcessBuilder builder = new ProcessBuilder(cmdline);
return runProcess(builder);
}
@@ -958,6 +965,11 @@
return runArtRaw(Collections.singletonList(file), mainClass, null);
}
+ public static ProcessResult runArtRaw(
+ String file, String mainClass, Consumer<ArtCommandBuilder> extras) throws IOException {
+ return runArtRaw(Collections.singletonList(file), mainClass, extras);
+ }
+
public static ProcessResult runArtRaw(List<String> files, String mainClass,
Consumer<ArtCommandBuilder> extras)
throws IOException {
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java b/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java
new file mode 100644
index 0000000..c80047a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java
@@ -0,0 +1,189 @@
+// 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.cf;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+import org.objectweb.asm.*;
+
+// The method MethodHandleDump.transform() translates methods in MethodHandleTest that look like
+// MethodType.methodType(TYPES)
+// and
+// MethodHandles.lookup().findKIND(FOO.class, "METHOD", TYPE)
+// into LDC instructions.
+// This is necessary since there is no Java syntax that compiles to
+// LDC of a constant method handle or constant method type.
+//
+// The method dumpD() dumps a class equivalent to MethodHandleTest.D
+// that uses an LDC instruction instead of MethodHandles.lookup().findSpecial().
+// The LDC instruction loads an InvokeSpecial constant method handle to a C method,
+// so this LDC instruction must be in a subclass of C, and not directly on MethodHandleTest.
+public class MethodHandleDump implements Opcodes {
+
+ private static final Type viType = Type.getMethodType(Type.VOID_TYPE, Type.INT_TYPE);
+ private static final Type jiType = Type.getMethodType(Type.LONG_TYPE, Type.INT_TYPE);
+ private static final Type vicType =
+ Type.getMethodType(Type.VOID_TYPE, Type.INT_TYPE, Type.CHAR_TYPE);
+ private static final Type jicType =
+ Type.getMethodType(Type.LONG_TYPE, Type.INT_TYPE, Type.CHAR_TYPE);
+ private static final String cDesc = "com/android/tools/r8/cf/MethodHandleTest$C";
+ private static final String iDesc = "com/android/tools/r8/cf/MethodHandleTest$I";
+ private static final String viDesc = viType.getDescriptor();
+ private static final String jiDesc = jiType.getDescriptor();
+ private static final String vicDesc = vicType.getDescriptor();
+ private static final String jicDesc = jicType.getDescriptor();
+ private static final String intDesc = Type.INT_TYPE.getDescriptor();
+
+ public static byte[] transform(byte[] input) throws Exception {
+ ImmutableMap.Builder<String, Type> typesBuilder = ImmutableMap.builder();
+ ImmutableMap<String, Type> types =
+ typesBuilder
+ .put("viType", viType)
+ .put("jiType", jiType)
+ .put("vicType", vicType)
+ .put("jicType", jicType)
+ .build();
+
+ Builder<String, Handle> methodsBuilder = ImmutableMap.builder();
+ methodsBuilder
+ .put("scviMethod", new Handle(H_INVOKESTATIC, cDesc, "svi", viDesc, false))
+ .put("scjiMethod", new Handle(H_INVOKESTATIC, cDesc, "sji", jiDesc, false))
+ .put("scvicMethod", new Handle(H_INVOKESTATIC, cDesc, "svic", vicDesc, false))
+ .put("scjicMethod", new Handle(H_INVOKESTATIC, cDesc, "sjic", jicDesc, false))
+ .put("vcviMethod", new Handle(H_INVOKEVIRTUAL, cDesc, "vvi", viDesc, false))
+ .put("vcjiMethod", new Handle(H_INVOKEVIRTUAL, cDesc, "vji", jiDesc, false))
+ .put("vcvicMethod", new Handle(H_INVOKEVIRTUAL, cDesc, "vvic", vicDesc, false))
+ .put("vcjicMethod", new Handle(H_INVOKEVIRTUAL, cDesc, "vjic", jicDesc, false))
+ .put("siviMethod", new Handle(H_INVOKESTATIC, iDesc, "svi", viDesc, true))
+ .put("sijiMethod", new Handle(H_INVOKESTATIC, iDesc, "sji", jiDesc, true))
+ .put("sivicMethod", new Handle(H_INVOKESTATIC, iDesc, "svic", vicDesc, true))
+ .put("sijicMethod", new Handle(H_INVOKESTATIC, iDesc, "sjic", jicDesc, true))
+ .put("diviMethod", new Handle(H_INVOKEINTERFACE, iDesc, "dvi", viDesc, true))
+ .put("dijiMethod", new Handle(H_INVOKEINTERFACE, iDesc, "dji", jiDesc, true))
+ .put("divicMethod", new Handle(H_INVOKEINTERFACE, iDesc, "dvic", vicDesc, true))
+ .put("dijicMethod", new Handle(H_INVOKEINTERFACE, iDesc, "djic", jicDesc, true))
+ .put("vciSetField", new Handle(H_PUTFIELD, cDesc, "vi", intDesc, false))
+ .put("sciSetField", new Handle(H_PUTSTATIC, cDesc, "si", intDesc, false))
+ .put("vciGetField", new Handle(H_GETFIELD, cDesc, "vi", intDesc, false))
+ .put("sciGetField", new Handle(H_GETSTATIC, cDesc, "si", intDesc, false))
+ .put("iiSetField", new Handle(H_PUTSTATIC, iDesc, "ii", intDesc, true))
+ .put("iiGetField", new Handle(H_GETSTATIC, iDesc, "ii", intDesc, true))
+ .put("constructorMethod", new Handle(H_NEWINVOKESPECIAL, cDesc, "<init>", viDesc, false));
+ ImmutableMap<String, Handle> methods = methodsBuilder.build();
+ ClassReader cr = new ClassReader(input);
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+ cr.accept(
+ new ClassVisitor(ASM6, cw) {
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String desc, String signature, String[] exceptions) {
+ switch (desc) {
+ case "()Ljava/lang/invoke/MethodType;":
+ {
+ Type type = types.get(name);
+ assert type != null : name;
+ assert access == ACC_PUBLIC + ACC_STATIC;
+ assert signature == null;
+ assert exceptions == null;
+ MethodVisitor mv = cw.visitMethod(access, name, desc, null, null);
+ mv.visitCode();
+ mv.visitLdcInsn(type);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(-1, -1);
+ mv.visitEnd();
+ return null;
+ }
+ case "()Ljava/lang/invoke/MethodHandle;":
+ {
+ Handle method = methods.get(name);
+ assert access == ACC_PUBLIC + ACC_STATIC;
+ assert method != null : name;
+ assert signature == null;
+ assert exceptions == null;
+ MethodVisitor mv = cw.visitMethod(access, name, desc, null, null);
+ mv.visitCode();
+ mv.visitLdcInsn(method);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(-1, -1);
+ mv.visitEnd();
+ return null;
+ }
+ default:
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+ }
+ },
+ 0);
+ return cw.toByteArray();
+ }
+
+ public static byte[] dumpD() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ MethodVisitor mv;
+
+ cw.visit(
+ V1_8,
+ ACC_PUBLIC + ACC_SUPER,
+ "com/android/tools/r8/cf/MethodHandleTest$D",
+ null,
+ "com/android/tools/r8/cf/MethodHandleTest$C",
+ null);
+
+ cw.visitInnerClass(
+ "com/android/tools/r8/cf/MethodHandleTest$D",
+ "com/android/tools/r8/cf/MethodHandleTest",
+ "D",
+ ACC_PUBLIC + ACC_STATIC);
+
+ cw.visitInnerClass(
+ "com/android/tools/r8/cf/MethodHandleTest$C",
+ "com/android/tools/r8/cf/MethodHandleTest",
+ "C",
+ ACC_PUBLIC + ACC_STATIC);
+
+ cw.visitInnerClass(
+ "java/lang/invoke/MethodHandles$Lookup",
+ "java/lang/invoke/MethodHandles",
+ "Lookup",
+ ACC_PUBLIC + ACC_FINAL + ACC_STATIC);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(
+ INVOKESPECIAL, "com/android/tools/r8/cf/MethodHandleTest$C", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv =
+ cw.visitMethod(
+ ACC_PUBLIC + ACC_STATIC,
+ "vcviSpecialMethod",
+ "()Ljava/lang/invoke/MethodHandle;",
+ null,
+ null);
+ mv.visitCode();
+ mv.visitLdcInsn(new Handle(H_INVOKESPECIAL, cDesc, "vvi", viDesc, false));
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(-1, -1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "vvi", "(I)V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTest.java
new file mode 100644
index 0000000..d42d443
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTest.java
@@ -0,0 +1,385 @@
+// 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.cf;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+public class MethodHandleTest {
+
+ public static class C {
+ public C(int i) {
+ System.out.println("C " + i);
+ }
+
+ public C() {
+ System.out.println("C");
+ }
+
+ public int vi;
+ public static int si;
+
+ public static void svi(int i) {
+ System.out.println("svi " + i);
+ }
+
+ public static long sji(int i) {
+ System.out.println("sji " + i);
+ return 42L;
+ }
+
+ public static void svic(int i, char c) {
+ System.out.println("svic " + i);
+ }
+
+ public static long sjic(int i, char c) {
+ System.out.println("sjic " + i);
+ return 42L;
+ }
+
+ public void vvi(int i) {
+ System.out.println("vvi " + i);
+ }
+
+ public long vji(int i) {
+ System.out.println("vji " + i);
+ return 42L;
+ }
+
+ public void vvic(int i, char c) {
+ System.out.println("vvic " + i);
+ }
+
+ public long vjic(int i, char c) {
+ System.out.println("vjic " + i);
+ return 42L;
+ }
+ }
+
+ public static class D extends C {
+ public static MethodHandle vcviSpecialMethod() {
+ try {
+ return MethodHandles.lookup().findSpecial(C.class, "vvi", viType(), D.class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void vvi(int i) {
+ // Overridden to output nothing.
+ }
+ }
+
+ public interface I {
+ int ii = 42;
+
+ static void svi(int i) {
+ System.out.println("svi " + i);
+ }
+
+ static long sji(int i) {
+ System.out.println("sji " + i);
+ return 42L;
+ }
+
+ static void svic(int i, char c) {
+ System.out.println("svic " + i);
+ }
+
+ static long sjic(int i, char c) {
+ System.out.println("sjic " + i);
+ return 42L;
+ }
+
+ default void dvi(int i) {
+ System.out.println("dvi " + i);
+ }
+
+ default long dji(int i) {
+ System.out.println("dji " + i);
+ return 42L;
+ }
+
+ default void dvic(int i, char c) {
+ System.out.println("dvic " + i);
+ }
+
+ default long djic(int i, char c) {
+ System.out.println("djic " + i);
+ return 42L;
+ }
+ }
+
+ public static class Impl implements I {}
+
+ public static void main(String[] args) {
+ // When MethodHandleTestRunner invokes this program with the JVM, "fail" is passed as arg.
+ // When invoked with Art, no arg is passed since interface fields may be modified on Art.
+ String expectedResult = args[0];
+ C c = new C(42);
+ I i = new Impl();
+ try {
+ scviMethod().invoke(1);
+ assertEquals(42L, (long) scjiMethod().invoke(2));
+ scvicMethod().invoke(3, 'x');
+ assertEquals(42L, (long) scjicMethod().invoke(4, 'x'));
+ vcviMethod().invoke(c, 5);
+ assertEquals(42L, (long) vcjiMethod().invoke(c, 6));
+ vcvicMethod().invoke(c, 7, 'x');
+ assertEquals(42L, (long) vcjicMethod().invoke(c, 8, 'x'));
+ siviMethod().invoke(9);
+ assertEquals(42L, (long) sijiMethod().invoke(10));
+ sivicMethod().invoke(11, 'x');
+ assertEquals(42L, (long) sijicMethod().invoke(12, 'x'));
+ diviMethod().invoke(i, 13);
+ assertEquals(42L, (long) dijiMethod().invoke(i, 14));
+ divicMethod().invoke(i, 15, 'x');
+ assertEquals(42L, (long) dijicMethod().invoke(i, 16, 'x'));
+ vciSetField().invoke(c, 17);
+ assertEquals(17, (int) vciGetField().invoke(c));
+ sciSetField().invoke(18);
+ assertEquals(18, (int) sciGetField().invoke());
+ String interfaceSetResult;
+ try {
+ iiSetField().invoke(19);
+ interfaceSetResult = "pass";
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof IllegalAccessException) {
+ interfaceSetResult = "exception";
+ } else {
+ throw e;
+ }
+ } catch (IllegalAccessError e) {
+ interfaceSetResult = "error";
+ }
+ if (!interfaceSetResult.equals(expectedResult)) {
+ throw new RuntimeException(
+ "Wrong outcome of iiSetField().invoke(): Expected "
+ + expectedResult
+ + " but got "
+ + interfaceSetResult);
+ }
+ assertEquals(interfaceSetResult.equals("pass") ? 19 : 42, (int) iiGetField().invoke());
+ MethodHandle methodHandle = D.vcviSpecialMethod();
+ methodHandle.invoke(new D(), 20);
+ constructorMethod().invoke(21);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static void assertEquals(long l, long x) {
+ if (l != x) {
+ throw new AssertionError("Not equal: " + l + " != " + x);
+ }
+ }
+
+ private static void assertEquals(int l, int x) {
+ if (l != x) {
+ throw new AssertionError("Not equal: " + l + " != " + x);
+ }
+ }
+
+ public static MethodType viType() {
+ return MethodType.methodType(void.class, int.class);
+ }
+
+ public static MethodType jiType() {
+ return MethodType.methodType(long.class, int.class);
+ }
+
+ public static MethodType vicType() {
+ return MethodType.methodType(void.class, int.class, char.class);
+ }
+
+ public static MethodType jicType() {
+ return MethodType.methodType(long.class, int.class, char.class);
+ }
+
+ public static MethodHandle scviMethod() {
+ try {
+ return MethodHandles.lookup().findStatic(C.class, "svi", viType());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle scjiMethod() {
+ try {
+ return MethodHandles.lookup().findStatic(C.class, "sji", jiType());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle scvicMethod() {
+ try {
+ return MethodHandles.lookup().findStatic(C.class, "svic", vicType());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle scjicMethod() {
+ try {
+ return MethodHandles.lookup().findStatic(C.class, "sjic", jicType());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle vcviMethod() {
+ try {
+ return MethodHandles.lookup().findVirtual(C.class, "vvi", viType());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle vcjiMethod() {
+ try {
+ return MethodHandles.lookup().findVirtual(C.class, "vji", jiType());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle vcvicMethod() {
+ try {
+ return MethodHandles.lookup().findVirtual(C.class, "vvic", vicType());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle vcjicMethod() {
+ try {
+ return MethodHandles.lookup().findVirtual(C.class, "vjic", jicType());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle siviMethod() {
+ try {
+ return MethodHandles.lookup().findStatic(I.class, "svi", viType());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle sijiMethod() {
+ try {
+ return MethodHandles.lookup().findStatic(I.class, "sji", jiType());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle sivicMethod() {
+ try {
+ return MethodHandles.lookup().findStatic(I.class, "svic", vicType());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle sijicMethod() {
+ try {
+ return MethodHandles.lookup().findStatic(I.class, "sjic", jicType());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle diviMethod() {
+ try {
+ return MethodHandles.lookup().findVirtual(I.class, "dvi", viType());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle dijiMethod() {
+ try {
+ return MethodHandles.lookup().findVirtual(I.class, "dji", jiType());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle divicMethod() {
+ try {
+ return MethodHandles.lookup().findVirtual(I.class, "dvic", vicType());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle dijicMethod() {
+ try {
+ return MethodHandles.lookup().findVirtual(I.class, "djic", jicType());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle vciSetField() {
+ try {
+ return MethodHandles.lookup().findSetter(C.class, "vi", int.class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle sciSetField() {
+ try {
+ return MethodHandles.lookup().findStaticSetter(C.class, "si", int.class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle vciGetField() {
+ try {
+ return MethodHandles.lookup().findGetter(C.class, "vi", int.class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle sciGetField() {
+ try {
+ return MethodHandles.lookup().findStaticGetter(C.class, "si", int.class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle iiSetField() {
+ try {
+ return MethodHandles.lookup().findStaticSetter(I.class, "ii", int.class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle iiGetField() {
+ try {
+ return MethodHandles.lookup().findStaticGetter(I.class, "ii", int.class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle constructorMethod() {
+ try {
+ return MethodHandles.lookup().findConstructor(C.class, viType());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
new file mode 100644
index 0000000..3870598
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -0,0 +1,120 @@
+// 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.cf;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8Command.Builder;
+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.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.nio.file.Path;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class MethodHandleTestRunner {
+ static final Class<?> CLASS = MethodHandleTest.class;
+
+ private boolean ldc = false;
+
+ @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ @Test
+ public void testMethodHandlesLookup() throws Exception {
+ // Run test with dynamic method lookups, i.e. using MethodHandles.lookup().find*()
+ ldc = false;
+ test();
+ }
+
+ @Test
+ public void testLdcMethodHandle() throws Exception {
+ // Run test with LDC methods, i.e. without java.lang.invoke.MethodHandles
+ ldc = true;
+ test();
+ }
+
+ private final Class[] inputClasses = {
+ MethodHandleTest.class,
+ MethodHandleTest.C.class,
+ MethodHandleTest.I.class,
+ MethodHandleTest.Impl.class,
+ MethodHandleTest.D.class,
+ };
+
+ private void test() throws Exception {
+ ProcessResult runInput = runInput();
+ Path outCf = temp.getRoot().toPath().resolve("cf.jar");
+ build(new ClassFileConsumer.ArchiveConsumer(outCf));
+ Path outDex = temp.getRoot().toPath().resolve("dex.zip");
+ build(new DexIndexedConsumer.ArchiveConsumer(outDex));
+
+ ProcessResult runCf =
+ ToolHelper.runJava(outCf, CLASS.getCanonicalName(), ldc ? "error" : "exception");
+ ProcessResult runDex =
+ ToolHelper.runArtRaw(
+ outDex.toString(),
+ CLASS.getCanonicalName(),
+ cmd -> cmd.appendProgramArgument(ldc ? "pass" : "exception"));
+ assertEquals(runInput.toString(), runCf.toString());
+ // Only compare stdout and exitCode since dex2oat prints to stderr.
+ if (runInput.exitCode != runDex.exitCode) {
+ System.out.println(runDex.stderr);
+ }
+ assertEquals(runInput.stdout, runDex.stdout);
+ assertEquals(runInput.exitCode, runDex.exitCode);
+ }
+
+ private void build(ProgramConsumer programConsumer) throws Exception {
+ // MethodHandle.invoke() only supported from Android O
+ // ConstMethodHandle only supported from Android P
+ AndroidApiLevel apiLevel = AndroidApiLevel.P;
+ Builder cfBuilder =
+ R8Command.builder()
+ .setMinApiLevel(apiLevel.getLevel())
+ .setMode(CompilationMode.DEBUG)
+ .addLibraryFiles(ToolHelper.getAndroidJar(apiLevel))
+ .setProgramConsumer(programConsumer);
+ for (Class<?> c : inputClasses) {
+ byte[] classAsBytes = getClassAsBytes(c);
+ cfBuilder.addClassProgramData(classAsBytes, Origin.unknown());
+ }
+ R8.run(cfBuilder.build());
+ }
+
+ private ProcessResult runInput() throws Exception {
+ Path out = temp.getRoot().toPath().resolve("input.jar");
+ ClassFileConsumer.ArchiveConsumer archiveConsumer = new ClassFileConsumer.ArchiveConsumer(out);
+ for (Class<?> c : inputClasses) {
+ archiveConsumer.accept(
+ getClassAsBytes(c), DescriptorUtils.javaTypeToDescriptor(c.getName()), null);
+ }
+ archiveConsumer.finished(null);
+ ProcessResult runInput = ToolHelper.runJava(out, CLASS.getName(), ldc ? "error" : "exception");
+ if (runInput.exitCode != 0) {
+ System.out.println(runInput);
+ }
+ assertEquals(0, runInput.exitCode);
+ return runInput;
+ }
+
+ private byte[] getClassAsBytes(Class<?> clazz) throws Exception {
+ if (ldc) {
+ if (clazz == MethodHandleTest.D.class) {
+ return MethodHandleDump.dumpD();
+ } else if (clazz == MethodHandleTest.class) {
+ return MethodHandleDump.transform(ToolHelper.getClassAsBytes(clazz));
+ }
+ }
+ return ToolHelper.getClassAsBytes(clazz);
+ }
+}