Desugaring of CONSTANT_Dynamic to support JaCoCo
This desugaring supports the use of CONSTANT_Dynamic found
in JaCoCo version 0.8.4+ when instrumenting class files
with version 55 or higher.
Initial implementation use <clinit> for synchronizing constant
creation.
Bug: 178172809
Change-Id: I2ec90f9848d1323c5af83a6f3c19066f08b5836d
diff --git a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
index fdec52c..ab9165b 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.cf.code.CfCheckCast;
import com.android.tools.r8.cf.code.CfCmp;
import com.android.tools.r8.cf.code.CfConstClass;
+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;
@@ -388,6 +389,12 @@
}
@Override
+ public void print(CfConstDynamic constDynamic) {
+ // TODO(b/198143561): Support CfConstDynamic.
+ throw new Unimplemented(constDynamic.getClass().getSimpleName());
+ }
+
+ @Override
public void print(CfReturnVoid ret) {
printNewInstruction("CfReturnVoid");
}
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index c6ba520..480c9cb 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.cf.code.CfCheckCast;
import com.android.tools.r8.cf.code.CfCmp;
import com.android.tools.r8.cf.code.CfConstClass;
+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;
@@ -326,6 +327,12 @@
appendType(constClass.getType());
}
+ public void print(CfConstDynamic constDynamic) {
+ indent();
+ builder.append("ldc <dynamic> ");
+ appendType(constDynamic.getType());
+ }
+
public void print(CfInitClass initClass) {
indent();
builder.append("initclass ");
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
new file mode 100644
index 0000000..e6e1f0d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
@@ -0,0 +1,232 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.cf.code;
+
+import static com.android.tools.r8.graph.UseRegistry.MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+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.GraphLens;
+import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.JarApplicationReader;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicReference;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.structural.CompareToVisitor;
+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 {
+
+ private final ConstantDynamicReference reference;
+
+ public CfConstDynamic(
+ DexString name,
+ DexType type,
+ DexMethodHandle bootstrapMethod,
+ Object[] bootstrapMethodArguments) {
+ assert name != null;
+ assert type != null;
+ assert bootstrapMethod != null;
+ assert bootstrapMethodArguments != null;
+ assert bootstrapMethodArguments.length == 0;
+
+ reference = new ConstantDynamicReference(name, type, bootstrapMethod, bootstrapMethodArguments);
+ }
+
+ @Override
+ public CfConstDynamic asConstDynamic() {
+ return this;
+ }
+
+ @Override
+ public boolean isConstDynamic() {
+ return true;
+ }
+
+ public ConstantDynamicReference getReference() {
+ return reference;
+ }
+
+ public DexString getName() {
+ return reference.getName();
+ }
+
+ public DexMethodHandle getBootstrapMethod() {
+ return reference.getBootstrapMethod();
+ }
+
+ public Object[] getBootstrapMethodArguments() {
+ return reference.getBootstrapMethodArguments();
+ }
+
+ public static CfConstDynamic fromAsmConstantDynamic(
+ 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");
+ }
+ return new CfConstDynamic(
+ application.getString(constantName),
+ application.getTypeFromDescriptor(constantDescriptor),
+ bootstrapMethodHandle,
+ new Object[] {});
+ }
+
+ @Override
+ public int getCompareToId() {
+ return CfCompareHelper.CONST_DYNAMIC_COMPARE_ID;
+ }
+
+ @Override
+ public int internalAcceptCompareTo(
+ CfInstruction other, CompareToVisitor visitor, CfCompareHelper helper) {
+ int diff = getName().acceptCompareTo(((CfConstDynamic) other).getName(), visitor);
+ if (diff != 0) {
+ return diff;
+ }
+ diff = getType().acceptCompareTo(((CfConstDynamic) other).getType(), visitor);
+ if (diff != 0) {
+ return diff;
+ }
+ return getBootstrapMethod()
+ .acceptCompareTo(((CfConstDynamic) other).getBootstrapMethod(), visitor);
+ }
+
+ @Override
+ public CfTypeInstruction asTypeInstruction() {
+ return this;
+ }
+
+ @Override
+ public boolean isTypeInstruction() {
+ return true;
+ }
+
+ @Override
+ public DexType getType() {
+ return reference.getType();
+ }
+
+ @Override
+ public CfInstruction withType(DexType newType) {
+ throw new Unimplemented();
+ }
+
+ @Override
+ public void write(
+ AppView<?> appView,
+ ProgramMethod context,
+ DexItemFactory dexItemFactory,
+ GraphLens graphLens,
+ InitClassLens initClassLens,
+ 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)");
+ }
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
+
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
+
+ @Override
+ void internalRegisterUse(
+ UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+ registry.registerTypeReference(reference.getType());
+ registry.registerMethodHandle(
+ reference.getBootstrapMethod(), NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
+ assert reference.getBootstrapMethodArguments().length == 0;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ throw new CompilationError("Unsupported dynamic constant (not desugaring)");
+ }
+
+ @Override
+ public ConstraintWithTarget inliningConstraint(
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public void evaluate(
+ CfFrameVerificationHelper frameBuilder,
+ DexType context,
+ DexType returnType,
+ DexItemFactory factory,
+ InitClassLens initClassLens) {
+ // ... →
+ // ..., value
+ frameBuilder.push(factory.classType);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 6067cc3..188c575 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -118,6 +118,14 @@
return false;
}
+ public CfConstDynamic asConstDynamic() {
+ return null;
+ }
+
+ public boolean isConstDynamic() {
+ return false;
+ }
+
public CfFieldInstruction asFieldInstruction() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java b/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java
index 1108c72..d3c3351 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java
@@ -25,6 +25,7 @@
public static final int CONST_NUMBER_COMPARE_ID;
public static final int CONST_METHOD_TYPE_COMPARE_ID;
public static final int CONST_METHOD_HANDLE_COMPARE_ID;
+ public static final int CONST_DYNAMIC_COMPARE_ID;
public static final int FRAME_COMPARE_ID;
public static final int INIT_CLASS_COMPARE_ID;
public static final int LABEL_COMPARE_ID;
@@ -38,6 +39,7 @@
CONST_NUMBER_COMPARE_ID = ++lastId;
CONST_METHOD_TYPE_COMPARE_ID = ++lastId;
CONST_METHOD_HANDLE_COMPARE_ID = ++lastId;
+ CONST_DYNAMIC_COMPARE_ID = ++lastId;
FRAME_COMPARE_ID = ++lastId;
INIT_CLASS_COMPARE_ID = ++lastId;
LABEL_COMPARE_ID = ++lastId;
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 174dca5..c554ca5 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.cf.code.CfCheckCast;
import com.android.tools.r8.cf.code.CfCmp;
import com.android.tools.r8.cf.code.CfConstClass;
+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;
@@ -887,7 +888,9 @@
new CfConstMethodHandle(
DexMethodHandle.fromAsmHandle((Handle) cst, application, method.holder)));
} else if (cst instanceof ConstantDynamic) {
- throw new CompilationError("Unsupported dynamic constant: " + cst.toString());
+ instructions.add(
+ CfConstDynamic.fromAsmConstantDynamic(
+ (ConstantDynamic) cst, application, method.holder));
} else {
throw new CompilationError("Unsupported constant: " + cst.toString());
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index c6fd133..2493dd3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -14,6 +14,8 @@
import com.android.tools.r8.ir.conversion.ClassConverterResult;
import com.android.tools.r8.ir.conversion.D8MethodProcessor;
import com.android.tools.r8.ir.desugar.backports.BackportedMethodDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass;
+import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterInstructionEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPIConverterEventConsumer;
import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialBridgeInfo;
@@ -45,6 +47,7 @@
implements BackportedMethodDesugaringEventConsumer,
InvokeSpecialToSelfDesugaringEventConsumer,
LambdaDesugaringEventConsumer,
+ ConstantDynamicDesugaringEventConsumer,
NestBasedAccessDesugaringEventConsumer,
RecordInstructionDesugaringEventConsumer,
TwrCloseResourceDesugaringEventConsumer,
@@ -61,12 +64,14 @@
public static R8CfInstructionDesugaringEventConsumer createForR8(
AppView<? extends AppInfoWithClassHierarchy> appView,
BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer,
+ BiConsumer<ConstantDynamicClass, ProgramMethod> constantDynamicClassConsumer,
BiConsumer<ProgramMethod, ProgramMethod> twrCloseResourceMethodConsumer,
SyntheticAdditions additions,
BiConsumer<ProgramMethod, ProgramMethod> companionMethodConsumer) {
return new R8CfInstructionDesugaringEventConsumer(
appView,
lambdaClassConsumer,
+ constantDynamicClassConsumer,
twrCloseResourceMethodConsumer,
additions,
companionMethodConsumer);
@@ -141,6 +146,12 @@
}
@Override
+ public void acceptConstantDynamicClass(
+ ConstantDynamicClass constantDynamicClass, ProgramMethod context) {
+ assert false;
+ }
+
+ @Override
public void acceptNestFieldGetBridge(ProgramField target, ProgramMethod bridge) {
assert false;
}
@@ -175,6 +186,7 @@
private final Map<DexReference, InvokeSpecialBridgeInfo> pendingInvokeSpecialBridges =
new LinkedHashMap<>();
private final List<LambdaClass> synthesizedLambdaClasses = new ArrayList<>();
+ private final List<ConstantDynamicClass> synthesizedConstantDynamicClasses = new ArrayList<>();
private D8CfInstructionDesugaringEventConsumer(D8MethodProcessor methodProcessor) {
this.methodProcessor = methodProcessor;
@@ -226,6 +238,14 @@
}
@Override
+ public void acceptConstantDynamicClass(
+ ConstantDynamicClass constantDynamicClass, ProgramMethod context) {
+ synchronized (synthesizedConstantDynamicClasses) {
+ synthesizedConstantDynamicClasses.add(constantDynamicClass);
+ }
+ }
+
+ @Override
public void acceptNestFieldGetBridge(ProgramField target, ProgramMethod bridge) {
assert false;
}
@@ -276,6 +296,7 @@
List<ProgramMethod> needsProcessing = new ArrayList<>();
finalizeInvokeSpecialDesugaring(appView, needsProcessing::add);
finalizeLambdaDesugaring(classConverterResultBuilder, needsProcessing::add);
+ finalizeConstantDynamicDesugaring(needsProcessing::add);
return needsProcessing;
}
@@ -318,6 +339,13 @@
synthesizedLambdaClasses.clear();
}
+ private void finalizeConstantDynamicDesugaring(Consumer<ProgramMethod> needsProcessing) {
+ for (ConstantDynamicClass constantDynamicClass : synthesizedConstantDynamicClasses) {
+ constantDynamicClass.getConstantDynamicProgramClass().forEachProgramMethod(needsProcessing);
+ }
+ synthesizedLambdaClasses.clear();
+ }
+
public boolean verifyNothingToFinalize() {
assert pendingInvokeSpecialBridges.isEmpty();
assert synthesizedLambdaClasses.isEmpty();
@@ -330,26 +358,31 @@
private final AppView<? extends AppInfoWithClassHierarchy> appView;
- // TODO(b/180091213): Remove these two consumers when synthesizing contexts are accessible from
+ // TODO(b/180091213): Remove these three consumers when synthesizing contexts are accessible
+ // from
// synthetic items.
private final BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer;
+ private final BiConsumer<ConstantDynamicClass, ProgramMethod> constantDynamicClassConsumer;
private final BiConsumer<ProgramMethod, ProgramMethod> twrCloseResourceMethodConsumer;
private final SyntheticAdditions additions;
private final Map<LambdaClass, ProgramMethod> synthesizedLambdaClasses =
new IdentityHashMap<>();
private final List<InvokeSpecialBridgeInfo> pendingInvokeSpecialBridges = new ArrayList<>();
+ private final List<ConstantDynamicClass> synthesizedConstantDynamicClasses = new ArrayList<>();
private final BiConsumer<ProgramMethod, ProgramMethod> onCompanionMethodCallback;
public R8CfInstructionDesugaringEventConsumer(
AppView<? extends AppInfoWithClassHierarchy> appView,
BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer,
+ BiConsumer<ConstantDynamicClass, ProgramMethod> constantDynamicClassConsumer,
BiConsumer<ProgramMethod, ProgramMethod> twrCloseResourceMethodConsumer,
SyntheticAdditions additions,
BiConsumer<ProgramMethod, ProgramMethod> onCompanionMethodCallback) {
this.appView = appView;
this.lambdaClassConsumer = lambdaClassConsumer;
+ this.constantDynamicClassConsumer = constantDynamicClassConsumer;
this.twrCloseResourceMethodConsumer = twrCloseResourceMethodConsumer;
this.additions = additions;
this.onCompanionMethodCallback = onCompanionMethodCallback;
@@ -430,6 +463,17 @@
}
@Override
+ public void acceptConstantDynamicClass(
+ ConstantDynamicClass constantDynamicClass, ProgramMethod context) {
+ synchronized (synthesizedConstantDynamicClasses) {
+ synthesizedConstantDynamicClasses.add(constantDynamicClass);
+ }
+ // TODO(b/180091213): Remove the recording of the synthesizing context when this is accessible
+ // from synthetic items.
+ constantDynamicClassConsumer.accept(constantDynamicClass, context);
+ }
+
+ @Override
public void acceptNestFieldGetBridge(ProgramField target, ProgramMethod bridge) {
assert 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 dd15950..bd9805d 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
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicInstructionDesugaring;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
@@ -95,6 +96,7 @@
desugarings.add(desugaredLibraryAPIConverter);
}
desugarings.add(new LambdaInstructionDesugaring(appView));
+ desugarings.add(new ConstantDynamicInstructionDesugaring(appView));
desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
desugarings.add(new InvokeToPrivateRewriter());
desugarings.add(new StringConcatInstructionDesugaring(appView));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
new file mode 100644
index 0000000..0743b32
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
@@ -0,0 +1,221 @@
+// Copyright (c) 2021, 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.constantdynamic;
+
+import static org.objectweb.asm.Opcodes.GETSTATIC;
+import static org.objectweb.asm.Opcodes.INVOKESTATIC;
+import static org.objectweb.asm.Opcodes.PUTSTATIC;
+
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstDynamic;
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+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.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue.DexValueNull;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ConstantDynamicClass {
+ public static final String CONDY_CONST_FIELD_NAME = "CONST";
+
+ final AppView<?> appView;
+ final ConstantDynamicInstructionDesugaring desugaring;
+ private final DexType accessedFrom;
+ public ConstantDynamicReference reference;
+ public final DexField constantValueField;
+ final DexMethod classConstructor;
+ final DexMethod getConstMethod;
+
+ // Considered final but is set after due to circularity in allocation.
+ private DexProgramClass clazz = null;
+
+ public ConstantDynamicClass(
+ SyntheticProgramClassBuilder builder,
+ AppView<?> appView,
+ ConstantDynamicInstructionDesugaring desugaring,
+ ProgramMethod accessedFrom,
+ CfConstDynamic constantDynamic) {
+ DexItemFactory factory = appView.dexItemFactory();
+ this.appView = appView;
+ this.desugaring = desugaring;
+ this.accessedFrom = accessedFrom.getHolderType();
+ this.reference = constantDynamic.getReference();
+ this.constantValueField =
+ factory.createField(
+ builder.getType(),
+ constantDynamic.getType(),
+ factory.createString(CONDY_CONST_FIELD_NAME));
+ this.classConstructor =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(factory.voidType),
+ factory.classConstructorMethodName);
+ this.getConstMethod =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(constantDynamic.getType()),
+ factory.createString("get"));
+
+ synthesizeConstantDynamicClass(builder);
+ }
+
+ /*
+ // TODO(b/178172809): Don't use <clinit> to synchronize constant creation.
+ // Simple one class per. constant using <clinit> to synchronize constant creation
+ // generated with a pattern like this:
+
+ class ExternalSyntheticXXX {
+ public static <constant type> CONST =
+ (<constant type>) bootstrapMethod(null, "constant name", <constant type>)
+
+ public static <constant type> get() {
+ return CONST;
+ }
+ }
+
+ */
+ private void synthesizeConstantDynamicClass(SyntheticProgramClassBuilder builder) {
+ synthesizeStaticFields(builder);
+ synthesizeDirectMethods(builder);
+ }
+
+ private void synthesizeStaticFields(SyntheticProgramClassBuilder builder) {
+ // Create static field for the constant.
+ boolean deprecated = false;
+ boolean d8R8Synthesized = true;
+ builder.setStaticFields(
+ ImmutableList.of(
+ new DexEncodedField(
+ this.constantValueField,
+ FieldAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PRIVATE
+ | Constants.ACC_FINAL
+ | Constants.ACC_SYNTHETIC
+ | Constants.ACC_STATIC),
+ FieldTypeSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ DexValueNull.NULL,
+ deprecated,
+ d8R8Synthesized,
+ // The api level is computed when tracing.
+ AndroidApiLevel.minApiLevelIfEnabledOrUnknown(appView))));
+ }
+
+ private void synthesizeDirectMethods(SyntheticProgramClassBuilder builder) {
+
+ List<DexEncodedMethod> methods = new ArrayList<>(2);
+ methods.add(
+ DexEncodedMethod.builder()
+ .setMethod(classConstructor)
+ .setAccessFlags(MethodAccessFlags.createForClassInitializer())
+ .setCode(generateClassInitializerCode(builder.getType()))
+ .setD8R8Synthesized()
+ .setApiLevelForDefinition(AndroidApiLevel.S)
+ .setApiLevelForCode(AndroidApiLevel.S)
+ .build());
+ methods.add(
+ DexEncodedMethod.builder()
+ .setMethod(getConstMethod)
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setCode(generateGetterCode(builder.getType()))
+ .setD8R8Synthesized()
+ .setApiLevelForDefinition(AndroidApiLevel.S)
+ .setApiLevelForCode(AndroidApiLevel.S)
+ .build());
+ builder.setDirectMethods(methods);
+ }
+
+ private CfCode generateClassInitializerCode(DexType holder) {
+ int maxStack = 3;
+ int maxLocals = 0;
+ ImmutableList<CfTryCatch> tryCatchRanges = ImmutableList.of();
+ ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
+ ImmutableList.Builder<CfInstruction> instructions = ImmutableList.builder();
+ invokeBootstrapMethod(reference, instructions);
+ instructions.add(new CfFieldInstruction(PUTSTATIC, constantValueField));
+ instructions.add(new CfReturnVoid());
+ return new CfCode(
+ holder, maxStack, maxLocals, instructions.build(), tryCatchRanges, localVariables);
+ }
+
+ private void invokeBootstrapMethod(
+ ConstantDynamicReference reference, ImmutableList.Builder<CfInstruction> instructions) {
+ assert reference.getBootstrapMethod().type.isInvokeStatic();
+
+ // TODO(b/178172809): Use MethodHandle.invokeWithArguments if supported.
+ DexMethodHandle bootstrapMethodHandle = reference.getBootstrapMethod();
+ DexMethod bootstrapMethodReference = bootstrapMethodHandle.asMethod();
+ MethodResolutionResult resolution =
+ appView
+ .appInfoForDesugaring()
+ .resolveMethod(bootstrapMethodReference, bootstrapMethodHandle.isInterface);
+ if (resolution.isFailedResolution()) {
+ // TODO(b/178172809): Generate code which throws ICCE.
+ }
+ SingleResolutionResult result = resolution.asSingleResolution();
+ assert result.getResolvedMethod().isStatic();
+ assert result.getResolvedHolder().isProgramClass();
+ instructions.add(new CfConstNull());
+ instructions.add(new CfConstString(reference.getName()));
+ instructions.add(new CfConstClass(reference.getType()));
+ instructions.add(new CfInvoke(INVOKESTATIC, bootstrapMethodReference, false));
+ instructions.add(new CfCheckCast(reference.getType()));
+
+ // Ensure that the bootstrap method is accessible from the generated class.
+ MethodAccessFlags flags = result.getResolvedMethod().getAccessFlags();
+ flags.unsetPrivate();
+ flags.setPublic();
+ }
+
+ private CfCode generateGetterCode(DexType holder) {
+ int maxStack = 1;
+ int maxLocals = 0;
+ ImmutableList<CfTryCatch> tryCatchRanges = ImmutableList.of();
+ ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
+ ImmutableList.Builder<CfInstruction> instructions = ImmutableList.builder();
+ instructions.add(new CfFieldInstruction(GETSTATIC, constantValueField));
+ instructions.add(new CfReturn(ValueType.OBJECT));
+ return new CfCode(
+ holder, maxStack, maxLocals, instructions.build(), tryCatchRanges, localVariables);
+ }
+
+ public final DexProgramClass getConstantDynamicProgramClass() {
+ assert clazz != null;
+ return clazz;
+ }
+
+ public void setClass(DexProgramClass clazz) {
+ assert this.clazz == null;
+ assert clazz != null;
+ this.clazz = clazz;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java
new file mode 100644
index 0000000..3c0efd7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2021, 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.constantdynamic;
+
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface ConstantDynamicDesugaringEventConsumer {
+
+ void acceptConstantDynamicClass(ConstantDynamicClass lambdaClass, ProgramMethod context);
+}
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
new file mode 100644
index 0000000..a01fdd2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
@@ -0,0 +1,129 @@
+// Copyright (c) 2021, 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.constantdynamic;
+
+import com.android.tools.r8.cf.code.CfConstDynamic;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+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.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.FreshLocalProvider;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.Box;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.objectweb.asm.Opcodes;
+
+public class ConstantDynamicInstructionDesugaring implements CfInstructionDesugaring {
+
+ private final AppView<?> appView;
+ private final Map<DexType, Map<ConstantDynamicReference, ConstantDynamicClass>>
+ dynamicConstantSyntheticsPerClass = new ConcurrentHashMap<>();
+
+ public ConstantDynamicInstructionDesugaring(AppView<?> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+ return instruction.isConstDynamic();
+ }
+
+ @Override
+ public Collection<CfInstruction> desugarInstruction(
+ CfInstruction instruction,
+ FreshLocalProvider freshLocalProvider,
+ LocalStackAllocator localStackAllocator,
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ ProgramMethod context,
+ MethodProcessingContext methodProcessingContext,
+ DexItemFactory dexItemFactory) {
+ if (instruction.isConstDynamic()) {
+ return desugarConstDynamicInstruction(
+ instruction.asConstDynamic(),
+ freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext);
+ }
+ return null;
+ }
+
+ private Collection<CfInstruction> desugarConstDynamicInstruction(
+ CfConstDynamic invoke,
+ FreshLocalProvider freshLocalProvider,
+ LocalStackAllocator localStackAllocator,
+ ConstantDynamicDesugaringEventConsumer eventConsumer,
+ ProgramMethod context,
+ MethodProcessingContext methodProcessingContext) {
+ ConstantDynamicClass constantDynamicClass =
+ ensureConstantDynamicClass(invoke, context, methodProcessingContext, eventConsumer);
+ return ImmutableList.of(
+ new CfInvoke(Opcodes.INVOKESTATIC, constantDynamicClass.getConstMethod, false));
+ }
+
+ // Creates a class corresponding to the constant dynamic symbolic reference and context.
+ // TODO(b/178172809): Move this ensure handling to the synthetic items handling and move to
+ // one class for dynamic constants for each class using dynamic constants.
+ private ConstantDynamicClass ensureConstantDynamicClass(
+ CfConstDynamic constantDynamic,
+ ProgramMethod context,
+ MethodProcessingContext methodProcessingContext,
+ ConstantDynamicDesugaringEventConsumer eventConsumer) {
+ Map<ConstantDynamicReference, ConstantDynamicClass> dynamicConstantSyntheticsForClass =
+ dynamicConstantSyntheticsPerClass.computeIfAbsent(
+ context.getHolderType(), (ignore) -> new HashMap<>());
+ ConstantDynamicClass result =
+ dynamicConstantSyntheticsForClass.get(constantDynamic.getReference());
+ if (result == null) {
+ synchronized (dynamicConstantSyntheticsForClass) {
+ result = dynamicConstantSyntheticsForClass.get(constantDynamic.getReference());
+ if (result == null) {
+ result =
+ createConstantDynamicClass(
+ constantDynamic, context, methodProcessingContext, eventConsumer);
+ dynamicConstantSyntheticsForClass.put(constantDynamic.getReference(), result);
+ }
+ }
+ }
+ return result;
+ }
+
+ // TODO(b/178172809): Change to use ensureFixedClass.
+ private ConstantDynamicClass createConstantDynamicClass(
+ CfConstDynamic constantDynamic,
+ ProgramMethod context,
+ MethodProcessingContext methodProcessingContext,
+ ConstantDynamicDesugaringEventConsumer eventConsumer) {
+ Box<ConstantDynamicClass> box = new Box<>();
+ DexProgramClass clazz =
+ appView
+ .getSyntheticItems()
+ .createClass(
+ SyntheticKind.CONST_DYNAMIC,
+ methodProcessingContext.createUniqueContext(),
+ appView,
+ builder ->
+ box.set(
+ new ConstantDynamicClass(
+ builder, appView, this, context, constantDynamic)));
+ // Immediately set the actual program class on the constant dynamic.
+ ConstantDynamicClass constantDynamicClass = box.get();
+ constantDynamicClass.setClass(clazz);
+ eventConsumer.acceptConstantDynamicClass(constantDynamicClass, context);
+ return constantDynamicClass;
+ }
+}
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
new file mode 100644
index 0000000..da8257c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2021, 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.constantdynamic;
+
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import java.util.Objects;
+
+public class ConstantDynamicReference {
+ private final DexString name;
+ private final DexType type;
+ private final DexMethodHandle bootstrapMethod;
+ private final Object[] bootstrapMethodArguments;
+
+ public ConstantDynamicReference(
+ DexString name,
+ DexType type,
+ DexMethodHandle bootstrapMethod,
+ Object[] bootstrapMethodArguments) {
+ assert bootstrapMethodArguments.length == 0;
+ this.name = name;
+ this.type = type;
+ this.bootstrapMethod = bootstrapMethod;
+ this.bootstrapMethodArguments = bootstrapMethodArguments;
+ }
+
+ public DexString getName() {
+ return name;
+ }
+
+ public DexType getType() {
+ return type;
+ }
+
+ public DexMethodHandle getBootstrapMethod() {
+ return bootstrapMethod;
+ }
+
+ public Object[] getBootstrapMethodArguments() {
+ return bootstrapMethodArguments;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (!(obj instanceof ConstantDynamicReference)) {
+ return false;
+ }
+ ConstantDynamicReference other = (ConstantDynamicReference) obj;
+ return Objects.equals(name, other.name)
+ && Objects.equals(type, other.type)
+ && Objects.equals(bootstrapMethod, other.bootstrapMethod);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, type, bootstrapMethod);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index a30b406..66e9136 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -100,6 +100,7 @@
import com.android.tools.r8.ir.desugar.LambdaClass;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.ir.desugar.ProgramAdditions;
+import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
import com.android.tools.r8.ir.desugar.itf.InterfaceProcessor;
@@ -3516,6 +3517,7 @@
CfInstructionDesugaringEventConsumer.createForR8(
appView,
this::recordLambdaSynthesizingContext,
+ this::recordConstantDynamicSynthesizingContext,
this::recordTwrCloseResourceMethodSynthesizingContext,
additions,
(method, companion) -> {
@@ -3566,6 +3568,13 @@
}
}
+ private void recordConstantDynamicSynthesizingContext(
+ ConstantDynamicClass constantDynamicClass, ProgramMethod context) {
+ synchronized (synthesizingContexts) {
+ synthesizingContexts.put(constantDynamicClass.getConstantDynamicProgramClass(), context);
+ }
+ }
+
private void recordTwrCloseResourceMethodSynthesizingContext(
ProgramMethod closeMethod, ProgramMethod context) {
synchronized (synthesizingContexts) {
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 61d74fd..0a05c0f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -39,6 +39,7 @@
HORIZONTAL_INIT_TYPE_ARGUMENT_1(SYNTHETIC_CLASS_SEPARATOR + "IA$1", 6, false, true),
HORIZONTAL_INIT_TYPE_ARGUMENT_2(SYNTHETIC_CLASS_SEPARATOR + "IA$2", 7, false, true),
HORIZONTAL_INIT_TYPE_ARGUMENT_3(SYNTHETIC_CLASS_SEPARATOR + "IA$3", 8, false, true),
+ CONST_DYNAMIC("$Condy", 30, false),
// Method synthetics.
ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD("CheckNotZero", 27, true),
RECORD_HELPER("Record", 9, true),
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/BasicConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/BasicConstantDynamicTest.java
index b0d261e..af9f383 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/BasicConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/BasicConstantDynamicTest.java
@@ -12,6 +12,7 @@
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
@@ -40,6 +41,7 @@
}
private static final String EXPECTED_OUTPUT = StringUtils.lines("true", "true");
+ private static final Class<?> MAIN_CLASS = A.class;
@Test
public void testReference() throws Exception {
@@ -49,49 +51,75 @@
testForJvm()
.addProgramClassFileData(getTransformedClasses())
- .run(parameters.getRuntime(), A.class)
+ .run(parameters.getRuntime(), MAIN_CLASS)
.assertSuccessWithOutput(EXPECTED_OUTPUT);
}
@Test
+ public void TestD8Cf() throws Exception {
+ testForDesugaring(parameters)
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .applyIf(
+ // When not desugaring the CF code requires JDK 11.
+ DesugarTestConfiguration::isNotDesugared,
+ r -> {
+ if (parameters.isCfRuntime()
+ && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+ r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+ } else {
+ r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+ }
+ })
+ .applyIf(
+ DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+ }
+
+ @Test
public void testD8() throws Exception {
assumeTrue(parameters.getRuntime().isDex());
- assertThrows(
- CompilationFailedException.class,
- () ->
- testForD8(parameters.getBackend())
- .addProgramClassFileData(getTransformedClasses())
- .setMinApi(parameters.getApiLevel())
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertOnlyErrors();
- diagnostics.assertErrorsMatch(
- allOf(
- diagnosticMessage(containsString("Unsupported dynamic constant")),
- diagnosticOrigin(hasParent(Origin.unknown()))));
- }));
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
}
@Test
public void testR8() throws Exception {
assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
- assertThrows(
- CompilationFailedException.class,
- () ->
- testForR8(parameters.getBackend())
- .addProgramClassFileData(getTransformedClasses())
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(A.class)
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertOnlyErrors();
- diagnostics.assertErrorsMatch(
- allOf(
- diagnosticMessage(containsString("Unsupported dynamic constant")),
- diagnosticOrigin(hasParent(Origin.unknown()))));
- }));
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_CLASS)
+ // TODO(b/198142613): There should not be a warnings on class references which are
+ // desugared away.
+ .applyIf(
+ parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+ b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup"))
+ // TODO(b/198142625): Support CONSTANT_Dynamic output for class files.
+ .applyIf(
+ parameters.isCfRuntime(),
+ r -> {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ r.compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ allOf(
+ diagnosticMessage(
+ containsString(
+ "Unsupported dynamic constant (not desugaring)")),
+ diagnosticOrigin(hasParent(Origin.unknown()))));
+ }));
+ },
+ r ->
+ r.run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT));
}
private byte[] getTransformedClasses() throws IOException {
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 9286463..48cbeaf 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
@@ -56,7 +56,8 @@
.compileWithExpectedDiagnostics(
diagnostics ->
diagnostics.assertErrorMessageThatMatches(
- containsString("Unsupported dynamic constant")));
+ containsString(
+ "Unsupported dynamic constant (runtime provided bootstrap method)")));
}
@Test(expected = CompilationFailedException.class)
@@ -70,7 +71,8 @@
.compileWithExpectedDiagnostics(
diagnostics ->
diagnostics.assertErrorMessageThatMatches(
- containsString("Unsupported dynamic constant")));
+ containsString(
+ "Unsupported dynamic constant (runtime provided bootstrap method)")));
}
private byte[] getTransformedMain() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/HierarchyConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/HierarchyConstantDynamicTest.java
index c206b32..edc3cab 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/HierarchyConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/HierarchyConstantDynamicTest.java
@@ -38,6 +38,7 @@
}
private static final String EXPECTED_OUTPUT = StringUtils.lines("true", "true", "true");
+ private static final Class<?> MAIN_CLASS = A.class;
@Test
public void testReference() throws Exception {
@@ -55,37 +56,45 @@
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(
- diagnosticMessage(containsString("Unsupported dynamic constant")));
- }));
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
}
@Test
public void testR8() throws Exception {
assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
- assertThrows(
- CompilationFailedException.class,
- () ->
- testForR8(parameters.getBackend())
- .addProgramClassFileData(getTransformedClasses())
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(A.class)
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertOnlyErrors();
- diagnostics.assertErrorsMatch(
- diagnosticMessage(containsString("Unsupported dynamic constant")));
- }));
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_CLASS)
+ // TODO(b/198142613): There should not be a warnings on class references which are
+ // desugared away.
+ .applyIf(
+ parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+ b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup"))
+ // TODO(b/198142625): Support CONSTANT_Dynamic output for class files.
+ .applyIf(
+ parameters.isCfRuntime(),
+ r -> {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ r.compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ diagnosticMessage(
+ containsString(
+ "Unsupported dynamic constant (not desugaring)")));
+ }));
+ },
+ r ->
+ r.run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT));
}
private Collection<byte[]> getTransformedClasses() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
index d1a4d35..86fad6d 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
@@ -3,20 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
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.utils.DescriptorUtils.JAVA_PACKAGE_SEPARATOR;
import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-import static org.hamcrest.CoreMatchers.allOf;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThrows;
import static org.junit.Assume.assumeTrue;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
@@ -24,7 +17,6 @@
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.utils.ArchiveResourceProvider;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ZipUtils;
@@ -45,8 +37,7 @@
@RunWith(Parameterized.class)
public class JacocoConstantDynamicTest extends TestBase {
- @Parameter(0)
- public TestParameters parameters;
+ @Parameter() public TestParameters parameters;
@Parameter(1)
public boolean useConstantDynamic;
@@ -140,25 +131,11 @@
assertFalse(Files.exists(agentOutput));
}
} else {
- assertThrows(
- CompilationFailedException.class,
- () -> {
- ArchiveResourceProvider provider =
- ArchiveResourceProvider.fromArchive(testClasses.getInstrumented(), true);
- testForD8(parameters.getBackend())
- .addProgramResourceProviders(provider)
- .addProgramFiles(ToolHelper.JACOCO_AGENT)
- .setMinApi(parameters.getApiLevel())
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- // Check that the error is reported as an error to the diagnostics handler.
- diagnostics.assertOnlyErrors();
- diagnostics.assertErrorsMatch(
- allOf(
- diagnosticMessage(containsString("Unsupported dynamic constant")),
- diagnosticOrigin(hasParent(provider.getOrigin()))));
- });
- });
+ testForD8(parameters.getBackend())
+ .addProgramFiles(testClasses.getInstrumented())
+ .addProgramFiles(ToolHelper.JACOCO_AGENT)
+ .setMinApi(parameters.getApiLevel())
+ .compile();
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleNamedConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleNamedConstantDynamicTest.java
index 69cd883..1f7b9d1 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleNamedConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleNamedConstantDynamicTest.java
@@ -12,6 +12,7 @@
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
@@ -42,6 +43,7 @@
private static final String EXPECTED_OUTPUT =
StringUtils.lines("true", "true", "true", "true", "true");
+ private static final Class<?> MAIN_CLASS = A.class;
@Test
public void testReference() throws Exception {
@@ -51,28 +53,39 @@
testForJvm()
.addProgramClassFileData(getTransformedClasses())
- .run(parameters.getRuntime(), A.class)
+ .run(parameters.getRuntime(), MAIN_CLASS)
.assertSuccessWithOutput(EXPECTED_OUTPUT);
}
@Test
+ public void TestD8Cf() throws Exception {
+ testForDesugaring(parameters)
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .applyIf(
+ // When not desugaring the CF code requires JDK 11.
+ DesugarTestConfiguration::isNotDesugared,
+ r -> {
+ if (parameters.isCfRuntime()
+ && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+ r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+ } else {
+ r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+ }
+ })
+ .applyIf(
+ DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+ }
+
+ @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")),
- diagnosticOrigin(hasParent(Origin.unknown()))));
- }));
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
}
@Test
@@ -80,21 +93,36 @@
assumeTrue(
parameters.getRuntime().isDex() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
- assertThrows(
- CompilationFailedException.class,
- () ->
- testForR8(parameters.getBackend())
- .addProgramClassFileData(getTransformedClasses())
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(A.class)
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertOnlyErrors();
- diagnostics.assertErrorsMatch(
- allOf(
- diagnosticMessage(containsString("Unsupported dynamic constant")),
- diagnosticOrigin(hasParent(Origin.unknown()))));
- }));
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(A.class)
+ // TODO(b/198142613): There should not be a warnings on class references which are
+ // desugared away.
+ .applyIf(
+ parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+ b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup"))
+ // TODO(b/198142625): Support CONSTANT_Dynamic output for class files.
+ .applyIf(
+ parameters.isCfRuntime(),
+ r -> {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ r.compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ allOf(
+ diagnosticMessage(
+ containsString(
+ "Unsupported dynamic constant (not desugaring)")),
+ diagnosticOrigin(hasParent(Origin.unknown()))));
+ }));
+ },
+ r ->
+ r.run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT));
}
private byte[] getTransformedClasses() throws IOException {
@@ -138,6 +166,9 @@
}
private static Object myConstant(MethodHandles.Lookup lookup, String name, Class<?> type) {
+ // TODO(b/178172809): Use a un-moddeled library method to prevent R8 from seeing that name
+ // is unused.
+ name.toCharArray();
return new Object();
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleTypesConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleTypesConstantDynamicTest.java
index b04792f..5ee0e8f 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleTypesConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleTypesConstantDynamicTest.java
@@ -12,6 +12,7 @@
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
@@ -41,6 +42,7 @@
private static final String EXPECTED_OUTPUT =
StringUtils.lines("true", "true", "true", "true", "true");
+ private static final Class<?> MAIN_CLASS = A.class;
@Test
public void testReference() throws Exception {
@@ -55,23 +57,34 @@
}
@Test
+ public void TestD8Cf() throws Exception {
+ testForDesugaring(parameters)
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .applyIf(
+ // When not desugaring the CF code requires JDK 11.
+ DesugarTestConfiguration::isNotDesugared,
+ r -> {
+ if (parameters.isCfRuntime()
+ && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+ r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+ } else {
+ r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+ }
+ })
+ .applyIf(
+ DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+ }
+
+ @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")),
- diagnosticOrigin(hasParent(Origin.unknown()))));
- }));
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
}
@Test
@@ -79,21 +92,36 @@
assumeTrue(
parameters.getRuntime().isDex() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
- assertThrows(
- CompilationFailedException.class,
- () ->
- testForR8(parameters.getBackend())
- .addProgramClassFileData(getTransformedClasses())
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(A.class)
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertOnlyErrors();
- diagnostics.assertErrorsMatch(
- allOf(
- diagnosticMessage(containsString("Unsupported dynamic constant")),
- diagnosticOrigin(hasParent(Origin.unknown()))));
- }));
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_CLASS)
+ // TODO(b/198142613): There should not be a warnings on class references which are
+ // desugared away.
+ .applyIf(
+ parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+ b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup"))
+ // TODO(b/198142625): Support CONSTANT_Dynamic output for class files.
+ .applyIf(
+ parameters.isCfRuntime(),
+ r -> {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ r.compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ allOf(
+ diagnosticMessage(
+ containsString(
+ "Unsupported dynamic constant (not desugaring)")),
+ diagnosticOrigin(hasParent(Origin.unknown()))));
+ }));
+ },
+ r ->
+ r.run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT));
}
private byte[] getTransformedClasses() 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 24c9ed8..2021777 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
@@ -44,6 +44,7 @@
private static final String EXPECTED_OUTPUT =
StringUtils.lines("true", "true", "true", "true", "true");
+ private static final Class<?> MAIN_CLASS = A.class;
@Test
public void testReference() throws Exception {
@@ -53,11 +54,30 @@
testForJvm()
.addProgramClassFileData(getTransformedClasses())
- .run(parameters.getRuntime(), A.class)
+ .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(
+ allOf(
+ diagnosticMessage(
+ containsString("Unsupported dynamic constant (different owner)")),
+ diagnosticOrigin(hasParent(Origin.unknown()))));
+ }));
+ }
+
+ @Test
public void testD8() throws Exception {
assumeTrue(parameters.isDexRuntime());
@@ -72,7 +92,8 @@
diagnostics.assertOnlyErrors();
diagnostics.assertErrorsMatch(
allOf(
- diagnosticMessage(containsString("Unsupported dynamic constant")),
+ diagnosticMessage(
+ containsString("Unsupported dynamic constant (different owner)")),
diagnosticOrigin(hasParent(Origin.unknown()))));
}));
}
@@ -87,13 +108,14 @@
testForR8(parameters.getBackend())
.addProgramClassFileData(getTransformedClasses())
.setMinApi(parameters.getApiLevel())
- .addKeepMainRule(A.class)
+ .addKeepMainRule(MAIN_CLASS)
.compileWithExpectedDiagnostics(
diagnostics -> {
diagnostics.assertOnlyErrors();
diagnostics.assertErrorsMatch(
allOf(
- diagnosticMessage(containsString("Unsupported dynamic constant")),
+ diagnosticMessage(
+ containsString("Unsupported dynamic constant (different owner)")),
diagnosticOrigin(hasParent(Origin.unknown()))));
}));
}