Support compiling with features that are not supported in DEX.
This change refactors the errors for unsupported features to non-final
errors. The --map-diagnostics flag/API can be used to make these
warnings instead. In so doing, the compiler will replace the unsupported
instructions by unconditional throwing instructions with a descriptive
error message.
This CL also adds additional diagnostics types for each of the
unsupported features as part of the future compiler API.
Bug: b/174733673
Bug: b/154778581
Bug: b/198142625
Change-Id: I05d7965d7f9d49b97a5ac06bce301d7a861be802
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
index d52f83c..4bff728 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
@@ -14,10 +14,10 @@
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.InitClassLens;
import com.android.tools.r8.graph.JarApplicationReader;
@@ -34,10 +34,11 @@
import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
import com.android.tools.r8.utils.structural.CompareToVisitor;
+import java.util.ArrayList;
+import java.util.List;
import java.util.ListIterator;
import org.objectweb.asm.ConstantDynamic;
import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
public class CfConstDynamic extends CfInstruction implements CfTypeInstruction {
@@ -47,12 +48,12 @@
DexString name,
DexType type,
DexMethodHandle bootstrapMethod,
- Object[] bootstrapMethodArguments) {
+ List<DexValue> bootstrapMethodArguments) {
assert name != null;
assert type != null;
assert bootstrapMethod != null;
assert bootstrapMethodArguments != null;
- assert bootstrapMethodArguments.length == 0;
+ assert bootstrapMethodArguments.isEmpty();
reference = new ConstantDynamicReference(name, type, bootstrapMethod, bootstrapMethodArguments);
}
@@ -79,7 +80,7 @@
return reference.getBootstrapMethod();
}
- public Object[] getBootstrapMethodArguments() {
+ public List<DexValue> getBootstrapMethodArguments() {
return reference.getBootstrapMethodArguments();
}
@@ -87,53 +88,20 @@
ConstantDynamic insn, JarApplicationReader application, DexType clazz) {
String constantName = insn.getName();
String constantDescriptor = insn.getDescriptor();
- // TODO(b/178172809): Handle bootstrap arguments.
- if (insn.getBootstrapMethodArgumentCount() > 0) {
- throw new CompilationError(
- "Unsupported dynamic constant (has arguments to bootstrap method)");
- }
- if (insn.getBootstrapMethod().getTag() != Opcodes.H_INVOKESTATIC) {
- throw new CompilationError("Unsupported dynamic constant (not invoke static)");
- }
- if (insn.getBootstrapMethod().getOwner().equals("java/lang/invoke/ConstantBootstraps")) {
- throw new CompilationError(
- "Unsupported dynamic constant (runtime provided bootstrap method)");
- }
- if (application.getTypeFromName(insn.getBootstrapMethod().getOwner()) != clazz) {
- throw new CompilationError("Unsupported dynamic constant (different owner)");
- }
- // Resolve the bootstrap method.
DexMethodHandle bootstrapMethodHandle =
DexMethodHandle.fromAsmHandle(insn.getBootstrapMethod(), application, clazz);
- if (!bootstrapMethodHandle.member.isDexMethod()) {
- throw new CompilationError("Unsupported dynamic constant (invalid method handle)");
- }
- DexMethod bootstrapMethod = bootstrapMethodHandle.asMethod();
- if (bootstrapMethod.getProto().returnType != application.getTypeFromDescriptor("[Z")
- && bootstrapMethod.getProto().returnType
- != application.getTypeFromDescriptor("Ljava/lang/Object;")) {
- throw new CompilationError("Unsupported dynamic constant (unsupported constant type)");
- }
- if (bootstrapMethod.getProto().getParameters().size() != 3) {
- throw new CompilationError("Unsupported dynamic constant (unsupported signature)");
- }
- if (bootstrapMethod.getProto().getParameters().get(0) != application.getFactory().lookupType) {
- throw new CompilationError(
- "Unsupported dynamic constant (unexpected type of first argument to bootstrap method");
- }
- if (bootstrapMethod.getProto().getParameters().get(1) != application.getFactory().stringType) {
- throw new CompilationError(
- "Unsupported dynamic constant (unexpected type of second argument to bootstrap method");
- }
- if (bootstrapMethod.getProto().getParameters().get(2) != application.getFactory().classType) {
- throw new CompilationError(
- "Unsupported dynamic constant (unexpected type of third argument to bootstrap method");
+ int argumentCount = insn.getBootstrapMethodArgumentCount();
+ List<DexValue> bootstrapMethodArguments = new ArrayList<>(argumentCount);
+ for (int i = 0; i < argumentCount; i++) {
+ Object argument = insn.getBootstrapMethodArgument(i);
+ DexValue dexValue = DexValue.fromAsmBootstrapArgument(argument, application, clazz);
+ bootstrapMethodArguments.add(dexValue);
}
return new CfConstDynamic(
application.getString(constantName),
application.getTypeFromDescriptor(constantDescriptor),
bootstrapMethodHandle,
- new Object[] {});
+ bootstrapMethodArguments);
}
@Override
@@ -186,8 +154,30 @@
NamingLens namingLens,
LensCodeRewriterUtils rewriter,
MethodVisitor visitor) {
- // TODO(b/198142625): Support CONSTANT_Dynamic for R8 cf to cf.
- throw new CompilationError("Unsupported dynamic constant (not desugaring)");
+ DexMethodHandle rewrittenHandle =
+ rewriter.rewriteDexMethodHandle(
+ reference.getBootstrapMethod(), NOT_ARGUMENT_TO_LAMBDA_METAFACTORY, context);
+ List<DexValue> rewrittenArguments =
+ rewriter.rewriteBootstrapArguments(
+ reference.getBootstrapMethodArguments(), NOT_ARGUMENT_TO_LAMBDA_METAFACTORY, context);
+ Object[] bsmArgs = new Object[rewrittenArguments.size()];
+ for (int i = 0; i < rewrittenArguments.size(); i++) {
+ bsmArgs[i] = CfInvokeDynamic.decodeBootstrapArgument(rewrittenArguments.get(i), namingLens);
+ }
+ ConstantDynamic constantDynamic =
+ new ConstantDynamic(
+ reference.getName().toString(),
+ getConstantTypeDescriptor(graphLens, namingLens, dexItemFactory),
+ rewrittenHandle.toAsmHandle(namingLens),
+ bsmArgs);
+ visitor.visitLdcInsn(constantDynamic);
+ }
+
+ private String getConstantTypeDescriptor(
+ GraphLens graphLens, NamingLens namingLens, DexItemFactory factory) {
+ DexType rewrittenType = graphLens.lookupType(reference.getType());
+ DexType renamedType = namingLens.lookupType(rewrittenType, factory);
+ return renamedType.toDescriptorString();
}
@Override
@@ -212,7 +202,7 @@
registry.registerTypeReference(reference.getType());
registry.registerMethodHandle(
reference.getBootstrapMethod(), NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
- assert reference.getBootstrapMethodArguments().length == 0;
+ assert reference.getBootstrapMethodArguments().isEmpty();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index 45228d7..b880c47 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -98,7 +98,7 @@
return 5;
}
- private Object decodeBootstrapArgument(DexValue value, NamingLens lens) {
+ public static Object decodeBootstrapArgument(DexValue value, NamingLens lens) {
switch (value.getValueKind()) {
case DOUBLE:
return value.asDexValueDouble().getValue();
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 6167f9b..a6f6a52 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -7,10 +7,10 @@
import com.android.tools.r8.ByteBufferProvider;
import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.DefaultInterfaceMethodDiagnostic;
-import com.android.tools.r8.errors.InvokeCustomDiagnostic;
-import com.android.tools.r8.errors.PrivateInterfaceMethodDiagnostic;
-import com.android.tools.r8.errors.StaticInterfaceMethodDiagnostic;
+import com.android.tools.r8.errors.UnsupportedDefaultInterfaceMethodDiagnostic;
+import com.android.tools.r8.errors.UnsupportedInvokeCustomDiagnostic;
+import com.android.tools.r8.errors.UnsupportedPrivateInterfaceMethodDiagnostic;
+import com.android.tools.r8.errors.UnsupportedStaticInterfaceMethodDiagnostic;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationDirectory;
@@ -290,9 +290,9 @@
if (!options.canUseDefaultAndStaticInterfaceMethods()
&& !options.testing.allowStaticInterfaceMethodsForPreNApiLevel) {
throw options.reporter.fatalError(
- new StaticInterfaceMethodDiagnostic(holder.getOrigin(), MethodPosition.create(method)));
+ new UnsupportedStaticInterfaceMethodDiagnostic(
+ holder.getOrigin(), MethodPosition.create(method)));
}
-
} else {
if (method.isInstanceInitializer()) {
throw new CompilationError(
@@ -301,7 +301,7 @@
if (!method.accessFlags.isAbstract() && !method.accessFlags.isPrivate() &&
!options.canUseDefaultAndStaticInterfaceMethods()) {
throw options.reporter.fatalError(
- new DefaultInterfaceMethodDiagnostic(
+ new UnsupportedDefaultInterfaceMethodDiagnostic(
holder.getOrigin(), MethodPosition.create(method)));
}
}
@@ -311,7 +311,8 @@
return;
}
throw options.reporter.fatalError(
- new PrivateInterfaceMethodDiagnostic(holder.getOrigin(), MethodPosition.create(method)));
+ new UnsupportedPrivateInterfaceMethodDiagnostic(
+ holder.getOrigin(), MethodPosition.create(method)));
}
if (!method.accessFlags.isPublic()) {
@@ -1382,7 +1383,7 @@
private void checkThatInvokeCustomIsAllowed() {
if (!options.canUseInvokeCustom()) {
throw options.reporter.fatalError(
- new InvokeCustomDiagnostic(Origin.unknown(), Position.UNKNOWN));
+ new UnsupportedInvokeCustomDiagnostic(Origin.unknown(), Position.UNKNOWN));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/errors/ConstMethodHandleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/ConstMethodHandleDiagnostic.java
deleted file mode 100644
index d77a5f5..0000000
--- a/src/main/java/com/android/tools/r8/errors/ConstMethodHandleDiagnostic.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2022, 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.errors;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class ConstMethodHandleDiagnostic extends UnsupportedFeatureDiagnostic {
-
- public ConstMethodHandleDiagnostic(Origin origin, MethodPosition position) {
- super("const-method-handle", AndroidApiLevel.P, origin, position);
- }
-
- @Override
- public String getDiagnosticMessage() {
- return UnsupportedFeatureDiagnostic.makeMessage(
- AndroidApiLevel.P, "Const-method-handle", getPosition().toString());
- }
-}
diff --git a/src/main/java/com/android/tools/r8/errors/ConstMethodTypeDiagnostic.java b/src/main/java/com/android/tools/r8/errors/ConstMethodTypeDiagnostic.java
deleted file mode 100644
index 2a9eff5..0000000
--- a/src/main/java/com/android/tools/r8/errors/ConstMethodTypeDiagnostic.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2022, 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.errors;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class ConstMethodTypeDiagnostic extends UnsupportedFeatureDiagnostic {
-
- public ConstMethodTypeDiagnostic(Origin origin, MethodPosition position) {
- super("const-method-type", AndroidApiLevel.P, origin, position);
- }
-
- @Override
- public String getDiagnosticMessage() {
- return UnsupportedFeatureDiagnostic.makeMessage(
- AndroidApiLevel.P, "Const-method-type", getPosition().toString());
- }
-}
diff --git a/src/main/java/com/android/tools/r8/errors/ConstantDynamicDesugarDiagnostic.java b/src/main/java/com/android/tools/r8/errors/ConstantDynamicDesugarDiagnostic.java
new file mode 100644
index 0000000..d80b530
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/ConstantDynamicDesugarDiagnostic.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2022, 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.errors;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+/** Common type for all diagnostics related to constant-dynamic desugaring. */
+@Keep
+public class ConstantDynamicDesugarDiagnostic implements DesugarDiagnostic {
+
+ private final Origin origin;
+ private final Position position;
+ private final String message;
+
+ public ConstantDynamicDesugarDiagnostic(Origin origin, Position position, String message) {
+ this.origin = origin;
+ this.position = position;
+ this.message = message;
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin;
+ }
+
+ @Override
+ public Position getPosition() {
+ return position;
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return message;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/DefaultInterfaceMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/DefaultInterfaceMethodDiagnostic.java
deleted file mode 100644
index 21b15b2..0000000
--- a/src/main/java/com/android/tools/r8/errors/DefaultInterfaceMethodDiagnostic.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.errors;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class DefaultInterfaceMethodDiagnostic extends UnsupportedFeatureDiagnostic {
-
- public DefaultInterfaceMethodDiagnostic(Origin origin, MethodPosition position) {
- super("default-interface-method", AndroidApiLevel.N, origin, position);
- }
-
- @Override
- public String getDiagnosticMessage() {
- return UnsupportedFeatureDiagnostic.makeMessage(
- AndroidApiLevel.N, "Default interface methods", getPosition().toString());
- }
-}
diff --git a/src/main/java/com/android/tools/r8/errors/InvokeCustomDiagnostic.java b/src/main/java/com/android/tools/r8/errors/InvokeCustomDiagnostic.java
deleted file mode 100644
index 0f8c6ab..0000000
--- a/src/main/java/com/android/tools/r8/errors/InvokeCustomDiagnostic.java
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.errors;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.Position;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class InvokeCustomDiagnostic extends UnsupportedFeatureDiagnostic {
-
- public InvokeCustomDiagnostic(Origin origin, Position position) {
- super("invoke-custom", AndroidApiLevel.O, origin, position);
- }
-
- @Override
- public String getDiagnosticMessage() {
- return UnsupportedFeatureDiagnostic.makeMessage(AndroidApiLevel.O, "Invoke-customs", null);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/errors/InvokePolymorphicMethodHandleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/InvokePolymorphicMethodHandleDiagnostic.java
deleted file mode 100644
index 1efbb31..0000000
--- a/src/main/java/com/android/tools/r8/errors/InvokePolymorphicMethodHandleDiagnostic.java
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) 2022, 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.errors;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class InvokePolymorphicMethodHandleDiagnostic extends UnsupportedFeatureDiagnostic {
-
- public InvokePolymorphicMethodHandleDiagnostic(Origin origin, MethodPosition position) {
- super("invoke-polymorphic-method-handle", AndroidApiLevel.O, origin, position);
- }
-
- @Override
- public String getDiagnosticMessage() {
- return UnsupportedFeatureDiagnostic.makeMessage(
- AndroidApiLevel.O,
- "MethodHandle.invoke and MethodHandle.invokeExact",
- getPosition().toString());
- }
-}
diff --git a/src/main/java/com/android/tools/r8/errors/InvokePolymorphicVarHandleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/InvokePolymorphicVarHandleDiagnostic.java
deleted file mode 100644
index 9758581..0000000
--- a/src/main/java/com/android/tools/r8/errors/InvokePolymorphicVarHandleDiagnostic.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2022, 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.errors;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class InvokePolymorphicVarHandleDiagnostic extends UnsupportedFeatureDiagnostic {
-
- public InvokePolymorphicVarHandleDiagnostic(Origin origin, MethodPosition position) {
- super("invoke-polymorphic-var-handle", AndroidApiLevel.P, origin, position);
- }
-
- @Override
- public String getDiagnosticMessage() {
- return UnsupportedFeatureDiagnostic.makeMessage(
- AndroidApiLevel.P, "Call to polymorphic signature of VarHandle", getPosition().toString());
- }
-}
diff --git a/src/main/java/com/android/tools/r8/errors/PrivateInterfaceMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/PrivateInterfaceMethodDiagnostic.java
deleted file mode 100644
index 9c85254..0000000
--- a/src/main/java/com/android/tools/r8/errors/PrivateInterfaceMethodDiagnostic.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.errors;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class PrivateInterfaceMethodDiagnostic extends UnsupportedFeatureDiagnostic {
-
- public PrivateInterfaceMethodDiagnostic(Origin origin, MethodPosition position) {
- super("private-interface-method", AndroidApiLevel.N, origin, position);
- }
-
- @Override
- public String getDiagnosticMessage() {
- return UnsupportedFeatureDiagnostic.makeMessage(
- AndroidApiLevel.N, "Private interface methods", getPosition().toString());
- }
-}
diff --git a/src/main/java/com/android/tools/r8/errors/StaticInterfaceMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/StaticInterfaceMethodDiagnostic.java
deleted file mode 100644
index 9bc4f48..0000000
--- a/src/main/java/com/android/tools/r8/errors/StaticInterfaceMethodDiagnostic.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.errors;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class StaticInterfaceMethodDiagnostic extends UnsupportedFeatureDiagnostic {
-
- public StaticInterfaceMethodDiagnostic(Origin origin, MethodPosition position) {
- super("static-interface-method", AndroidApiLevel.N, origin, position);
- }
-
- @Override
- public String getDiagnosticMessage() {
- return UnsupportedFeatureDiagnostic.makeMessage(
- AndroidApiLevel.N, "Static interface methods", getPosition().toString());
- }
-}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedConstDynamicDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedConstDynamicDiagnostic.java
new file mode 100644
index 0000000..b75f686
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedConstDynamicDiagnostic.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2022, 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.errors;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.utils.InternalOptions;
+
+@Keep
+public class UnsupportedConstDynamicDiagnostic extends UnsupportedFeatureDiagnostic {
+
+ // API: MUST NOT CHANGE!
+ private static final String DESCRIPTOR = "const-dynamic";
+
+ public UnsupportedConstDynamicDiagnostic(Origin origin, Position position) {
+ super(DESCRIPTOR, InternalOptions.constantDynamicApiLevel(), origin, position);
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return UnsupportedFeatureDiagnostic.makeMessage(
+ InternalOptions.constantDynamicApiLevel(), DESCRIPTOR, getPosition().toString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedConstMethodHandleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedConstMethodHandleDiagnostic.java
new file mode 100644
index 0000000..375adf1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedConstMethodHandleDiagnostic.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2022, 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.errors;
+
+import static com.android.tools.r8.utils.InternalOptions.constantMethodHandleApiLevel;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class UnsupportedConstMethodHandleDiagnostic extends UnsupportedFeatureDiagnostic {
+
+ // API: MUST NOT CHANGE!
+ private static final String DESCRIPTOR = "const-method-handle";
+
+ public UnsupportedConstMethodHandleDiagnostic(Origin origin, Position position) {
+ super(DESCRIPTOR, constantMethodHandleApiLevel(), origin, position);
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return UnsupportedFeatureDiagnostic.makeMessage(
+ constantMethodHandleApiLevel(), DESCRIPTOR, getPosition().toString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedConstMethodTypeDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedConstMethodTypeDiagnostic.java
new file mode 100644
index 0000000..5809011
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedConstMethodTypeDiagnostic.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2022, 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.errors;
+
+import static com.android.tools.r8.utils.InternalOptions.constantMethodTypeApiLevel;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class UnsupportedConstMethodTypeDiagnostic extends UnsupportedFeatureDiagnostic {
+
+ // API: MUST NOT CHANGE!
+ private static final String DESCRIPTOR = "const-method-type";
+
+ public UnsupportedConstMethodTypeDiagnostic(Origin origin, Position position) {
+ super(DESCRIPTOR, constantMethodTypeApiLevel(), origin, position);
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return UnsupportedFeatureDiagnostic.makeMessage(
+ constantMethodTypeApiLevel(), DESCRIPTOR, getPosition().toString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedDefaultInterfaceMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedDefaultInterfaceMethodDiagnostic.java
new file mode 100644
index 0000000..6905dcc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedDefaultInterfaceMethodDiagnostic.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2022, 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.errors;
+
+import static com.android.tools.r8.utils.InternalOptions.defaultInterfaceMethodsApiLevel;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class UnsupportedDefaultInterfaceMethodDiagnostic extends UnsupportedFeatureDiagnostic {
+
+ // API: MUST NOT CHANGE!
+ private static final String DESCRIPTOR = "default-interface-method";
+
+ public UnsupportedDefaultInterfaceMethodDiagnostic(Origin origin, Position position) {
+ super(DESCRIPTOR, defaultInterfaceMethodsApiLevel(), origin, position);
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return UnsupportedFeatureDiagnostic.makeMessage(
+ defaultInterfaceMethodsApiLevel(), "Default interface methods", getPosition().toString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedFeatureDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedFeatureDiagnostic.java
index 74c3ad6..1ac0482 100644
--- a/src/main/java/com/android/tools/r8/errors/UnsupportedFeatureDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedFeatureDiagnostic.java
@@ -15,12 +15,14 @@
public static String makeMessage(
AndroidApiLevel minApiLevel, String unsupportedFeatures, String sourceString) {
String message =
- unsupportedFeatures
- + " are only supported starting with "
- + minApiLevel.getName()
- + " (--min-api "
- + minApiLevel.getLevel()
- + ")";
+ minApiLevel == null
+ ? (unsupportedFeatures + " are not supported at any API level known by the compiler")
+ : (unsupportedFeatures
+ + " are only supported starting with "
+ + minApiLevel.getName()
+ + " (--min-api "
+ + minApiLevel.getLevel()
+ + ")");
message = (sourceString != null) ? message + ": " + sourceString : message;
return message;
}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedInvokeCustomDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedInvokeCustomDiagnostic.java
new file mode 100644
index 0000000..ca67569
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedInvokeCustomDiagnostic.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2022, 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.errors;
+
+import static com.android.tools.r8.utils.InternalOptions.invokeCustomApiLevel;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class UnsupportedInvokeCustomDiagnostic extends UnsupportedFeatureDiagnostic {
+
+ // API: MUST NOT CHANGE!
+ private static final String DESCRIPTOR = "invoke-custom";
+
+ public UnsupportedInvokeCustomDiagnostic(Origin origin, Position position) {
+ super(DESCRIPTOR, invokeCustomApiLevel(), origin, position);
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return UnsupportedFeatureDiagnostic.makeMessage(invokeCustomApiLevel(), "Invoke-customs", null);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedInvokePolymorphicMethodHandleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedInvokePolymorphicMethodHandleDiagnostic.java
new file mode 100644
index 0000000..23718f3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedInvokePolymorphicMethodHandleDiagnostic.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2022, 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.errors;
+
+import static com.android.tools.r8.utils.InternalOptions.invokePolymorphicOnMethodHandleApiLevel;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class UnsupportedInvokePolymorphicMethodHandleDiagnostic
+ extends UnsupportedFeatureDiagnostic {
+
+ // API: MUST NOT CHANGE!
+ private static final String DESCRIPTOR = "invoke-polymorphic-method-handle";
+
+ public UnsupportedInvokePolymorphicMethodHandleDiagnostic(Origin origin, Position position) {
+ super(DESCRIPTOR, invokePolymorphicOnMethodHandleApiLevel(), origin, position);
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return UnsupportedFeatureDiagnostic.makeMessage(
+ invokePolymorphicOnMethodHandleApiLevel(),
+ "MethodHandle.invoke and MethodHandle.invokeExact",
+ getPosition().toString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedInvokePolymorphicVarHandleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedInvokePolymorphicVarHandleDiagnostic.java
new file mode 100644
index 0000000..49f73a6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedInvokePolymorphicVarHandleDiagnostic.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2022, 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.errors;
+
+import static com.android.tools.r8.utils.InternalOptions.invokePolymorphicOnVarHandleApiLevel;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class UnsupportedInvokePolymorphicVarHandleDiagnostic extends UnsupportedFeatureDiagnostic {
+
+ // API: MUST NOT CHANGE!
+ private static final String DESCRIPTOR = "invoke-polymorphic-var-handle";
+
+ public UnsupportedInvokePolymorphicVarHandleDiagnostic(Origin origin, Position position) {
+ super(DESCRIPTOR, invokePolymorphicOnVarHandleApiLevel(), origin, position);
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return UnsupportedFeatureDiagnostic.makeMessage(
+ invokePolymorphicOnVarHandleApiLevel(),
+ "Call to polymorphic signature of VarHandle",
+ getPosition().toString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedPrivateInterfaceMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedPrivateInterfaceMethodDiagnostic.java
new file mode 100644
index 0000000..0a52151
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedPrivateInterfaceMethodDiagnostic.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2022, 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.errors;
+
+import static com.android.tools.r8.utils.InternalOptions.privateInterfaceMethodsApiLevel;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class UnsupportedPrivateInterfaceMethodDiagnostic extends UnsupportedFeatureDiagnostic {
+
+ // API: MUST NOT CHANGE!
+ private static final String DESCRIPTOR = "private-interface-method";
+
+ public UnsupportedPrivateInterfaceMethodDiagnostic(Origin origin, Position position) {
+ super(DESCRIPTOR, privateInterfaceMethodsApiLevel(), origin, position);
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return UnsupportedFeatureDiagnostic.makeMessage(
+ privateInterfaceMethodsApiLevel(), "Private interface methods", getPosition().toString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedStaticInterfaceMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedStaticInterfaceMethodDiagnostic.java
new file mode 100644
index 0000000..3220744
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedStaticInterfaceMethodDiagnostic.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2022, 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.errors;
+
+import static com.android.tools.r8.utils.InternalOptions.staticInterfaceMethodsApiLevel;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class UnsupportedStaticInterfaceMethodDiagnostic extends UnsupportedFeatureDiagnostic {
+
+ // API: MUST NOT CHANGE!
+ private static final String DESCRIPTOR = "static-interface-method";
+
+ public UnsupportedStaticInterfaceMethodDiagnostic(Origin origin, Position position) {
+ super(DESCRIPTOR, staticInterfaceMethodsApiLevel(), origin, position);
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return UnsupportedFeatureDiagnostic.makeMessage(
+ staticInterfaceMethodsApiLevel(), "Static interface methods", getPosition().toString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 68deb8e..9da9fd8 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -422,11 +422,13 @@
parameterLabel.write(
appView, method, dexItemFactory, graphLens, initClassLens, namingLens, rewriter, visitor);
}
+ boolean discardFrames =
+ classFileVersion.isLessThan(CfVersion.V1_6)
+ || (appView.enableWholeProgramOptimizations()
+ && classFileVersion.isEqualTo(CfVersion.V1_6)
+ && !options.shouldKeepStackMapTable());
for (CfInstruction instruction : instructions) {
- if (instruction instanceof CfFrame
- && (classFileVersion.isLessThan(CfVersion.V1_6)
- || (classFileVersion.isEqualTo(CfVersion.V1_6)
- && !options.shouldKeepStackMapTable()))) {
+ if (discardFrames && instruction instanceof CfFrame) {
continue;
}
instruction.write(
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index fbd4b5a..c0aaa95 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -666,6 +666,8 @@
public final DexType metafactoryType =
createStaticallyKnownType("Ljava/lang/invoke/LambdaMetafactory;");
+ public final DexType constantBootstrapsType =
+ createStaticallyKnownType("Ljava/lang/invoke/ConstantBootstraps;");
public final DexType callSiteType = createStaticallyKnownType("Ljava/lang/invoke/CallSite;");
public final DexType lookupType =
createStaticallyKnownType("Ljava/lang/invoke/MethodHandles$Lookup;");
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 2cb6abd..c455396 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -312,7 +312,7 @@
public abstract AbstractValue toAbstractValue(AbstractValueFactory factory);
- static DexValue fromAsmBootstrapArgument(
+ public static DexValue fromAsmBootstrapArgument(
Object value, JarApplicationReader application, DexType clazz) {
if (value instanceof Integer) {
return DexValue.DexValueInt.create((Integer) value);
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 70fddd9..4a29cb6 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
@@ -17,12 +17,8 @@
import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.ConstMethodHandleDiagnostic;
-import com.android.tools.r8.errors.ConstMethodTypeDiagnostic;
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.errors.InvalidDebugInfoException;
-import com.android.tools.r8.errors.InvokePolymorphicMethodHandleDiagnostic;
-import com.android.tools.r8.errors.InvokePolymorphicVarHandleDiagnostic;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
@@ -483,6 +479,14 @@
this.basicBlockNumberGenerator = new NumberGenerator();
}
+ private Origin getOrigin() {
+ return origin;
+ }
+
+ private MethodPosition getPosition() {
+ return MethodPosition.create(method);
+ }
+
public DexItemFactory dexItemFactory() {
return appView.dexItemFactory();
}
@@ -1226,11 +1230,7 @@
}
public void addConstMethodHandle(int dest, DexMethodHandle methodHandle) {
- if (!appView.options().canUseConstantMethodHandle()) {
- throw appView
- .reporter()
- .fatalError(new ConstMethodHandleDiagnostic(origin, MethodPosition.create(method)));
- }
+ assert appView.options().canUseConstantMethodHandle();
TypeElement typeLattice =
TypeElement.fromDexType(
appView.dexItemFactory().methodHandleType, definitelyNotNull(), appView);
@@ -1240,11 +1240,7 @@
}
public void addConstMethodType(int dest, DexProto methodType) {
- if (!appView.options().canUseConstantMethodType()) {
- throw appView
- .reporter()
- .fatalError(new ConstMethodTypeDiagnostic(origin, MethodPosition.create(method)));
- }
+ assert appView.options().canUseConstantMethodType();
TypeElement typeLattice =
TypeElement.fromDexType(
appView.dexItemFactory().methodTypeType, definitelyNotNull(), appView);
@@ -1502,23 +1498,23 @@
add(new RecordFieldValues(fields, out, arguments));
}
+ private boolean verifyRepresentablePolymorphicInvoke(Type type, DexItem item) {
+ if (type != Type.POLYMORPHIC) {
+ return true;
+ }
+ assert item instanceof DexMethod;
+ if (((DexMethod) item).holder == appView.dexItemFactory().methodHandleType) {
+ assert appView.options().canUseInvokePolymorphicOnMethodHandle();
+ }
+ if (((DexMethod) item).holder == appView.dexItemFactory().varHandleType) {
+ assert appView.options().canUseInvokePolymorphicOnVarHandle();
+ }
+ return true;
+ }
+
public void addInvoke(
Type type, DexItem item, DexProto callSiteProto, List<Value> arguments, boolean itf) {
- if (type == Type.POLYMORPHIC) {
- assert item instanceof DexMethod;
- if (!appView.options().canUseInvokePolymorphic()) {
- throw appView
- .reporter()
- .fatalError(
- new InvokePolymorphicMethodHandleDiagnostic(origin, MethodPosition.create(method)));
- } else if (!appView.options().canUseInvokePolymorphicOnVarHandle()
- && ((DexMethod) item).holder == appView.dexItemFactory().varHandleType) {
- throw appView
- .reporter()
- .fatalError(
- new InvokePolymorphicVarHandleDiagnostic(origin, MethodPosition.create(method)));
- }
- }
+ assert verifyRepresentablePolymorphicInvoke(type, item);
add(Invoke.create(type, item, callSiteProto, null, arguments, itf));
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index b0694ab..dfc82e0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -544,7 +544,8 @@
if (options.testing.forceIRForCfToCfDesugar) {
return true;
}
- return !options.isCfDesugaring();
+ assert method.getDefinition().getCode().isCfCode();
+ return !options.isGeneratingClassFiles();
}
private void checkPrefixMerging(ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
index 4adcf5f..eb12093 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
@@ -181,7 +181,7 @@
return methodHandle;
}
- private List<DexValue> rewriteBootstrapArguments(
+ public List<DexValue> rewriteBootstrapArguments(
List<DexValue> bootstrapArgs, MethodHandleUse use, ProgramMethod context) {
List<DexValue> newBootstrapArgs = null;
boolean changed = false;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index a0fd25e..3e41a70 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -137,6 +137,7 @@
if (recordRewriter != null) {
desugarings.add(recordRewriter);
}
+ yieldingDesugarings.add(new UnrepresentableInDexInstructionRemover(appView));
}
static NonEmptyCfInstructionDesugaringCollection createForCfToCfNonDesugar(AppView<?> appView) {
@@ -157,6 +158,8 @@
new NonEmptyCfInstructionDesugaringCollection(appView, noAndroidApiLevelCompute());
desugaringCollection.desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
desugaringCollection.desugarings.add(new InvokeToPrivateRewriter());
+ desugaringCollection.yieldingDesugarings.add(
+ new UnrepresentableInDexInstructionRemover(appView));
return desugaringCollection;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/UnrepresentableInDexInstructionRemover.java b/src/main/java/com/android/tools/r8/ir/desugar/UnrepresentableInDexInstructionRemover.java
new file mode 100644
index 0000000..6d6dc0f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/UnrepresentableInDexInstructionRemover.java
@@ -0,0 +1,397 @@
+// Copyright (c) 2022, 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.desugar;
+
+import static com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.synthesizeThrowRuntimeExceptionWithMessageMethod;
+
+import com.android.tools.r8.cf.code.CfConstDynamic;
+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;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.errors.UnsupportedConstDynamicDiagnostic;
+import com.android.tools.r8.errors.UnsupportedConstMethodHandleDiagnostic;
+import com.android.tools.r8.errors.UnsupportedConstMethodTypeDiagnostic;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
+import com.android.tools.r8.errors.UnsupportedInvokeCustomDiagnostic;
+import com.android.tools.r8.errors.UnsupportedInvokePolymorphicMethodHandleDiagnostic;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Non desugared invoke-dynamic instructions as well as MethodHandle.invokeX instructions cannot be
+ * represented below O API level. Desugar them into throwing stubs to allow compilation to proceed
+ * under the assumption that the code is dead code.
+ */
+public class UnrepresentableInDexInstructionRemover implements CfInstructionDesugaring {
+
+ private abstract static class InstructionMatcher {
+ final AppView<?> appView;
+ final String descriptor;
+ final AndroidApiLevel supportedApiLevel;
+ // TODO(b/237250957): Using ConcurrentHashMap.newKeySet() causes failures on:
+ // HelloWorldCompiledOnArtTest.testHelloCompiledWithX8Dex[Y, api:21, spec: JDK8, D8_L8DEBUG]
+ final Set<DexMethod> reported = Sets.newConcurrentHashSet();
+
+ InstructionMatcher(AppView<?> appView, String descriptor, AndroidApiLevel supportedApiLevel) {
+ this.appView = appView;
+ this.descriptor = descriptor;
+ this.supportedApiLevel = supportedApiLevel;
+ }
+
+ // Rewrite implementation for each instruction case.
+ abstract DesugarDescription compute(CfInstruction instruction);
+
+ abstract UnsupportedFeatureDiagnostic makeDiagnostic(Origin origin, Position position);
+
+ // Helpers
+
+ void report(ProgramMethod context) {
+ if (reported.add(context.getReference())) {
+ UnsupportedFeatureDiagnostic diagnostic =
+ makeDiagnostic(context.getOrigin(), MethodPosition.create(context));
+ assert (diagnostic.getSupportedApiLevel() == -1 && supportedApiLevel == null)
+ || (diagnostic.getSupportedApiLevel() == supportedApiLevel.getLevel());
+ appView.reporter().error(diagnostic);
+ }
+ }
+
+ void invokeThrowingStub(
+ MethodProcessingContext methodProcessingContext,
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ ProgramMethod context,
+ ImmutableList.Builder<CfInstruction> builder) {
+ UtilityMethodForCodeOptimizations throwUtility =
+ synthesizeThrowRuntimeExceptionWithMessageMethod(appView, methodProcessingContext);
+ ProgramMethod throwMethod = throwUtility.uncheckedGetMethod();
+ eventConsumer.acceptThrowMethod(throwMethod, context);
+ builder.add(
+ createMessageString(),
+ new CfInvoke(Opcodes.INVOKESTATIC, throwMethod.getReference(), false),
+ new CfStackInstruction(Opcode.Pop));
+ }
+
+ CfConstString createMessageString() {
+ return new CfConstString(
+ appView
+ .dexItemFactory()
+ .createString(
+ "Instruction is unrepresentable in DEX "
+ + appView.options().getMinApiLevel().getDexVersion()
+ + ": "
+ + descriptor));
+ }
+
+ static void pop(DexType type, Builder<CfInstruction> builder) {
+ assert !type.isVoidType();
+ builder.add(new CfStackInstruction(type.isWideType() ? Opcode.Pop2 : Opcode.Pop));
+ }
+
+ static void pop(Iterable<DexType> types, Builder<CfInstruction> builder) {
+ types.forEach(t -> pop(t, builder));
+ }
+
+ static Builder<CfInstruction> pushReturnValue(DexType type, Builder<CfInstruction> builder) {
+ if (!type.isVoidType()) {
+ builder.add(createDefaultValueForType(type));
+ }
+ return builder;
+ }
+
+ static CfInstruction createDefaultValueForType(DexType type) {
+ assert !type.isVoidType();
+ if (type.isPrimitiveType()) {
+ return new CfConstNumber(0, ValueType.fromDexType(type));
+ }
+ assert type.isReferenceType();
+ return new CfConstNull();
+ }
+ }
+
+ private static class InvokeDynamicMatcher extends InstructionMatcher {
+ static void addIfNeeded(AppView<?> appView, Builder<InstructionMatcher> builder) {
+ InternalOptions options = appView.options();
+ if (!options.canUseInvokeCustom()) {
+ builder.add(new InvokeDynamicMatcher(appView));
+ }
+ }
+
+ InvokeDynamicMatcher(AppView<?> appView) {
+ super(appView, "invoke-dynamic", InternalOptions.invokeCustomApiLevel());
+ }
+
+ @Override
+ UnsupportedFeatureDiagnostic makeDiagnostic(Origin origin, Position position) {
+ return new UnsupportedInvokeCustomDiagnostic(origin, position);
+ }
+
+ @Override
+ DesugarDescription compute(CfInstruction instruction) {
+ CfInvokeDynamic invokeDynamic = instruction.asInvokeDynamic();
+ if (invokeDynamic == null) {
+ return null;
+ }
+ return DesugarDescription.builder()
+ .setDesugarRewrite(
+ (freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ dexItemFactory) -> {
+ report(context);
+ Builder<CfInstruction> replacement = ImmutableList.builder();
+ DexCallSite callSite = invokeDynamic.getCallSite();
+ pop(callSite.getMethodProto().getParameters(), replacement);
+ localStackAllocator.allocateLocalStack(1);
+ invokeThrowingStub(methodProcessingContext, eventConsumer, context, replacement);
+ pushReturnValue(callSite.getMethodProto().getReturnType(), replacement);
+ return replacement.build();
+ })
+ .build();
+ }
+ }
+
+ private static class InvokePolymorphicMatcher extends InstructionMatcher {
+ static void addIfNeeded(AppView<?> appView, Builder<InstructionMatcher> builder) {
+ InternalOptions options = appView.options();
+ if (!options.canUseInvokePolymorphicOnMethodHandle()) {
+ builder.add(new InvokePolymorphicMatcher(appView));
+ }
+ }
+
+ InvokePolymorphicMatcher(AppView<?> appView) {
+ super(
+ appView, "invoke-polymorphic", InternalOptions.invokePolymorphicOnMethodHandleApiLevel());
+ }
+
+ boolean isPolymorphicInvoke(CfInvoke invoke) {
+ return appView.dexItemFactory().polymorphicMethods.isPolymorphicInvoke(invoke.getMethod());
+ }
+
+ @Override
+ UnsupportedFeatureDiagnostic makeDiagnostic(Origin origin, Position position) {
+ return new UnsupportedInvokePolymorphicMethodHandleDiagnostic(origin, position);
+ }
+
+ @Override
+ DesugarDescription compute(CfInstruction instruction) {
+ CfInvoke invoke = instruction.asInvoke();
+ if (invoke == null || !isPolymorphicInvoke(invoke)) {
+ return null;
+ }
+ return DesugarDescription.builder()
+ .setDesugarRewrite(
+ (freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ dexItemFactory) -> {
+ report(context);
+ Builder<CfInstruction> replacement = ImmutableList.builder();
+ if (!invoke.isInvokeStatic()) {
+ pop(dexItemFactory.objectType, replacement);
+ }
+ pop(invoke.getMethod().getParameters(), replacement);
+ localStackAllocator.allocateLocalStack(1);
+ invokeThrowingStub(methodProcessingContext, eventConsumer, context, replacement);
+ pushReturnValue(invoke.getMethod().getReturnType(), replacement);
+ return replacement.build();
+ })
+ .build();
+ }
+ }
+
+ private static class ConstMethodHandleMatcher extends InstructionMatcher {
+ static void addIfNeeded(AppView<?> appView, Builder<InstructionMatcher> builder) {
+ InternalOptions options = appView.options();
+ if (!options.canUseConstantMethodHandle()) {
+ builder.add(new ConstMethodHandleMatcher(appView));
+ }
+ }
+
+ ConstMethodHandleMatcher(AppView<?> appView) {
+ super(appView, "const-method-handle", InternalOptions.constantMethodHandleApiLevel());
+ }
+
+ @Override
+ UnsupportedFeatureDiagnostic makeDiagnostic(Origin origin, Position position) {
+ return new UnsupportedConstMethodHandleDiagnostic(origin, position);
+ }
+
+ @Override
+ DesugarDescription compute(CfInstruction instruction) {
+ if (!(instruction instanceof CfConstMethodHandle)) {
+ return null;
+ }
+ return DesugarDescription.builder()
+ .setDesugarRewrite(
+ (freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ dexItemFactory) -> {
+ report(context);
+ Builder<CfInstruction> replacement = ImmutableList.builder();
+ invokeThrowingStub(methodProcessingContext, eventConsumer, context, replacement);
+ return replacement.add(new CfConstNull()).build();
+ })
+ .build();
+ }
+ }
+
+ private static class ConstMethodTypeMatcher extends InstructionMatcher {
+ static void addIfNeeded(AppView<?> appView, Builder<InstructionMatcher> builder) {
+ InternalOptions options = appView.options();
+ if (!options.canUseConstantMethodType()) {
+ builder.add(new ConstMethodTypeMatcher(appView));
+ }
+ }
+
+ ConstMethodTypeMatcher(AppView<?> appView) {
+ super(appView, "const-method-type", InternalOptions.constantMethodTypeApiLevel());
+ }
+
+ @Override
+ UnsupportedFeatureDiagnostic makeDiagnostic(Origin origin, Position position) {
+ return new UnsupportedConstMethodTypeDiagnostic(origin, position);
+ }
+
+ @Override
+ DesugarDescription compute(CfInstruction instruction) {
+ if (!(instruction instanceof CfConstMethodType)) {
+ return null;
+ }
+ return DesugarDescription.builder()
+ .setDesugarRewrite(
+ (freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ dexItemFactory) -> {
+ report(context);
+ Builder<CfInstruction> replacement = ImmutableList.builder();
+ invokeThrowingStub(methodProcessingContext, eventConsumer, context, replacement);
+ return replacement.add(new CfConstNull()).build();
+ })
+ .build();
+ }
+ }
+
+ private static class ConstDynamicMatcher extends InstructionMatcher {
+ static void addIfNeeded(AppView<?> appView, Builder<InstructionMatcher> builder) {
+ InternalOptions options = appView.options();
+ if (!options.canUseConstantDynamic()) {
+ builder.add(new ConstDynamicMatcher(appView));
+ }
+ }
+
+ ConstDynamicMatcher(AppView<?> appView) {
+ super(appView, "const-dynamic", InternalOptions.constantDynamicApiLevel());
+ }
+
+ @Override
+ UnsupportedFeatureDiagnostic makeDiagnostic(Origin origin, Position position) {
+ return new UnsupportedConstDynamicDiagnostic(origin, position);
+ }
+
+ @Override
+ DesugarDescription compute(CfInstruction instruction) {
+ final CfConstDynamic constDynamic = instruction.asConstDynamic();
+ if (constDynamic == null) {
+ return null;
+ }
+ return DesugarDescription.builder()
+ .setDesugarRewrite(
+ (freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ dexItemFactory) -> {
+ report(context);
+ Builder<CfInstruction> replacement = ImmutableList.builder();
+ invokeThrowingStub(methodProcessingContext, eventConsumer, context, replacement);
+ return pushReturnValue(constDynamic.getType(), replacement).build();
+ })
+ .build();
+ }
+ }
+
+ private final List<InstructionMatcher> matchers;
+
+ public UnrepresentableInDexInstructionRemover(AppView<?> appView) {
+ Builder<InstructionMatcher> builder = ImmutableList.builder();
+ InvokeDynamicMatcher.addIfNeeded(appView, builder);
+ InvokePolymorphicMatcher.addIfNeeded(appView, builder);
+ ConstMethodHandleMatcher.addIfNeeded(appView, builder);
+ ConstMethodTypeMatcher.addIfNeeded(appView, builder);
+ ConstDynamicMatcher.addIfNeeded(appView, builder);
+ matchers = builder.build();
+ }
+
+ private DesugarDescription compute(CfInstruction instruction) {
+ for (InstructionMatcher matcher : matchers) {
+ DesugarDescription result = matcher.compute(instruction);
+ if (result != null) {
+ return result;
+ }
+ }
+ return DesugarDescription.nothing();
+ }
+
+ @Override
+ public Collection<CfInstruction> desugarInstruction(
+ CfInstruction instruction,
+ FreshLocalProvider freshLocalProvider,
+ LocalStackAllocator localStackAllocator,
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ ProgramMethod context,
+ MethodProcessingContext methodProcessingContext,
+ CfInstructionDesugaringCollection desugaringCollection,
+ DexItemFactory dexItemFactory) {
+ return compute(instruction)
+ .desugarInstruction(
+ freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ dexItemFactory);
+ }
+
+ @Override
+ public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+ return compute(instruction).needsDesugaring();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
index 8853e8b..b9826d5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
@@ -7,16 +7,20 @@
import com.android.tools.r8.cf.code.CfConstDynamic;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.errors.ConstantDynamicDesugarDiagnostic;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.DesugarDescription;
import com.android.tools.r8.ir.desugar.FreshLocalProvider;
import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.position.MethodPosition;
import com.android.tools.r8.utils.Box;
import java.util.Collection;
import java.util.HashMap;
@@ -33,9 +37,90 @@
this.appView = appView;
}
+ private DesugarDescription report(String message, ProgramMethod context) {
+ return DesugarDescription.builder()
+ .addScanEffect(
+ () ->
+ appView
+ .reporter()
+ .error(
+ new ConstantDynamicDesugarDiagnostic(
+ context.getOrigin(), MethodPosition.create(context), message)))
+ .build();
+ }
+
+ private DesugarDescription computeDesugaring(CfInstruction instruction, ProgramMethod context) {
+ if (!instruction.isConstDynamic()) {
+ return DesugarDescription.nothing();
+ }
+ CfConstDynamic constDynamic = instruction.asConstDynamic();
+ if (!constDynamic.getBootstrapMethodArguments().isEmpty()) {
+ // TODO(b/178172809): Handle bootstrap arguments.
+ return report("Unsupported dynamic constant (has arguments to bootstrap method)", context);
+ }
+ if (!constDynamic.getBootstrapMethod().type.isInvokeStatic()) {
+ return report("Unsupported dynamic constant (not invoke static)", context);
+ }
+ DexItemFactory factory = appView.dexItemFactory();
+ DexMethod bootstrapMethod = constDynamic.getBootstrapMethod().asMethod();
+ DexType holder = bootstrapMethod.getHolderType();
+ if (holder == factory.constantBootstrapsType) {
+ return report("Unsupported dynamic constant (runtime provided bootstrap method)", context);
+ }
+ if (holder != context.getHolderType()) {
+ return report("Unsupported dynamic constant (different owner)", context);
+ }
+ if (bootstrapMethod.getProto().returnType != factory.booleanArrayType
+ && bootstrapMethod.getProto().returnType != factory.objectType) {
+ return report("Unsupported dynamic constant (unsupported constant type)", context);
+ }
+ if (bootstrapMethod.getProto().getParameters().size() != 3) {
+ return report("Unsupported dynamic constant (unsupported signature)", context);
+ }
+ if (bootstrapMethod.getProto().getParameters().get(0) != factory.lookupType) {
+ return report(
+ "Unsupported dynamic constant (unexpected type of first argument to bootstrap method",
+ context);
+ }
+ if (bootstrapMethod.getProto().getParameters().get(1) != factory.stringType) {
+ return report(
+ "Unsupported dynamic constant (unexpected type of second argument to bootstrap method",
+ context);
+ }
+ if (bootstrapMethod.getProto().getParameters().get(2) != factory.classType) {
+ return report(
+ "Unsupported dynamic constant (unexpected type of third argument to bootstrap method",
+ context);
+ }
+ return DesugarDescription.builder()
+ .setDesugarRewrite(
+ (freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context1,
+ methodProcessingContext,
+ dexItemFactory) ->
+ desugarConstDynamicInstruction(
+ instruction.asConstDynamic(),
+ freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context1,
+ methodProcessingContext))
+ .build();
+ }
+
+ @Override
+ public void scan(ProgramMethod method, CfInstructionDesugaringEventConsumer eventConsumer) {
+ for (CfInstruction instruction :
+ method.getDefinition().getCode().asCfCode().getInstructions()) {
+ computeDesugaring(instruction, method).scan();
+ }
+ }
+
@Override
public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
- return instruction.isConstDynamic();
+ return computeDesugaring(instruction, context).needsDesugaring();
}
@Override
@@ -47,17 +132,15 @@
ProgramMethod context,
MethodProcessingContext methodProcessingContext,
CfInstructionDesugaringCollection desugaringCollection,
- DexItemFactory dexItemFactory) {
- if (instruction.isConstDynamic()) {
- return desugarConstDynamicInstruction(
- instruction.asConstDynamic(),
- freshLocalProvider,
- localStackAllocator,
- eventConsumer,
- context,
- methodProcessingContext);
- }
- return null;
+ DexItemFactory factory) {
+ return computeDesugaring(instruction, context)
+ .desugarInstruction(
+ freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ factory);
}
private Collection<CfInstruction> desugarConstDynamicInstruction(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
index da8257c..43d76c7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
@@ -6,20 +6,22 @@
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import java.util.List;
import java.util.Objects;
public class ConstantDynamicReference {
private final DexString name;
private final DexType type;
private final DexMethodHandle bootstrapMethod;
- private final Object[] bootstrapMethodArguments;
+ private final List<DexValue> bootstrapMethodArguments;
public ConstantDynamicReference(
DexString name,
DexType type,
DexMethodHandle bootstrapMethod,
- Object[] bootstrapMethodArguments) {
- assert bootstrapMethodArguments.length == 0;
+ List<DexValue> bootstrapMethodArguments) {
+ assert bootstrapMethodArguments.isEmpty();
this.name = name;
this.type = type;
this.bootstrapMethod = bootstrapMethod;
@@ -38,7 +40,7 @@
return bootstrapMethod;
}
- public Object[] getBootstrapMethodArguments() {
+ public List<DexValue> getBootstrapMethodArguments() {
return bootstrapMethodArguments;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
index 257fd7d..33e5159 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
@@ -172,6 +172,37 @@
.CfUtilityMethodsForCodeOptimizationsTemplates_throwNoSuchMethodError(options, method);
}
+ public static UtilityMethodForCodeOptimizations synthesizeThrowRuntimeExceptionWithMessageMethod(
+ AppView<?> appView, MethodProcessingContext methodProcessingContext) {
+ InternalOptions options = appView.options();
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ DexProto proto =
+ dexItemFactory.createProto(dexItemFactory.runtimeExceptionType, dexItemFactory.stringType);
+ SyntheticItems syntheticItems = appView.getSyntheticItems();
+ ProgramMethod syntheticMethod =
+ syntheticItems.createMethod(
+ kinds -> kinds.THROW_RTE,
+ methodProcessingContext.createUniqueContext(),
+ appView,
+ builder ->
+ builder
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setClassFileVersion(CfVersion.V1_8)
+ .setApiLevelForDefinition(appView.computedMinApiLevel())
+ .setApiLevelForCode(appView.computedMinApiLevel())
+ .setCode(
+ method -> getThrowRuntimeExceptionWithMessageCodeTemplate(method, options))
+ .setProto(proto));
+ return new UtilityMethodForCodeOptimizations(syntheticMethod);
+ }
+
+ private static CfCode getThrowRuntimeExceptionWithMessageCodeTemplate(
+ DexMethod method, InternalOptions options) {
+ return CfUtilityMethodsForCodeOptimizations
+ .CfUtilityMethodsForCodeOptimizationsTemplates_throwRuntimeExceptionWithMessage(
+ options, method);
+ }
+
public static class UtilityMethodForCodeOptimizations {
private final ProgramMethod method;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
index 0cb8e39..d512ff4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
@@ -34,6 +34,7 @@
factory.createSynthesizedType("Ljava/lang/IllegalAccessError;");
factory.createSynthesizedType("Ljava/lang/IncompatibleClassChangeError;");
factory.createSynthesizedType("Ljava/lang/NoSuchMethodError;");
+ factory.createSynthesizedType("Ljava/lang/RuntimeException;");
}
public static CfCode
@@ -145,6 +146,34 @@
ImmutableList.of());
}
+ public static CfCode
+ CfUtilityMethodsForCodeOptimizationsTemplates_throwRuntimeExceptionWithMessage(
+ InternalOptions options, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 3,
+ 1,
+ ImmutableList.of(
+ label0,
+ new CfNew(options.itemFactory.createType("Ljava/lang/RuntimeException;")),
+ new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInvoke(
+ 183,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/RuntimeException;"),
+ options.itemFactory.createProto(
+ options.itemFactory.voidType, options.itemFactory.stringType),
+ options.itemFactory.createString("<init>")),
+ false),
+ new CfThrow(),
+ label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
public static CfCode CfUtilityMethodsForCodeOptimizationsTemplates_toStringIfNotNull(
InternalOptions options, DexMethod method) {
CfLabel label0 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 29ca8e4..631115f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -75,6 +75,7 @@
public final SyntheticKind THROW_IAE = generator.forSingleMethod("ThrowIAE");
public final SyntheticKind THROW_ICCE = generator.forSingleMethod("ThrowICCE");
public final SyntheticKind THROW_NSME = generator.forSingleMethod("ThrowNSME");
+ public final SyntheticKind THROW_RTE = generator.forSingleMethod("ThrowRTE");
public final SyntheticKind TWR_CLOSE_RESOURCE = generator.forSingleMethod("TwrCloseResource");
public final SyntheticKind SERVICE_LOADER = generator.forSingleMethod("ServiceLoad");
public final SyntheticKind OUTLINE = generator.forSingleMethod("Outline");
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 7759cc3..7d6d982 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -509,10 +509,8 @@
}
public boolean shouldKeepStackMapTable() {
- assert isCfDesugaring() || isRelocatorCompilation() || getProguardConfiguration() != null;
- return isCfDesugaring()
- || isRelocatorCompilation()
- || getProguardConfiguration().getKeepAttributes().stackMapTable;
+ assert isRelocatorCompilation() || getProguardConfiguration() != null;
+ return isRelocatorCompilation() || getProguardConfiguration().getKeepAttributes().stackMapTable;
}
public boolean shouldRerunEnqueuer() {
@@ -2070,28 +2068,76 @@
return CfVersion.V1_5;
}
- public boolean canUseInvokePolymorphicOnVarHandle() {
- return hasFeaturePresentFrom(AndroidApiLevel.P);
+ public static AndroidApiLevel invokePolymorphicOnMethodHandleApiLevel() {
+ return AndroidApiLevel.O;
}
- public boolean canUseInvokePolymorphic() {
- return hasFeaturePresentFrom(AndroidApiLevel.O);
+ public boolean canUseInvokePolymorphicOnMethodHandle() {
+ return hasFeaturePresentFrom(invokePolymorphicOnMethodHandleApiLevel());
+ }
+
+ public static AndroidApiLevel invokePolymorphicOnVarHandleApiLevel() {
+ return AndroidApiLevel.P;
+ }
+
+ public boolean canUseInvokePolymorphicOnVarHandle() {
+ return hasFeaturePresentFrom(invokePolymorphicOnMethodHandleApiLevel());
+ }
+
+ public static AndroidApiLevel constantMethodHandleApiLevel() {
+ return AndroidApiLevel.P;
}
public boolean canUseConstantMethodHandle() {
- return hasFeaturePresentFrom(AndroidApiLevel.P);
+ return hasFeaturePresentFrom(constantMethodHandleApiLevel());
+ }
+
+ public static AndroidApiLevel constantMethodTypeApiLevel() {
+ return AndroidApiLevel.P;
}
public boolean canUseConstantMethodType() {
- return hasFeaturePresentFrom(AndroidApiLevel.P);
+ return hasFeaturePresentFrom(constantMethodTypeApiLevel());
+ }
+
+ public static AndroidApiLevel invokeCustomApiLevel() {
+ return AndroidApiLevel.O;
}
public boolean canUseInvokeCustom() {
- return hasFeaturePresentFrom(AndroidApiLevel.O);
+ return hasFeaturePresentFrom(invokeCustomApiLevel());
+ }
+
+ public static AndroidApiLevel constantDynamicApiLevel() {
+ return null;
+ }
+
+ public boolean canUseConstantDynamic() {
+ return hasFeaturePresentFrom(constantDynamicApiLevel());
+ }
+
+ public static AndroidApiLevel defaultAndStaticInterfaceMethodsApiLevel() {
+ return AndroidApiLevel.N;
+ }
+
+ public static AndroidApiLevel defaultInterfaceMethodsApiLevel() {
+ return defaultAndStaticInterfaceMethodsApiLevel();
+ }
+
+ public static AndroidApiLevel staticInterfaceMethodsApiLevel() {
+ return defaultAndStaticInterfaceMethodsApiLevel();
}
public boolean canUseDefaultAndStaticInterfaceMethods() {
- return hasFeaturePresentFrom(AndroidApiLevel.N);
+ return hasFeaturePresentFrom(defaultInterfaceMethodsApiLevel());
+ }
+
+ public static AndroidApiLevel privateInterfaceMethodsApiLevel() {
+ return AndroidApiLevel.N;
+ }
+
+ public boolean canUsePrivateInterfaceMethods() {
+ return hasFeaturePresentFrom(privateInterfaceMethodsApiLevel());
}
public boolean canUseNestBasedAccess() {
@@ -2137,10 +2183,6 @@
throw new Unreachable();
}
- public boolean canUsePrivateInterfaceMethods() {
- return hasFeaturePresentFrom(AndroidApiLevel.N);
- }
-
// Debug entries may be dropped only if the source file content allows being omitted from
// stack traces, or if the VM will report the source file even with a null valued debug info.
public boolean allowDiscardingResidualDebugInfo() {
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 66e5fc8..dbfd4c6 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -106,10 +106,10 @@
command.add(outJar.toString());
command.add("-printmapping");
command.add(mapFile.toString());
- if (!enableTreeShaking) {
+ if (enableTreeShaking.isFalse()) {
command.add("-dontshrink");
}
- if (!enableMinification) {
+ if (enableMinification.isFalse()) {
command.add("-dontobfuscate");
}
ProcessBuilder pbuilder = new ProcessBuilder(command);
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
index 7c3a605..cd5e1ca 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
@@ -56,16 +56,4 @@
R8TestRunner test(String testName, String packageName, String mainClass) {
return new R8TestRunner(testName, packageName, mainClass);
}
-
- @Test
- public void varHandle() throws Throwable {
- test("varhandle", "varhandle", "VarHandleTests")
- .withBuilderTransformation(
- builder ->
- builder.addProguardConfiguration(
- ImmutableList.of("-dontwarn java.lang.invoke.VarHandle"), Origin.unknown()))
- .withMinApiLevel(AndroidApiLevel.P.getLevel())
- .withKeepAll()
- .run();
- }
}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index ebe8352..3bdfa4b 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -82,8 +82,12 @@
builder.addProguardConfiguration(keepRules, Origin.unknown());
}
builder.addMainDexRulesFiles(mainDexRulesFiles);
- builder.setDisableTreeShaking(!enableTreeShaking);
- builder.setDisableMinification(!enableMinification);
+ if (enableTreeShaking.isFalse()) {
+ builder.setDisableTreeShaking(true);
+ }
+ if (enableMinification.isFalse()) {
+ builder.setDisableMinification(true);
+ }
StringBuilder proguardMapBuilder = new StringBuilder();
if (createDefaultProguardMapConsumer) {
builder.setProguardMapConsumer(
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
index ed69ecf..caed567 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
@@ -228,14 +228,6 @@
.run();
}
- @Test
- public void invokeCustomErrorDueToMinSdk() throws Throwable {
- test("invokecustom-error-due-to-min-sdk", "invokecustom", "InvokeCustom")
- .withMinApiLevel(AndroidApiLevel.O.getLevel())
- .withKeepAll()
- .run();
- }
-
abstract RunExamplesAndroidPTest<B>.TestRunner<?> test(String testName, String packageName,
String mainClass);
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index a20964a..485ac54 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -120,42 +120,17 @@
abstract void build(Path inputFile, Path out) throws Throwable;
}
- private static List<String> minSdkErrorExpected =
- ImmutableList.of("varhandle-error-due-to-min-sdk");
+ private static List<String> minSdkErrorExpected = ImmutableList.of();
private static Map<DexVm.Version, List<String>> failsOn;
static {
ImmutableMap.Builder<DexVm.Version, List<String>> builder = ImmutableMap.builder();
builder
- .put(DexVm.Version.V4_0_4, ImmutableList.of(
- "native-private-interface-methods", // Dex version not supported
- "varhandle"
- ))
- .put(DexVm.Version.V4_4_4, ImmutableList.of(
- "native-private-interface-methods", // Dex version not supported
- "varhandle"
- ))
- .put(DexVm.Version.V5_1_1, ImmutableList.of(
- "native-private-interface-methods", // Dex version not supported
- "varhandle"
- ))
- .put(DexVm.Version.V6_0_1, ImmutableList.of(
- "native-private-interface-methods", // Dex version not supported
- "varhandle"
- ))
- .put(DexVm.Version.V7_0_0, ImmutableList.of(
- // Dex version not supported
- "varhandle"
- ))
- .put(DexVm.Version.V8_1_0, ImmutableList.of(
- // Dex version not supported
- "varhandle"
- ))
- .put(DexVm.Version.DEFAULT, ImmutableList.of(
- // TODO(b/72536415): Update runtime when the support will be ready
- "varhandle"
- ));
+ .put(DexVm.Version.V4_0_4, ImmutableList.of("native-private-interface-methods"))
+ .put(DexVm.Version.V4_4_4, ImmutableList.of("native-private-interface-methods"))
+ .put(DexVm.Version.V5_1_1, ImmutableList.of("native-private-interface-methods"))
+ .put(DexVm.Version.V6_0_1, ImmutableList.of("native-private-interface-methods"));
failsOn = builder.build();
}
@@ -168,20 +143,9 @@
+ "1\n2\n3\n4\n5\n6\n7\n8\n99\n"
+ "i1\ni2\ni3\ni4\ni5\ni6\ni7\ni8\ni99\n",
"native-private-interface-methods",
- "0: s>i>a\n"
- + "1: d>i>s>i>a\n"
- + "2: l>i>s>i>a\n"
- + "3: x>s\n"
- + "4: c>d>i>s>i>a\n",
+ "0: s>i>a\n" + "1: d>i>s>i>a\n" + "2: l>i>s>i>a\n" + "3: x>s\n" + "4: c>d>i>s>i>a\n",
"desugared-private-interface-methods",
- "0: s>i>a\n"
- + "1: d>i>s>i>a\n"
- + "2: l>i>s>i>a\n"
- + "3: x>s\n"
- + "4: c>d>i>s>i>a\n",
- "varhandle",
- "true\nfalse\n"
- );
+ "0: s>i>a\n" + "1: d>i>s>i>a\n" + "2: l>i>s>i>a\n" + "3: x>s\n" + "4: c>d>i>s>i>a\n");
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
@@ -241,22 +205,6 @@
}
@Test
- public void varHandle() throws Throwable {
- test("varhandle", "varhandle", "VarHandleTests")
- .withMinApiLevel(AndroidApiLevel.P.getLevel())
- .withKeepAll()
- .run();
- }
-
- @Test
- public void varHandleErrorDueToMinSdk() throws Throwable {
- test("varhandle-error-due-to-min-sdk", "varhandle", "VarHandleTests")
- .withMinApiLevel(AndroidApiLevel.O.getLevel())
- .withKeepAll()
- .run();
- }
-
- @Test
public void testTwrCloseResourceMethod() throws Throwable {
TestRunner<?> test = test("twr-close-resource", "twrcloseresource", "TwrCloseResourceTest");
test
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 4383be5..98f1627 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1818,6 +1818,10 @@
return AndroidApiLevel.O;
}
+ public static AndroidApiLevel apiLevelWithInvokePolymorphicSupport() {
+ return AndroidApiLevel.O;
+ }
+
public static AndroidApiLevel apiLevelWithConstMethodHandleSupport() {
return AndroidApiLevel.P;
}
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 52d84fc..2b29085 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
@@ -250,6 +251,12 @@
return self();
}
+ public T mapUnsupportedFeaturesToWarnings() {
+ return setDiagnosticsLevelModifier(
+ (level, diagnostic) ->
+ diagnostic instanceof UnsupportedFeatureDiagnostic ? DiagnosticsLevel.WARNING : level);
+ }
+
public T allowStdoutMessages() {
// Default ignored.
return self();
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 8b4c482..5d9d7f5 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -75,6 +75,8 @@
private ProgramConsumer programConsumer;
private MainDexClassesCollector mainDexClassesCollector;
private StringConsumer mainDexListConsumer;
+ // TODO(b/186010707): This could become implicit once min always be set when fixed.
+ private boolean noMinApiLevel = false;
private int minApiLevel = -1;
private boolean optimizeMultidexForLinearAlloc = false;
private Consumer<InternalOptions> optionsConsumer = DEFAULT_OPTIONS;
@@ -219,7 +221,7 @@
.addIfNotNull(mainDexListConsumer)
.build());
}
- if (backend.isDex() || !isTestShrinkerBuilder()) {
+ if (!noMinApiLevel && (backend.isDex() || !isTestShrinkerBuilder())) {
assert !builder.isMinApiLevelSet()
: "Don't set the API level directly through BaseCompilerCommand.Builder in tests";
// TODO(b/186010707): This will always be set when fixed.
@@ -364,6 +366,12 @@
return self();
}
+ public T setNoMinApi() {
+ this.minApiLevel = -1;
+ this.noMinApiLevel = true;
+ return self();
+ }
+
/** @deprecated use {@link #setMinApi(AndroidApiLevel)} instead. */
@Deprecated
public T setMinApi(TestRuntime runtime) {
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index bf47b6e..9072aa3 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.Sets;
import java.io.IOException;
@@ -29,8 +30,8 @@
T extends TestShrinkerBuilder<C, B, CR, RR, T>>
extends TestCompilerBuilder<C, B, CR, RR, T> {
- protected boolean enableTreeShaking = true;
- protected boolean enableMinification = true;
+ protected OptionalBool enableTreeShaking = OptionalBool.UNKNOWN;
+ protected OptionalBool enableMinification = OptionalBool.UNKNOWN;
private final Set<Class<? extends Annotation>> addedTestingAnnotations =
Sets.newIdentityHashSet();
@@ -67,7 +68,7 @@
}
public T treeShaking(boolean enable) {
- enableTreeShaking = enable;
+ enableTreeShaking = OptionalBool.of(enable);
return self();
}
@@ -76,7 +77,7 @@
}
public T minification(boolean enable) {
- enableMinification = enable;
+ enableMinification = OptionalBool.of(enable);
return self();
}
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java b/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java
deleted file mode 100644
index f400169..0000000
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java
+++ /dev/null
@@ -1,202 +0,0 @@
-// 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.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableMap.Builder;
-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;
-
-// 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 String cDesc = "com/android/tools/r8/cf/MethodHandleTest$C";
- private static final String eDesc = "com/android/tools/r8/cf/MethodHandleTest$E";
- private static final String fDesc = "com/android/tools/r8/cf/MethodHandleTest$F";
- private static final String iDesc = "com/android/tools/r8/cf/MethodHandleTest$I";
- 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 Type veType = Type.getMethodType(Type.VOID_TYPE, Type.getObjectType(eDesc));
- private static final Type fType = Type.getMethodType(Type.getObjectType(fDesc));
- 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)
- .put("veType", veType)
- .put("fType", fType)
- .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(InternalOptions.ASM_VERSION, 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/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
deleted file mode 100644
index cc9b61a..0000000
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
+++ /dev/null
@@ -1,209 +0,0 @@
-// 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 static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.ByteDataView;
-import com.android.tools.r8.ClassFileConsumer;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.NoVerticalClassMerging;
-import com.android.tools.r8.ProgramConsumer;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.R8Command.Builder;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.cf.MethodHandleTest.C;
-import com.android.tools.r8.cf.MethodHandleTest.I;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class MethodHandleTestRunner extends TestBase {
- static final Class<?> CLASS = MethodHandleTest.class;
-
- enum LookupType {
- DYNAMIC,
- CONSTANT,
- }
-
- enum MinifyMode {
- NONE,
- MINIFY,
- }
-
- private CompilationMode compilationMode;
- private LookupType lookupType;
- private ProcessResult runInput;
- private MinifyMode minifyMode;
-
- @Parameters(name = "{0}_{1}_{2}")
- public static List<String[]> data() {
- List<String[]> res = new ArrayList<>();
- for (LookupType lookupType : LookupType.values()) {
- for (MinifyMode minifyMode : MinifyMode.values()) {
- if (lookupType == LookupType.DYNAMIC && minifyMode == MinifyMode.MINIFY) {
- // Skip because we don't keep the members looked up dynamically.
- continue;
- }
- for (CompilationMode compilationMode : CompilationMode.values()) {
- res.add(new String[] {lookupType.name(), minifyMode.name(), compilationMode.name()});
- }
- }
- }
- return res;
- }
-
- public MethodHandleTestRunner(String lookupType, String minifyMode, String compilationMode) {
- this.lookupType = LookupType.valueOf(lookupType);
- this.minifyMode = MinifyMode.valueOf(minifyMode);
- this.compilationMode = CompilationMode.valueOf(compilationMode);
- }
-
- @Test
- public void test() throws Exception {
- runInput();
- runCf();
- // TODO(mathiasr): Once we include a P runtime, change this to "P and above".
- if (ToolHelper.getDexVm() == DexVm.ART_DEFAULT && ToolHelper.artSupported()) {
- runDex();
- }
- }
-
- private final Class<?>[] inputClasses = {
- MethodHandleTest.class,
- MethodHandleTest.C.class,
- MethodHandleTest.I.class,
- MethodHandleTest.Impl.class,
- MethodHandleTest.D.class,
- MethodHandleTest.E.class,
- MethodHandleTest.F.class,
- NoVerticalClassMerging.class
- };
-
- private void runInput() throws Exception {
- Path out = temp.getRoot().toPath().resolve("input.jar");
- ClassFileConsumer.ArchiveConsumer archiveConsumer = new ClassFileConsumer.ArchiveConsumer(out);
- for (Class<?> c : inputClasses) {
- archiveConsumer.accept(
- ByteDataView.of(getClassAsBytes(c)),
- DescriptorUtils.javaTypeToDescriptor(c.getName()),
- null);
- }
- archiveConsumer.finished(null);
- String expected = lookupType == LookupType.CONSTANT ? "error" : "exception";
- runInput = ToolHelper.runJava(out, CLASS.getName(), expected);
- if (runInput.exitCode != 0) {
- System.out.println(runInput);
- }
- assertEquals(0, runInput.exitCode);
- }
-
- private void runCf() throws Exception {
- Path outCf = temp.getRoot().toPath().resolve("cf.jar");
- build(new ClassFileConsumer.ArchiveConsumer(outCf));
- String expected = lookupType == LookupType.CONSTANT ? "error" : "exception";
- ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName(), expected);
- assertEquals(runCf.stderr, 0, runCf.exitCode);
- assertEquals(runInput.toString(), runCf.toString());
- // Ensure that we did not inline the const method handle
- ensureConstHandleNotInlined(outCf);
- }
-
- private void ensureConstHandleNotInlined(Path file) throws IOException, ExecutionException {
- CodeInspector inspector = new CodeInspector(file);
- MethodSubject subject = inspector.clazz(MethodHandleTest.D.class).method(
- "java.lang.MethodHandle", "vcviSpecialMethod");
- assertTrue(inspector.clazz(MethodHandleTest.D.class)
- .method("java.lang.invoke.MethodHandle", "vcviSpecialMethod").isPresent());
- }
-
- private void runDex() throws Exception {
- Path outDex = temp.getRoot().toPath().resolve("dex.zip");
- build(new DexIndexedConsumer.ArchiveConsumer(outDex));
- String expected = lookupType == LookupType.CONSTANT ? "pass" : "exception";
- ProcessResult runDex =
- ToolHelper.runArtRaw(
- outDex.toString(),
- CLASS.getCanonicalName(),
- cmd -> cmd.appendProgramArgument(expected));
- // Only compare stdout and exitCode since dex2oat prints to stderr.
- if (runInput.exitCode != runDex.exitCode) {
- System.out.println(runDex.stderr);
- }
- assertEquals(runInput.exitCode, runDex.exitCode);
- assertEquals(runInput.stdout, runDex.stdout);
- }
-
- private void build(ProgramConsumer programConsumer) throws Exception {
- // MethodHandle.invoke() only supported from Android O
- // ConstMethodHandle only supported from Android P
- Builder builder =
- R8Command.builder()
- .setMode(compilationMode)
- .setProgramConsumer(programConsumer)
- .setDisableTreeShaking(true)
- .setDisableMinification(true);
- if (programConsumer instanceof ClassFileConsumer) {
- builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
- } else {
- AndroidApiLevel apiLevel = AndroidApiLevel.P;
- builder
- .setMinApiLevel(apiLevel.getLevel())
- .addLibraryFiles(ToolHelper.getAndroidJar(apiLevel));
- }
- for (Class<?> c : inputClasses) {
- byte[] classAsBytes = getClassAsBytes(c);
- builder.addClassProgramData(classAsBytes, Origin.unknown());
- }
- if (minifyMode == MinifyMode.MINIFY) {
- ToolHelper.allowTestProguardOptions(builder);
- builder.addProguardConfiguration(
- Arrays.asList(
- keepMainProguardConfiguration(MethodHandleTest.class),
- noVerticalClassMergingRule(),
- // Prevent the second argument of C.svic(), C.sjic(), I.sjic() and I.svic() from
- // being removed although they are never used unused. This is needed since these
- // methods are accessed reflectively.
- "-keep,allowobfuscation public class " + C.class.getTypeName() + " {",
- " static void svic(int, char);",
- " static long sjic(int, char);",
- "}",
- "-keep,allowobfuscation public interface " + I.class.getTypeName() + " {",
- " static long sjic(int, char);",
- " static void svic(int, char);",
- "}"),
- Origin.unknown());
- }
- ToolHelper.runR8(builder.build());
- }
-
- private byte[] getClassAsBytes(Class<?> clazz) throws Exception {
- if (lookupType == LookupType.CONSTANT) {
- if (clazz == MethodHandleTest.D.class) {
- return MethodHandleDump.dumpD();
- } else if (clazz == MethodHandleTest.class) {
- return MethodHandleDump.transform(ToolHelper.getClassAsBytes(clazz));
- }
- }
- return ToolHelper.getClassAsBytes(clazz);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/InvokeMethodHandleRuntimeErrorTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/InvokeMethodHandleRuntimeErrorTest.java
new file mode 100644
index 0000000..889fd91
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/InvokeMethodHandleRuntimeErrorTest.java
@@ -0,0 +1,135 @@
+// Copyright (c) 2022, 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.methodhandles;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static com.android.tools.r8.references.Reference.classFromClass;
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.invoke.MethodHandle;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Test that unrepresentable MethodHandle invokes are replaced by throwing instructions. See
+ * b/174733673.
+ */
+@RunWith(Parameterized.class)
+public class InvokeMethodHandleRuntimeErrorTest extends TestBase {
+
+ private static final String EXPECTED = StringUtils.lines("I.target");
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeMethodHandleRuntimeErrorTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private boolean hasCompileSupport() {
+ return parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithConstMethodHandleSupport());
+ }
+
+ @Test
+ public void testReference() throws Throwable {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(Main.class, I.class, Super.class)
+ .addProgramClassFileData(getInvokeCustomTransform())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testD8() throws Throwable {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addProgramClasses(Main.class, I.class, Super.class)
+ .addProgramClassFileData(getInvokeCustomTransform())
+ .setMinApi(parameters.getApiLevel())
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ if (hasCompileSupport()) {
+ diagnostics.assertNoMessages();
+ } else {
+ diagnostics
+ .assertAllWarningsMatch(diagnosticType(UnsupportedFeatureDiagnostic.class))
+ .assertOnlyWarnings();
+ }
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ hasCompileSupport(),
+ r -> r.assertSuccessWithOutput(EXPECTED),
+ r ->
+ r.assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertStderrMatches(containsString("const-method-handle")));
+ }
+
+ private static byte[] getInvokeCustomTransform() throws Throwable {
+ ClassReference symbolicHolder = classFromClass(InvokeCustom.class);
+ MethodReference method = methodFromMethod(InvokeCustom.class.getMethod("target"));
+ return transformer(InvokeCustom.class)
+ .transformMethodInsnInMethod(
+ "test",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ // Replace null argument by a const method handle.
+ visitor.visitInsn(Opcodes.POP);
+ visitor.visitLdcInsn(
+ new Handle(
+ Opcodes.H_INVOKEVIRTUAL,
+ symbolicHolder.getBinaryName(),
+ method.getMethodName(),
+ method.getMethodDescriptor(),
+ false));
+ visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ })
+ .transform();
+ }
+
+ interface I {
+ default void target() {
+ System.out.println("I.target");
+ }
+ }
+
+ static class Super implements I {}
+
+ static class InvokeCustom extends Super {
+
+ public static void doInvoke(MethodHandle handle) throws Throwable {
+ handle.invoke(new InvokeCustom());
+ }
+
+ public static void test() throws Throwable {
+ doInvoke(null /* will be const method handle */);
+ }
+ }
+
+ static class Main {
+
+ public static void main(String[] args) throws Throwable {
+ InvokeCustom.test();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleDump.java b/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleDump.java
new file mode 100644
index 0000000..4881866
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleDump.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2022, 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.methodhandles;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.google.common.collect.ImmutableMap;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+// 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 String cDesc = TestBase.binaryName(MethodHandleTest.C.class);
+ private static final String eDesc = TestBase.binaryName(MethodHandleTest.E.class);
+ private static final String fDesc = TestBase.binaryName(MethodHandleTest.F.class);
+ private static final String iDesc = TestBase.binaryName(MethodHandleTest.I.class);
+ 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 Type veType = Type.getMethodType(Type.VOID_TYPE, Type.getObjectType(eDesc));
+ private static final Type fType = Type.getMethodType(Type.getObjectType(fDesc));
+ 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();
+
+ public static byte[] getTransformedClass() throws Exception {
+ ImmutableMap<String, Type> types =
+ ImmutableMap.<String, Type>builder()
+ .put("viType", viType)
+ .put("jiType", jiType)
+ .put("vicType", vicType)
+ .put("jicType", jicType)
+ .put("veType", veType)
+ .put("fType", fType)
+ .build();
+
+ ImmutableMap<String, Handle> methods =
+ ImmutableMap.<String, Handle>builder()
+ .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(
+ "constructorMethod", new Handle(H_NEWINVOKESPECIAL, cDesc, "<init>", viDesc, false))
+ .build();
+
+ return ClassFileTransformer.create(MethodHandleTest.class)
+ .addClassTransformer(
+ new ClassTransformer() {
+ @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 = super.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 = super.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);
+ }
+ }
+ })
+ .transform();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTest.java
similarity index 72%
rename from src/test/java/com/android/tools/r8/cf/MethodHandleTest.java
rename to src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTest.java
index 690dd6b..497e19f 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTest.java
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2022, 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;
+package com.android.tools.r8.cf.methodhandles;
import com.android.tools.r8.NoVerticalClassMerging;
import java.lang.invoke.MethodHandle;
@@ -16,13 +16,6 @@
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);
}
@@ -60,20 +53,6 @@
}
}
- 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 static class E {
// Class that is only mentioned in parameter list of LDC(MethodType)-instruction.
}
@@ -84,7 +63,6 @@
@NoVerticalClassMerging
public interface I {
- int ii = 42;
static void svi(int i) {
System.out.println("svi " + i);
@@ -128,7 +106,6 @@
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 {
@@ -148,33 +125,6 @@
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);
System.out.println(veType().parameterType(0).getName().lastIndexOf('.'));
System.out.println(fType().returnType().getName().lastIndexOf('.'));
@@ -189,12 +139,6 @@
}
}
- 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);
}
@@ -347,54 +291,6 @@
}
}
- 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());
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTestRunner.java
new file mode 100644
index 0000000..1812854
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTestRunner.java
@@ -0,0 +1,207 @@
+// Copyright (c) 2022, 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.methodhandles;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.cf.methodhandles.MethodHandleTest.C;
+import com.android.tools.r8.cf.methodhandles.MethodHandleTest.E;
+import com.android.tools.r8.cf.methodhandles.MethodHandleTest.F;
+import com.android.tools.r8.cf.methodhandles.MethodHandleTest.I;
+import com.android.tools.r8.cf.methodhandles.MethodHandleTest.Impl;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MethodHandleTestRunner extends TestBase {
+ static final Class<?> CLASS = MethodHandleTest.class;
+
+ enum LookupType {
+ DYNAMIC,
+ CONSTANT,
+ }
+
+ enum MinifyMode {
+ NONE,
+ MINIFY,
+ }
+
+ private String getExpected() {
+ return StringUtils.lines(
+ "C 42", "svi 1", "sji 2", "svic 3", "sjic 4", "vvi 5", "vji 6", "vvic 7", "vjic 8", "svi 9",
+ "sji 10", "svic 11", "sjic 12", "dvi 13", "dji 14", "dvic 15", "djic 16", "C 21", "37",
+ "37");
+ }
+
+ private final TestParameters parameters;
+ private final LookupType lookupType;
+ private final MinifyMode minifyMode;
+
+ @Parameters(name = "{0}, lookup:{1}, minify:{2}")
+ public static List<Object[]> data() {
+ List<Object[]> res = new ArrayList<>();
+ for (TestParameters params :
+ TestParameters.builder()
+ .withCfRuntimes()
+ .withDexRuntimesStartingFromExcluding(Version.V7_0_0)
+ // .withApiLevelsStartingAtIncluding(AndroidApiLevel.P)
+ .withAllApiLevels()
+ .build()) {
+ for (LookupType lookupType : LookupType.values()) {
+ for (MinifyMode minifyMode : MinifyMode.values()) {
+ if (lookupType == LookupType.DYNAMIC && minifyMode == MinifyMode.MINIFY) {
+ // Skip because we don't keep the members looked up dynamically.
+ continue;
+ }
+ res.add(new Object[] {params, lookupType.name(), minifyMode.name()});
+ }
+ }
+ }
+ return res;
+ }
+
+ public MethodHandleTestRunner(TestParameters parameters, String lookupType, String minifyMode) {
+ this.parameters = parameters;
+ this.lookupType = LookupType.valueOf(lookupType);
+ this.minifyMode = MinifyMode.valueOf(minifyMode);
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(getInputClasses())
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), CLASS.getName())
+ .assertSuccessWithOutput(getExpected());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime() && minifyMode == MinifyMode.NONE);
+ testForD8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(getInputClasses())
+ .addProgramClassFileData(getTransformedClasses())
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(this::checkDiagnostics)
+ .run(parameters.getRuntime(), CLASS.getName())
+ .apply(this::checkResult);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ R8TestBuilder<?> builder =
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(getInputClasses())
+ .addProgramClassFileData(getTransformedClasses())
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addNoVerticalClassMergingAnnotations();
+ if (minifyMode == MinifyMode.MINIFY) {
+ builder
+ .enableProguardTestOptions()
+ .addKeepMainRule(MethodHandleTest.class)
+ .addKeepRules(
+ // Prevent the second argument of C.svic(), C.sjic(), I.sjic() and I.svic() from
+ // being removed although they are never used unused. This is needed since these
+ // methods are accessed reflectively.
+ "-keep,allowobfuscation public class " + typeName(C.class) + " {",
+ " static void svic(int, char);",
+ " static long sjic(int, char);",
+ "}",
+ "-keep,allowobfuscation public interface " + typeName(I.class) + " {",
+ " static long sjic(int, char);",
+ " static void svic(int, char);",
+ "}");
+ // TODO(b/235810300): The compiler fails with assertion in AppInfoWithLiveness.
+ if (lookupType == LookupType.CONSTANT && hasConstMethodCompileSupport()) {
+ builder.allowDiagnosticMessages();
+ assertThrows(CompilationFailedException.class, builder::compile);
+ return;
+ }
+ } else {
+ builder.noTreeShaking();
+ builder.noMinification();
+ }
+ builder
+ .allowDiagnosticMessages()
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(this::checkDiagnostics)
+ .run(parameters.getRuntime(), CLASS.getCanonicalName())
+ .apply(this::checkResult);
+ }
+
+ private boolean hasConstMethodCompileSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithConstMethodHandleSupport());
+ }
+
+ private boolean hasInvokePolymorphicCompileSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithInvokePolymorphicSupport());
+ }
+
+ private void checkDiagnostics(TestDiagnosticMessages diagnostics) {
+ if ((lookupType == LookupType.DYNAMIC && !hasInvokePolymorphicCompileSupport())
+ || (lookupType == LookupType.CONSTANT && !hasConstMethodCompileSupport())) {
+ diagnostics
+ .assertAllWarningsMatch(diagnosticType(UnsupportedFeatureDiagnostic.class))
+ .assertOnlyWarnings();
+ } else {
+ diagnostics.assertNoMessages();
+ }
+ }
+
+ private void checkResult(TestRunResult<?> result) {
+ if (lookupType == LookupType.DYNAMIC && !hasInvokePolymorphicCompileSupport()) {
+ result
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertStderrMatches(containsString("invoke-polymorphic"));
+ return;
+ }
+ if (lookupType == LookupType.CONSTANT && !hasConstMethodCompileSupport()) {
+ result
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertStderrMatches(containsString("const-method-handle"));
+ return;
+ }
+ result.assertSuccessWithOutput(getExpected());
+ }
+
+ private List<Class<?>> getInputClasses() {
+ Builder<Class<?>> builder =
+ ImmutableList.<Class<?>>builder().add(C.class, I.class, Impl.class, E.class, F.class);
+ if (lookupType == LookupType.DYNAMIC) {
+ builder.add(MethodHandleTest.class);
+ }
+ return builder.build();
+ }
+
+ private List<byte[]> getTransformedClasses() throws Exception {
+ if (lookupType == LookupType.DYNAMIC) {
+ return ImmutableList.of();
+ }
+ return ImmutableList.of(MethodHandleDump.getTransformedClass());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/VarHandleTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/VarHandleTest.java
new file mode 100644
index 0000000..ccb901d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/VarHandleTest.java
@@ -0,0 +1,137 @@
+// Copyright (c) 2022, 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.methodhandles;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.errors.UnsupportedInvokePolymorphicMethodHandleDiagnostic;
+import com.android.tools.r8.examples.JavaExampleClassProxy;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Test for VarHandle (these are a refactoring of the old example test setup.) */
+@RunWith(Parameterized.class)
+public class VarHandleTest extends TestBase {
+
+ private static final String PKG = "varhandle";
+ private static final String EXAMPLE = "examplesJava9/" + PKG;
+ private final JavaExampleClassProxy MAIN =
+ new JavaExampleClassProxy(EXAMPLE, PKG + ".VarHandleTests");
+
+ private static final String EXPECTED = StringUtils.lines("true", "false");
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withCfRuntimesStartingFromIncluding(CfVm.JDK9)
+ .withDexRuntimes()
+ .withAllApiLevels()
+ .build();
+ }
+
+ public VarHandleTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private boolean hasInvokePolymorphicCompileSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithInvokePolymorphicSupport());
+ }
+
+ private boolean hasMethodHandlesClass() {
+ return parameters.getRuntime().maxSupportedApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O);
+ }
+
+ private boolean hasFindStaticVarHandleMethod() {
+ // API docs list this as present from T(33), but it was included from 28
+ return parameters.getRuntime().maxSupportedApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.P);
+ }
+
+ private boolean hasVarHandleInLibrary() {
+ return parameters.isDexRuntime()
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.T);
+ }
+
+ public List<Path> getProgramInputs() {
+ return ImmutableList.of(JavaExampleClassProxy.examplesJar(EXAMPLE));
+ }
+
+ @Test
+ public void testReference() throws Throwable {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramFiles(getProgramInputs())
+ .run(parameters.getRuntime(), MAIN.typeName())
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testD8() throws Throwable {
+ assumeTrue(parameters.isDexRuntime());
+ assumeFalse(
+ "TODO(b/204855476): The default VM throws unsupported. Ignore it and reconsider for 8.0.0",
+ parameters.isDexRuntimeVersion(Version.DEFAULT));
+ testForD8()
+ .addProgramFiles(getProgramInputs())
+ .setMinApi(parameters.getApiLevel())
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ if (hasInvokePolymorphicCompileSupport()) {
+ diagnostics.assertNoMessages();
+ } else {
+ diagnostics
+ .assertAllWarningsMatch(
+ diagnosticType(UnsupportedInvokePolymorphicMethodHandleDiagnostic.class))
+ .assertOnlyWarnings();
+ }
+ })
+ .run(parameters.getRuntime(), MAIN.typeName())
+ .applyIf(
+ !hasMethodHandlesClass(),
+ r ->
+ r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class)
+ .assertStderrMatches(containsString("java.lang.invoke.MethodHandles")),
+ !hasFindStaticVarHandleMethod(),
+ r ->
+ r.assertFailureWithErrorThatThrows(NoSuchMethodError.class)
+ .assertStderrMatches(containsString("findStaticVarHandle")),
+ !hasInvokePolymorphicCompileSupport(),
+ r ->
+ r.assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertStderrMatches(containsString("invoke-polymorphic")),
+ r -> r.assertSuccessWithOutput(EXPECTED));
+ }
+
+ @Test
+ public void testR8() throws Throwable {
+ // This just tests R8 on the targets where the program is fully supported.
+ assumeTrue(hasInvokePolymorphicCompileSupport() && hasFindStaticVarHandleMethod());
+ testForR8(parameters.getBackend())
+ .addProgramFiles(getProgramInputs())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepClassAndMembersRules(MAIN.typeName())
+ .applyIf(!hasVarHandleInLibrary(), b -> b.addDontWarn("java.lang.invoke.VarHandle"))
+ .run(parameters.getRuntime(), MAIN.typeName())
+ .assertSuccessWithOutput(EXPECTED);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/fields/C.java b/src/test/java/com/android/tools/r8/cf/methodhandles/fields/C.java
new file mode 100644
index 0000000..0ccb1e8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/fields/C.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2022, 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.methodhandles.fields;
+
+// This is a top-level class.
+// The use of handles will check generics on C and fail if it cannot find the outer class.
+public class C {
+
+ public int vi;
+ public static int si;
+}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/fields/ClassFieldMethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/fields/ClassFieldMethodHandleTest.java
new file mode 100644
index 0000000..062f22e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/fields/ClassFieldMethodHandleTest.java
@@ -0,0 +1,229 @@
+// Copyright (c) 2022, 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.methodhandles.fields;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.H_GETFIELD;
+import static org.objectweb.asm.Opcodes.H_GETSTATIC;
+import static org.objectweb.asm.Opcodes.H_PUTFIELD;
+import static org.objectweb.asm.Opcodes.H_PUTSTATIC;
+
+import com.android.tools.r8.DiagnosticsMatcher;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableMap;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class ClassFieldMethodHandleTest extends TestBase {
+
+ enum LookupType {
+ DYNAMIC,
+ CONSTANT,
+ }
+
+ private final TestParameters parameters;
+ private final LookupType lookupType;
+
+ @Parameters(name = "{0}, lookup:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ TestParameters.builder().withAllRuntimesAndApiLevels().build(), LookupType.values());
+ }
+
+ public ClassFieldMethodHandleTest(TestParameters parameters, LookupType lookupType) {
+ this.parameters = parameters;
+ this.lookupType = lookupType;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(C.class)
+ .addProgramClassFileData(getTransformedMain())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(getExpected());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addProgramClasses(C.class)
+ .addProgramClassFileData(getTransformedMain())
+ .setMinApi(parameters.getApiLevel())
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(this::checkDiagnostics)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkResult);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addKeepClassAndMembersRules(C.class, Main.class)
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addProgramClasses(C.class)
+ .addProgramClassFileData(getTransformedMain())
+ .setMinApi(parameters.getApiLevel())
+ .allowDiagnosticMessages()
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(this::checkDiagnostics)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkResult);
+ }
+
+ private boolean hasConstMethodCompileSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithConstMethodHandleSupport());
+ }
+
+ private boolean hasInvokePolymorphicCompileSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithInvokePolymorphicSupport());
+ }
+
+ private boolean hasMethodHandlesRuntimeSupport() {
+ return parameters.isCfRuntime()
+ || parameters
+ .asDexRuntime()
+ .maxSupportedApiLevel()
+ .isGreaterThanOrEqualTo(AndroidApiLevel.O);
+ }
+
+ private void checkDiagnostics(TestDiagnosticMessages diagnostics) {
+ if ((lookupType == LookupType.DYNAMIC && !hasInvokePolymorphicCompileSupport())
+ || lookupType == LookupType.CONSTANT && !hasConstMethodCompileSupport()) {
+ diagnostics
+ .assertAllWarningsMatch(
+ DiagnosticsMatcher.diagnosticType(UnsupportedFeatureDiagnostic.class))
+ .assertOnlyWarnings();
+ } else {
+ diagnostics.assertNoMessages();
+ }
+ }
+
+ private void checkResult(TestRunResult<?> result) {
+ if (lookupType == LookupType.DYNAMIC && hasInvokePolymorphicCompileSupport()) {
+ result.assertSuccessWithOutput(getExpected());
+ } else if (hasConstMethodCompileSupport()) {
+ result.assertSuccessWithOutput(getExpected());
+ } else if (lookupType == LookupType.DYNAMIC && !hasMethodHandlesRuntimeSupport()) {
+ result.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
+ } else {
+ result.assertFailureWithErrorThatMatches(
+ containsString(
+ lookupType == LookupType.DYNAMIC ? "invoke-polymorphic" : "const-method-handle"));
+ }
+ }
+
+ private String getExpected() {
+ return StringUtils.lines("AOK");
+ }
+
+ byte[] getTransformedMain() throws Exception {
+ return transformer(Main.class)
+ .addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access,
+ String name,
+ String descriptor,
+ String signature,
+ String[] exceptions) {
+ MethodVisitor mv =
+ super.visitMethod(access, name, descriptor, signature, exceptions);
+ if (lookupType == LookupType.CONSTANT && name.endsWith("Field")) {
+ String fieldName = name.startsWith("sci") ? "si" : "vi";
+ int type =
+ ImmutableMap.<String, Integer>builder()
+ .put("sciSetField", H_PUTSTATIC)
+ .put("sciGetField", H_GETSTATIC)
+ .put("vciSetField", H_PUTFIELD)
+ .put("vciGetField", H_GETFIELD)
+ .build()
+ .get(name);
+ mv.visitCode();
+ mv.visitLdcInsn(new Handle(type, binaryName(C.class), fieldName, "I", false));
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(-1, -1);
+ mv.visitEnd();
+ return null;
+ }
+ return mv;
+ }
+ })
+ .transform();
+ }
+
+ public static class Main {
+
+ public static MethodHandle vciSetField() {
+ try {
+ return MethodHandles.lookup().findSetter(C.class, "vi", 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 sciSetField() {
+ try {
+ return MethodHandles.lookup().findStaticSetter(C.class, "si", 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 void assertEquals(int x, int y) {
+ if (x != y) {
+ throw new AssertionError("failed!");
+ }
+ }
+
+ public static void main(String[] args) throws Throwable {
+ C c = new C();
+ vciSetField().invoke(c, 17);
+ assertEquals(17, (int) vciGetField().invoke(c));
+ sciSetField().invoke(18);
+ assertEquals(18, (int) sciGetField().invoke());
+ System.out.println("AOK");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/fields/InterfaceFieldMethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/fields/InterfaceFieldMethodHandleTest.java
new file mode 100644
index 0000000..3d62b40
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/fields/InterfaceFieldMethodHandleTest.java
@@ -0,0 +1,217 @@
+// Copyright (c) 2022, 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.methodhandles.fields;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.H_GETSTATIC;
+import static org.objectweb.asm.Opcodes.H_PUTSTATIC;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class InterfaceFieldMethodHandleTest extends TestBase {
+
+ enum LookupType {
+ DYNAMIC,
+ CONSTANT,
+ }
+
+ private final TestParameters parameters;
+ private final LookupType lookupType;
+
+ @Parameters(name = "{0}, lookup:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ TestParameters.builder()
+ // Runtimes without Handle APIs fail in various ways. Start testing beyond that point.
+ .withDexRuntimesStartingFromExcluding(Version.V7_0_0)
+ .withAllApiLevels()
+ .withCfRuntimes()
+ .build(),
+ LookupType.values());
+ }
+
+ public InterfaceFieldMethodHandleTest(TestParameters parameters, LookupType lookupType) {
+ this.parameters = parameters;
+ this.lookupType = lookupType;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(I.class)
+ .addProgramClassFileData(getTransformedMain())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(getExpected());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addProgramClasses(I.class)
+ .addProgramClassFileData(getTransformedMain())
+ .setMinApi(parameters.getApiLevel())
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(this::checkDiagnostics)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkResult);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addKeepClassAndMembersRules(I.class, Main.class)
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addProgramClasses(I.class)
+ .addProgramClassFileData(getTransformedMain())
+ .setMinApi(parameters.getApiLevel())
+ .allowDiagnosticMessages()
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(this::checkDiagnostics)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkResult);
+ }
+
+ private boolean hasConstMethodCompileSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithConstMethodHandleSupport());
+ }
+
+ private boolean hasInvokePolymorphicCompileSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithInvokePolymorphicSupport());
+ }
+
+ private void checkDiagnostics(TestDiagnosticMessages diagnostics) {
+ if ((lookupType == LookupType.DYNAMIC && !hasInvokePolymorphicCompileSupport())
+ || lookupType == LookupType.CONSTANT && !hasConstMethodCompileSupport()) {
+ diagnostics
+ .assertAllWarningsMatch(diagnosticType(UnsupportedFeatureDiagnostic.class))
+ .assertOnlyWarnings();
+ } else {
+ diagnostics.assertNoMessages();
+ }
+ }
+
+ private void checkResult(TestRunResult<?> result) {
+ if (parameters.isDexRuntimeVersion(Version.V13_0_0)
+ && lookupType == LookupType.CONSTANT
+ && hasConstMethodCompileSupport()) {
+ // TODO(b/235576668): VM 13 throws an escaping IAE outside the guarded range.
+ result
+ .assertFailureWithErrorThatThrows(IllegalAccessError.class)
+ .assertStderrMatches(containsString("Main.main"));
+ return;
+ }
+ if (lookupType == LookupType.DYNAMIC && hasInvokePolymorphicCompileSupport()) {
+ result.assertSuccessWithOutput(getExpected());
+ } else if (hasConstMethodCompileSupport()) {
+ result.assertSuccessWithOutput(getExpected());
+ } else {
+ result.assertFailureWithErrorThatMatches(
+ containsString(
+ lookupType == LookupType.DYNAMIC ? "invoke-polymorphic" : "const-method-handle"));
+ }
+ }
+
+ private String getExpected() {
+ if (lookupType == LookupType.CONSTANT && parameters.isDexRuntimeVersion(Version.V9_0_0)) {
+ // VM 9 will assign the value in the setter in contrast to RI.
+ return StringUtils.lines("42", "pass", "19");
+ }
+ return StringUtils.lines("42", lookupType == LookupType.DYNAMIC ? "exception" : "error", "42");
+ }
+
+ byte[] getTransformedMain() throws Exception {
+ return transformer(Main.class)
+ .addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access,
+ String name,
+ String descriptor,
+ String signature,
+ String[] exceptions) {
+ MethodVisitor mv =
+ super.visitMethod(access, name, descriptor, signature, exceptions);
+ if (lookupType == LookupType.CONSTANT && name.endsWith("Field")) {
+ int type = name.equals("iiSetField") ? H_PUTSTATIC : H_GETSTATIC;
+ mv.visitCode();
+ mv.visitLdcInsn(new Handle(type, binaryName(I.class), "ii", "I", true));
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(-1, -1);
+ mv.visitEnd();
+ return null;
+ }
+ return mv;
+ }
+ })
+ .transform();
+ }
+
+ public interface I {
+ int ii = 42;
+ }
+
+ public static class Main {
+
+ 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 void read() throws Throwable {
+ System.out.println(iiGetField().invoke());
+ }
+
+ public static void main(String[] args) throws Throwable {
+ read();
+ // Note: having the try-catch inlined here hits ART issue b/235576668.
+ try {
+ iiSetField().invoke(19);
+ System.out.println("pass");
+ } catch (IllegalAccessError e) {
+ System.out.println("error");
+ } catch (RuntimeException e) {
+ System.out.println("exception");
+ }
+ read();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/invokespecial/InvokeSpecialMethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/invokespecial/InvokeSpecialMethodHandleTest.java
new file mode 100644
index 0000000..459ce08
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/invokespecial/InvokeSpecialMethodHandleTest.java
@@ -0,0 +1,206 @@
+// Copyright (c) 2022, 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.methodhandles.invokespecial;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.H_INVOKESPECIAL;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class InvokeSpecialMethodHandleTest extends TestBase {
+
+ enum LookupType {
+ DYNAMIC,
+ CONSTANT,
+ }
+
+ private final TestParameters parameters;
+ private final LookupType lookupType;
+
+ @Parameters(name = "{0}, lookup:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ TestParameters.builder()
+ // Runtimes without Handle APIs fail in various ways. Start testing beyond that point.
+ .withDexRuntimesStartingFromExcluding(Version.V7_0_0)
+ .withAllApiLevels()
+ .withCfRuntimes()
+ .build(),
+ LookupType.values());
+ }
+
+ public InvokeSpecialMethodHandleTest(TestParameters parameters, LookupType lookupType) {
+ this.parameters = parameters;
+ this.lookupType = lookupType;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(C.class, Main.class)
+ .addProgramClassFileData(getTransformedD())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(getExpected());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addProgramClasses(C.class, Main.class)
+ .addProgramClassFileData(getTransformedD())
+ .setMinApi(parameters.getApiLevel())
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(this::checkDiagnostics)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkResult);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addKeepClassAndMembersRules(C.class, D.class, Main.class)
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addProgramClasses(C.class, Main.class)
+ .addProgramClassFileData(getTransformedD())
+ .setMinApi(parameters.getApiLevel())
+ .allowDiagnosticMessages()
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(this::checkDiagnostics)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkResult);
+ }
+
+ private boolean hasConstMethodCompileSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithConstMethodHandleSupport());
+ }
+
+ private boolean hasInvokePolymorphicCompileSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithInvokePolymorphicSupport());
+ }
+
+ private void checkDiagnostics(TestDiagnosticMessages diagnostics) {
+ if ((lookupType == LookupType.DYNAMIC && !hasInvokePolymorphicCompileSupport())
+ || (lookupType == LookupType.CONSTANT && !hasConstMethodCompileSupport())) {
+ diagnostics
+ .assertAllWarningsMatch(diagnosticType(UnsupportedFeatureDiagnostic.class))
+ .assertOnlyWarnings();
+ } else {
+ diagnostics.assertNoMessages();
+ }
+ }
+
+ private void checkResult(TestRunResult<?> result) {
+ if (lookupType == LookupType.DYNAMIC && hasInvokePolymorphicCompileSupport()) {
+ result.assertSuccessWithOutput(getExpected());
+ } else if (lookupType == LookupType.CONSTANT && hasConstMethodCompileSupport()) {
+ if (parameters.isDexRuntimeVersion(Version.V9_0_0)) {
+ // VM 9 incorrectly prints out the overridden method despite the direct target.
+ result.assertSuccessWithOutput("");
+ } else if (parameters.isDexRuntimeVersion(Version.V13_0_0)) {
+ // TODO(b/235807678): Subsequent ART VMs incorrectly throw IAE.
+ result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
+ } else if (parameters.isDexRuntime()
+ && parameters.asDexRuntime().getVersion().isNewerThan(Version.V9_0_0)) {
+ // VMs between 9 and 13 segfault.
+ result.assertFailureWithErrorThatMatches(containsString("HandleUnexpectedSignal"));
+ } else {
+ result.assertSuccessWithOutput(getExpected());
+ }
+ } else {
+ result.assertFailureWithErrorThatMatches(
+ containsString(
+ lookupType == LookupType.DYNAMIC ? "invoke-polymorphic" : "const-method-handle"));
+ }
+ }
+
+ private String getExpected() {
+ return StringUtils.lines("vvi 20");
+ }
+
+ byte[] getTransformedD() throws Exception {
+ return transformer(D.class)
+ .addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access,
+ String name,
+ String descriptor,
+ String signature,
+ String[] exceptions) {
+ MethodVisitor mv =
+ super.visitMethod(access, name, descriptor, signature, exceptions);
+ if (lookupType == LookupType.CONSTANT && name.equals("vcviSpecialMethod")) {
+ mv.visitCode();
+ mv.visitLdcInsn(
+ new Handle(H_INVOKESPECIAL, binaryName(C.class), "vvi", "(I)V", false));
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(-1, -1);
+ mv.visitEnd();
+ return null;
+ }
+ return mv;
+ }
+ })
+ .transform();
+ }
+
+ public static class C {
+
+ public void vvi(int i) {
+ System.out.println("vvi " + i);
+ }
+ }
+
+ public static class D extends C {
+
+ public void vvi(int i) {
+ // Overridden to output nothing.
+ }
+
+ public static MethodHandle vcviSpecialMethod() {
+ try {
+ return MethodHandles.lookup()
+ .findSpecial(C.class, "vvi", MethodType.methodType(void.class, int.class), D.class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) throws Throwable {
+ MethodHandle methodHandle = D.vcviSpecialMethod();
+ methodHandle.invoke(new D(), 20);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/code/invokedynamic/InvokeCustomRuntimeErrorTest.java b/src/test/java/com/android/tools/r8/code/invokedynamic/InvokeCustomRuntimeErrorTest.java
new file mode 100644
index 0000000..cf34d5d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/code/invokedynamic/InvokeCustomRuntimeErrorTest.java
@@ -0,0 +1,198 @@
+// Copyright (c) 2022, 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.invokedynamic;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.UnsupportedInvokeCustomDiagnostic;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Test that unrepresentable invoke-dynamic instructions are replaced by throwing instructions. See
+ * b/174733673.
+ */
+@RunWith(Parameterized.class)
+public class InvokeCustomRuntimeErrorTest extends TestBase {
+
+ private static final String EXPECTED = StringUtils.lines("A::foo");
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeCustomRuntimeErrorTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private boolean hasCompileSupport() {
+ return parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithInvokeCustomSupport());
+ }
+
+ @Test
+ public void testReference() throws Throwable {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(I.class, A.class)
+ .addProgramClassFileData(getTransformedTestClass())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testD8CfNoDesugaring() throws Throwable {
+ assumeTrue(parameters.isCfRuntime());
+ // Explicitly test that no-desugaring will maintain a passthrough of the CF code.
+ testForD8(parameters.getBackend())
+ .addProgramClasses(I.class, A.class)
+ .addProgramClassFileData(getTransformedTestClass())
+ .setNoMinApi()
+ .disableDesugaring()
+ .compile()
+ .assertNoMessages()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testD8DexNoDesugaring() throws Throwable {
+ assumeTrue(parameters.isDexRuntime() && parameters.getApiLevel().equals(AndroidApiLevel.B));
+ // Explicitly test that no-desugaring will still strip instructions.
+ testForD8(parameters.getBackend())
+ .addProgramClasses(I.class, A.class)
+ .addProgramClassFileData(getTransformedTestClass())
+ .setMinApi(parameters.getApiLevel())
+ .disableDesugaring()
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics
+ .assertAllWarningsMatch(diagnosticType(UnsupportedInvokeCustomDiagnostic.class))
+ .assertOnlyWarnings())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertStderrMatches(containsString("invoke-dynamic"));
+ }
+
+ @Test
+ public void testD8() throws Throwable {
+ // For CF compilations we desugar to API level B, thus it should always fail.
+ AndroidApiLevel minApi =
+ parameters.isDexRuntime() ? parameters.getApiLevel() : AndroidApiLevel.B;
+ boolean expectedSuccess = parameters.isDexRuntime() && hasCompileSupport();
+ testForD8(parameters.getBackend())
+ .addProgramClasses(I.class, A.class)
+ .addProgramClassFileData(getTransformedTestClass())
+ .setMinApi(minApi)
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ if (expectedSuccess) {
+ diagnostics.assertNoMessages();
+ } else {
+ diagnostics
+ .assertAllWarningsMatch(diagnosticType(UnsupportedInvokeCustomDiagnostic.class))
+ .assertOnlyWarnings();
+ }
+ })
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ expectedSuccess,
+ r -> r.assertSuccessWithOutput(EXPECTED),
+ r ->
+ r.assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertStderrMatches(containsString("invoke-dynamic")));
+ }
+
+ private byte[] getTransformedTestClass() throws Exception {
+ ClassReference aClass = Reference.classFromClass(A.class);
+ MethodReference iFoo = Reference.methodFromMethod(I.class.getDeclaredMethod("foo"));
+ MethodReference bsm =
+ Reference.methodFromMethod(
+ TestClass.class.getDeclaredMethod(
+ "bsmCreateCallSite",
+ Lookup.class,
+ String.class,
+ MethodType.class,
+ MethodHandle.class));
+ return transformer(TestClass.class)
+ .transformMethodInsnInMethod(
+ "main",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ if (name.equals("replaced")) {
+ visitor.visitInvokeDynamicInsn(
+ iFoo.getMethodName(),
+ "(" + aClass.getDescriptor() + ")V",
+ new Handle(
+ Opcodes.H_INVOKESTATIC,
+ bsm.getHolderClass().getBinaryName(),
+ bsm.getMethodName(),
+ bsm.getMethodDescriptor(),
+ false),
+ new Handle(
+ Opcodes.H_INVOKEVIRTUAL,
+ aClass.getBinaryName(),
+ iFoo.getMethodName(),
+ iFoo.getMethodDescriptor(),
+ false));
+ } else {
+ visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .transform();
+ }
+
+ public interface I {
+ void foo();
+ }
+
+ public static class A implements I {
+
+ @Override
+ public void foo() {
+ System.out.println("A::foo");
+ }
+ }
+
+ static class TestClass {
+
+ public static CallSite bsmCreateCallSite(
+ MethodHandles.Lookup caller, String name, MethodType type, MethodHandle handle)
+ throws Throwable {
+ return new ConstantCallSite(handle);
+ }
+
+ public static void replaced(Object o) {
+ throw new RuntimeException("unreachable!");
+ }
+
+ public static void main(String[] args) {
+ replaced(new A());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/diagnostics/UnsupportedFeaturesDiagnosticApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/diagnostics/UnsupportedFeaturesDiagnosticApiTest.java
index d6eeb25..efcbe63 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/diagnostics/UnsupportedFeaturesDiagnosticApiTest.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/diagnostics/UnsupportedFeaturesDiagnosticApiTest.java
@@ -10,10 +10,19 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.compilerapi.CompilerApiTest;
import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
-import com.android.tools.r8.errors.InvokeCustomDiagnostic;
+import com.android.tools.r8.errors.UnsupportedConstDynamicDiagnostic;
+import com.android.tools.r8.errors.UnsupportedConstMethodHandleDiagnostic;
+import com.android.tools.r8.errors.UnsupportedConstMethodTypeDiagnostic;
+import com.android.tools.r8.errors.UnsupportedDefaultInterfaceMethodDiagnostic;
import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
+import com.android.tools.r8.errors.UnsupportedInvokeCustomDiagnostic;
+import com.android.tools.r8.errors.UnsupportedInvokePolymorphicMethodHandleDiagnostic;
+import com.android.tools.r8.errors.UnsupportedInvokePolymorphicVarHandleDiagnostic;
+import com.android.tools.r8.errors.UnsupportedPrivateInterfaceMethodDiagnostic;
+import com.android.tools.r8.errors.UnsupportedStaticInterfaceMethodDiagnostic;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
+import java.util.function.BiFunction;
import java.util.function.Consumer;
import org.junit.Test;
@@ -30,12 +39,29 @@
@Test
public void test() throws Exception {
+ check(UnsupportedDefaultInterfaceMethodDiagnostic::new, "default-interface-method", 24);
+ check(UnsupportedStaticInterfaceMethodDiagnostic::new, "static-interface-method", 24);
+ check(UnsupportedPrivateInterfaceMethodDiagnostic::new, "private-interface-method", 24);
+ check(UnsupportedInvokeCustomDiagnostic::new, "invoke-custom", 26);
+ check(
+ UnsupportedInvokePolymorphicMethodHandleDiagnostic::new,
+ "invoke-polymorphic-method-handle",
+ 26);
+ check(
+ UnsupportedInvokePolymorphicVarHandleDiagnostic::new, "invoke-polymorphic-var-handle", 28);
+ check(UnsupportedConstMethodHandleDiagnostic::new, "const-method-handle", 28);
+ check(UnsupportedConstMethodTypeDiagnostic::new, "const-method-type", 28);
+ check(UnsupportedConstDynamicDiagnostic::new, "const-dynamic", -1);
+ }
+
+ public void check(
+ BiFunction<Origin, Position, UnsupportedFeatureDiagnostic> makeFn,
+ String descriptor,
+ int level) {
ApiTest test = new ApiTest(ApiTest.PARAMETERS);
test.run(
- new InvokeCustomDiagnostic(Origin.unknown(), Position.UNKNOWN),
- result -> {
- assertEquals("invoke-custom @ 26", result);
- });
+ makeFn.apply(Origin.unknown(), Position.UNKNOWN),
+ result -> assertEquals(descriptor + " @ " + level, result));
}
public static class ApiTest extends CompilerApiTest {
@@ -49,7 +75,16 @@
new DiagnosticsHandler() {
@Override
public void warning(Diagnostic warning) {
- if (warning instanceof UnsupportedFeatureDiagnostic) {
+ if (warning instanceof UnsupportedConstDynamicDiagnostic
+ || warning instanceof UnsupportedConstMethodHandleDiagnostic
+ || warning instanceof UnsupportedConstMethodTypeDiagnostic
+ || warning instanceof UnsupportedDefaultInterfaceMethodDiagnostic
+ || warning instanceof UnsupportedInvokeCustomDiagnostic
+ || warning instanceof UnsupportedInvokePolymorphicMethodHandleDiagnostic
+ || warning instanceof UnsupportedInvokePolymorphicVarHandleDiagnostic
+ || warning instanceof UnsupportedPrivateInterfaceMethodDiagnostic
+ || warning instanceof UnsupportedStaticInterfaceMethodDiagnostic
+ || warning instanceof UnsupportedFeatureDiagnostic) {
UnsupportedFeatureDiagnostic unsupportedFeature =
(UnsupportedFeatureDiagnostic) warning;
String featureDescriptor = unsupportedFeature.getFeatureDescriptor();
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java
index 48cbeaf..8c1d8f1 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java
@@ -4,16 +4,23 @@
package com.android.tools.r8.desugar.constantdynamic;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
import static org.objectweb.asm.Opcodes.H_INVOKESTATIC;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DiagnosticsLevel;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.errors.ConstantDynamicDesugarDiagnostic;
+import com.android.tools.r8.errors.UnsupportedConstDynamicDiagnostic;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
import com.android.tools.r8.utils.AndroidApiLevel;
import java.io.IOException;
import org.junit.Test;
@@ -37,7 +44,7 @@
@Test
public void testReference() throws Exception {
assumeTrue(parameters.isCfRuntime());
- assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11));
+ assumeTrue(parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK11));
assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
testForJvm()
@@ -46,33 +53,70 @@
.assertSuccessWithOutputLines("null");
}
- @Test(expected = CompilationFailedException.class)
+ @Test
public void testD8() throws Exception {
assumeTrue(parameters.isDexRuntime());
testForD8()
.addProgramClassFileData(getTransformedMain())
.setMinApi(parameters.getApiLevel())
+ .setDiagnosticsLevelModifier(
+ (level, diagnostic) ->
+ (diagnostic instanceof UnsupportedFeatureDiagnostic
+ || diagnostic instanceof ConstantDynamicDesugarDiagnostic)
+ ? DiagnosticsLevel.WARNING
+ : level)
.compileWithExpectedDiagnostics(
diagnostics ->
- diagnostics.assertErrorMessageThatMatches(
- containsString(
- "Unsupported dynamic constant (runtime provided bootstrap method)")));
+ diagnostics.assertWarningsMatch(
+ diagnosticType(UnsupportedConstDynamicDiagnostic.class),
+ allOf(
+ diagnosticType(ConstantDynamicDesugarDiagnostic.class),
+ diagnosticMessage(
+ containsString(
+ "Unsupported dynamic constant (runtime provided bootstrap"
+ + " method)")))))
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertFailureWithErrorThatMatches(containsString("const-dynamic"));
}
+ // TODO(b/198142625): Support const-dynamic in IR CF/CF.
@Test(expected = CompilationFailedException.class)
- public void testR8() throws Exception {
- assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+ public void testR8Cf() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ assumeTrue(parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK11));
+ assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
testForR8(parameters.getBackend())
.addProgramClassFileData(getTransformedMain())
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(Main.class)
+ .compile();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedMain())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .allowDiagnosticWarningMessages()
+ .mapUnsupportedFeaturesToWarnings()
.compileWithExpectedDiagnostics(
- diagnostics ->
- diagnostics.assertErrorMessageThatMatches(
- containsString(
- "Unsupported dynamic constant (runtime provided bootstrap method)")));
+ diagnostics -> {
+ if (parameters.isDexRuntime()) {
+ diagnostics.assertWarningsMatch(
+ allOf(
+ diagnosticType(UnsupportedFeatureDiagnostic.class),
+ diagnosticMessage(containsString("const-dynamic"))));
+ }
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertFailureWithErrorThatMatches(containsString("const-dynamic"));
}
private byte[] getTransformedMain() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java
index 1ae1ccf..87f3382 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java
@@ -4,19 +4,23 @@
package com.android.tools.r8.desugar.constantdynamic;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
-import static com.android.tools.r8.OriginMatcher.hasParent;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThrows;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DiagnosticsLevel;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.errors.ConstantDynamicDesugarDiagnostic;
+import com.android.tools.r8.errors.UnsupportedConstDynamicDiagnostic;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
@@ -49,7 +53,7 @@
@Test
public void testReference() throws Exception {
assumeTrue(parameters.isCfRuntime());
- assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11));
+ assumeTrue(parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK11));
assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
testForJvm()
@@ -59,65 +63,130 @@
}
@Test
+ public void testD8CfNoDesugar() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ assumeTrue(parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK11));
+ assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setNoMinApi()
+ .disableDesugaring()
+ .compile()
+ .assertNoMessages()
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
public void testD8Cf() throws Exception {
- assertThrows(
- CompilationFailedException.class,
- () ->
- testForD8(Backend.CF)
- .addProgramClassFileData(getTransformedClasses())
- .setMinApi(parameters.getApiLevel())
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertOnlyErrors();
- diagnostics.assertErrorsMatch(
- diagnosticMessage(
- containsString("Unsupported dynamic constant (different owner)")));
- }));
+ assumeTrue(parameters.isCfRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .setDiagnosticsLevelModifier(
+ (level, diagnostic) ->
+ (diagnostic instanceof ConstantDynamicDesugarDiagnostic
+ || diagnostic instanceof UnsupportedFeatureDiagnostic)
+ ? DiagnosticsLevel.WARNING
+ : level)
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics
+ .assertAllWarningsMatch(
+ anyOf(
+ allOf(
+ diagnosticType(UnsupportedConstDynamicDiagnostic.class),
+ diagnosticMessage(containsString("const-dynamic"))),
+ allOf(
+ diagnosticType(ConstantDynamicDesugarDiagnostic.class),
+ diagnosticMessage(
+ containsString(
+ "Unsupported dynamic constant (different owner)")))))
+ .assertOnlyWarnings())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertFailureWithErrorThatMatches(containsString("const-dynamic"));
}
@Test
public void testD8() throws Exception {
assumeTrue(parameters.isDexRuntime());
-
- assertThrows(
- CompilationFailedException.class,
- () ->
- testForD8(parameters.getBackend())
- .addProgramClassFileData(getTransformedClasses())
- .setMinApi(parameters.getApiLevel())
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertOnlyErrors();
- diagnostics.assertErrorsMatch(
- allOf(
- diagnosticMessage(
- containsString("Unsupported dynamic constant (different owner)")),
- diagnosticOrigin(hasParent(Origin.unknown()))));
- }));
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .setDiagnosticsLevelModifier(
+ (level, diagnostic) ->
+ (diagnostic instanceof ConstantDynamicDesugarDiagnostic
+ || diagnostic instanceof UnsupportedFeatureDiagnostic)
+ ? DiagnosticsLevel.WARNING
+ : level)
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyWarnings();
+ diagnostics.assertAllWarningsMatch(
+ anyOf(
+ allOf(
+ diagnosticType(UnsupportedConstDynamicDiagnostic.class),
+ diagnosticMessage(containsString("const-dynamic"))),
+ allOf(
+ diagnosticType(ConstantDynamicDesugarDiagnostic.class),
+ diagnosticMessage(
+ containsString("Unsupported dynamic constant (different owner)")))));
+ })
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertFailureWithErrorThatMatches(containsString("const-dynamic"));
}
@Test
- public void testR8() throws Exception {
- assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
-
+ public void testR8Cf() {
+ assumeTrue(parameters.isCfRuntime() && parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
assertThrows(
CompilationFailedException.class,
() ->
testForR8(parameters.getBackend())
.addProgramClassFileData(getTransformedClasses())
- .setMinApi(parameters.getApiLevel())
.addKeepMainRule(MAIN_CLASS)
.compileWithExpectedDiagnostics(
diagnostics -> {
diagnostics.assertOnlyErrors();
diagnostics.assertErrorsMatch(
- allOf(
- diagnosticMessage(
- containsString("Unsupported dynamic constant (different owner)")),
- diagnosticOrigin(hasParent(Origin.unknown()))));
+ diagnosticMessage(
+ containsString("Unsupported dynamic constant (not desugaring)")));
}));
}
+ @Test
+ public void testR8Dex() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_CLASS)
+ .allowDiagnosticMessages()
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics
+ .assertAllWarningsMatch(
+ anyOf(
+ allOf(
+ diagnosticType(UnsupportedConstDynamicDiagnostic.class),
+ diagnosticMessage(containsString("const-dynamic"))),
+ allOf(
+ diagnosticType(ConstantDynamicDesugarDiagnostic.class),
+ diagnosticMessage(
+ containsString(
+ "Unsupported dynamic constant (different owner)")))))
+ .assertOnlyWarnings();
+ })
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertFailureWithErrorThatMatches(containsString("const-dynamic"));
+ }
+
private Collection<byte[]> getTransformedClasses() throws IOException {
return ImmutableList.of(
transformer(A.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java b/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
index b8ad8d9..2bc9233 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
@@ -29,4 +29,8 @@
public static NoSuchMethodError throwNoSuchMethodError() {
throw new NoSuchMethodError();
}
+
+ public static RuntimeException throwRuntimeExceptionWithMessage(String message) {
+ throw new RuntimeException(message);
+ }
}