Generate DesugarVarHandle class
This extends the method generator to generate a class as well.
Bug: b/247076137
Change-Id: I3c5e99021eade28e53770820e15214814cd03c33
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 293e189..f00f611 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -100,7 +100,11 @@
public CfCodePrinter() {}
- public List<String> getImports() {
+ public Set<String> getImports() {
+ return imports;
+ }
+
+ public List<String> getImportsSorted() {
ArrayList<String> sorted = new ArrayList<>(imports);
sorted.sort(String::compareTo);
return sorted;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 47fabfa..9807531 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -353,7 +353,7 @@
return new Builder(false);
}
- private static Builder builder(DexEncodedField from) {
+ public static Builder builder(DexEncodedField from) {
return new Builder(from.isD8R8Synthesized(), from);
}
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 2e2755d..809f8e6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -75,6 +75,9 @@
public static final String dalvikAnnotationSignatureString = "Ldalvik/annotation/Signature;";
public static final String recordTagDescriptorString = "Lcom/android/tools/r8/RecordTag;";
public static final String recordDescriptorString = "Ljava/lang/Record;";
+ public static final String desugarVarHandleDescriptorString =
+ "Lcom/android/tools/r8/DesugarVarHandle;";
+ public static final String varHandleDescriptorString = "Ljava/lang/invoke/VarHandle;";
public static final String dalvikAnnotationOptimizationPrefixString =
"Ldalvik/annotation/optimization/";
@@ -266,7 +269,7 @@
public final DexString stringBuilderDescriptor = createString("Ljava/lang/StringBuilder;");
public final DexString stringBufferDescriptor = createString("Ljava/lang/StringBuffer;");
- public final DexString varHandleDescriptor = createString("Ljava/lang/invoke/VarHandle;");
+ public final DexString varHandleDescriptor = createString(varHandleDescriptorString);
public final DexString methodHandleDescriptor = createString("Ljava/lang/invoke/MethodHandle;");
public final DexString methodTypeDescriptor = createString("Ljava/lang/invoke/MethodType;");
public final DexString invocationHandlerDescriptor =
@@ -738,6 +741,8 @@
public final DexType stringConcatFactoryType =
createStaticallyKnownType("Ljava/lang/invoke/StringConcatFactory;");
public final DexType unsafeType = createStaticallyKnownType("Lsun/misc/Unsafe;");
+ public final DexType desugarVarHandleType =
+ createStaticallyKnownType(desugarVarHandleDescriptorString);
public final ObjectMethodsMembers objectMethodsMembers = new ObjectMethodsMembers();
public final ServiceLoaderMethods serviceLoaderMethods = new ServiceLoaderMethods();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java
new file mode 100644
index 0000000..d463636
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java
@@ -0,0 +1,475 @@
+// 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See GenerateVarHandleMethods.java.
+// ***********************************************************************************
+
+package com.android.tools.r8.ir.desugar.varhandle;
+
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfConstClass;
+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.CfInstanceFieldRead;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
+import com.google.common.collect.ImmutableList;
+
+public final class VarHandleDesugaringMethods {
+
+ public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+ factory.createSynthesizedType("Lcom/android/tools/r8/DesugarVarHandle;");
+ factory.createSynthesizedType("Ljava/lang/reflect/Field;");
+ factory.createSynthesizedType("Lsun/misc/Unsafe;");
+ }
+
+ public static void generateDesugarVarHandleClass(
+ SyntheticProgramClassBuilder builder, DexItemFactory factory) {
+ builder.setInstanceFields(
+ ImmutableList.of(
+ DexEncodedField.syntheticBuilder()
+ .setField(
+ factory.createField(
+ builder.getType(),
+ factory.createType(factory.createString("Lsun/misc/Unsafe;")),
+ factory.createString("U")))
+ .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedField.syntheticBuilder()
+ .setField(
+ factory.createField(
+ builder.getType(),
+ factory.createType(factory.createString("Ljava/lang/Class;")),
+ factory.createString("recv")))
+ .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedField.syntheticBuilder()
+ .setField(
+ factory.createField(
+ builder.getType(),
+ factory.createType(factory.createString("Ljava/lang/Class;")),
+ factory.createString("type")))
+ .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedField.syntheticBuilder()
+ .setField(
+ factory.createField(
+ builder.getType(), factory.longType, factory.createString("offset")))
+ .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
+ .disableAndroidApiLevelCheck()
+ .build()));
+ DexMethod set =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(factory.voidType, factory.objectType, factory.objectType),
+ factory.createString("set"));
+ DexMethod get =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(factory.objectType, factory.objectType),
+ factory.createString("get"));
+ DexMethod compareAndSetInt =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(
+ factory.booleanType, factory.objectType, factory.intType, factory.intType),
+ factory.createString("compareAndSet"));
+ DexMethod getInt =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(factory.intType, factory.objectType),
+ factory.createString("get"));
+ DexMethod compareAndSet =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(
+ factory.booleanType, factory.objectType, factory.objectType, factory.objectType),
+ factory.createString("compareAndSet"));
+ DexMethod setInt =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(factory.voidType, factory.objectType, factory.intType),
+ factory.createString("set"));
+ DexMethod getLong =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(factory.longType, factory.objectType),
+ factory.createString("get"));
+ DexMethod constructor_3 =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(
+ factory.voidType,
+ factory.createType(factory.createString("Ljava/lang/Class;")),
+ factory.createType(factory.createString("Ljava/lang/String;")),
+ factory.createType(factory.createString("Ljava/lang/Class;"))),
+ factory.createString("<init>"));
+ DexMethod setLong =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(factory.voidType, factory.objectType, factory.longType),
+ factory.createString("set"));
+ DexMethod compareAndSetLong =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(
+ factory.booleanType, factory.objectType, factory.longType, factory.longType),
+ factory.createString("compareAndSet"));
+ builder.setDirectMethods(
+ ImmutableList.of(
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(constructor_3)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, true))
+ .setCode(DesugarVarHandle_constructor_3(factory, constructor_3))
+ .disableAndroidApiLevelCheck()
+ .build()));
+ builder.setVirtualMethods(
+ ImmutableList.of(
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(set)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarVarHandle_set(factory, set))
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(get)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarVarHandle_get(factory, get))
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(compareAndSetInt)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarVarHandle_compareAndSetInt(factory, compareAndSetInt))
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(getInt)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarVarHandle_getInt(factory, getInt))
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(compareAndSet)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarVarHandle_compareAndSet(factory, compareAndSet))
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(setInt)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarVarHandle_setInt(factory, setInt))
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(getLong)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarVarHandle_getLong(factory, getLong))
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(setLong)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarVarHandle_setLong(factory, setLong))
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(compareAndSetLong)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarVarHandle_compareAndSetLong(factory, compareAndSetLong))
+ .disableAndroidApiLevelCheck()
+ .build()));
+ }
+
+ public static CfCode DesugarVarHandle_constructor_3(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ CfLabel label2 = new CfLabel();
+ CfLabel label3 = new CfLabel();
+ CfLabel label4 = new CfLabel();
+ CfLabel label5 = new CfLabel();
+ CfLabel label6 = new CfLabel();
+ CfLabel label7 = new CfLabel();
+ CfLabel label8 = new CfLabel();
+ CfLabel label9 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 4,
+ 6,
+ ImmutableList.of(
+ label0,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInvoke(
+ 183,
+ factory.createMethod(
+ factory.objectType,
+ factory.createProto(factory.voidType),
+ factory.createString("<init>")),
+ false),
+ label1,
+ new CfConstClass(factory.createType("Lsun/misc/Unsafe;")),
+ new CfConstString(factory.createString("theUnsafe")),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.classType,
+ factory.createProto(
+ factory.createType("Ljava/lang/reflect/Field;"), factory.stringType),
+ factory.createString("getDeclaredField")),
+ false),
+ new CfStore(ValueType.OBJECT, 4),
+ label2,
+ new CfLoad(ValueType.OBJECT, 4),
+ new CfConstNumber(1, ValueType.INT),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.createType("Ljava/lang/reflect/Field;"),
+ factory.createProto(factory.voidType, factory.booleanType),
+ factory.createString("setAccessible")),
+ false),
+ label3,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfLoad(ValueType.OBJECT, 4),
+ new CfConstNull(),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.createType("Ljava/lang/reflect/Field;"),
+ factory.createProto(factory.objectType, factory.objectType),
+ factory.createString("get")),
+ false),
+ new CfCheckCast(factory.createType("Lsun/misc/Unsafe;")),
+ new CfInstanceFieldWrite(
+ factory.createField(
+ factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+ factory.createType("Lsun/misc/Unsafe;"),
+ factory.createString("U"))),
+ label4,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfLoad(ValueType.OBJECT, 1),
+ new CfInstanceFieldWrite(
+ factory.createField(
+ factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+ factory.classType,
+ factory.createString("recv"))),
+ label5,
+ new CfLoad(ValueType.OBJECT, 1),
+ new CfLoad(ValueType.OBJECT, 2),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.classType,
+ factory.createProto(
+ factory.createType("Ljava/lang/reflect/Field;"), factory.stringType),
+ factory.createString("getDeclaredField")),
+ false),
+ new CfStore(ValueType.OBJECT, 5),
+ label6,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfLoad(ValueType.OBJECT, 5),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.createType("Ljava/lang/reflect/Field;"),
+ factory.createProto(factory.classType),
+ factory.createString("getType")),
+ false),
+ new CfInstanceFieldWrite(
+ factory.createField(
+ factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+ factory.classType,
+ factory.createString("type"))),
+ label7,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInstanceFieldRead(
+ factory.createField(
+ factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+ factory.createType("Lsun/misc/Unsafe;"),
+ factory.createString("U"))),
+ new CfLoad(ValueType.OBJECT, 1),
+ new CfLoad(ValueType.OBJECT, 2),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.classType,
+ factory.createProto(
+ factory.createType("Ljava/lang/reflect/Field;"), factory.stringType),
+ factory.createString("getDeclaredField")),
+ false),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.createType("Lsun/misc/Unsafe;"),
+ factory.createProto(
+ factory.longType, factory.createType("Ljava/lang/reflect/Field;")),
+ factory.createString("objectFieldOffset")),
+ false),
+ new CfInstanceFieldWrite(
+ factory.createField(
+ factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+ factory.longType,
+ factory.createString("offset"))),
+ label8,
+ new CfReturnVoid(),
+ label9),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_compareAndSet(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 1,
+ 4,
+ ImmutableList.of(
+ label0, new CfConstNumber(0, ValueType.INT), new CfReturn(ValueType.INT), label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_compareAndSetInt(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 1,
+ 4,
+ ImmutableList.of(
+ label0, new CfConstNumber(0, ValueType.INT), new CfReturn(ValueType.INT), label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_compareAndSetLong(
+ DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 1,
+ 6,
+ ImmutableList.of(
+ label0, new CfConstNumber(0, ValueType.INT), new CfReturn(ValueType.INT), label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_get(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 1,
+ 2,
+ ImmutableList.of(label0, new CfConstNull(), new CfReturn(ValueType.OBJECT), label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_getInt(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 1,
+ 2,
+ ImmutableList.of(
+ label0, new CfConstNumber(-1, ValueType.INT), new CfReturn(ValueType.INT), label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_getLong(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 2,
+ 2,
+ ImmutableList.of(
+ label0, new CfConstNumber(-1, ValueType.LONG), new CfReturn(ValueType.LONG), label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_set(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 0,
+ 3,
+ ImmutableList.of(label0, new CfReturnVoid(), label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_setInt(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 0,
+ 3,
+ ImmutableList.of(label0, new CfReturnVoid(), label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_setLong(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 0,
+ 4,
+ ImmutableList.of(label0, new CfReturnVoid(), label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java
index c706f50..0630571 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java
@@ -363,7 +363,7 @@
codePrinter.visitMethod(generatedMethodName, method.getCode().asCfCode());
index++;
}
- codePrinter.getImports().forEach(imports::addImport);
+ codePrinter.getImportsSorted().forEach(imports::addImport);
return createCfCodeMethodNames;
}
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
index 28ab9a7..0cd6b23 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -8,30 +8,65 @@
import com.android.tools.r8.cf.CfCodePrinter;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.ClassKind;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.JarApplicationReader;
import com.android.tools.r8.graph.JarClassFileReader;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Reporter;
+import com.beust.jcommander.internal.Sets;
+import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
import java.util.TreeSet;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
public abstract class MethodGenerationBase extends CodeGenerationBase {
protected abstract List<Class<?>> getMethodTemplateClasses();
+ protected List<Class<?>> getClassesToGenerate() {
+ return ImmutableList.of();
+ }
+
+ protected DexEncodedField getField(DexEncodedField field) {
+ return field;
+ }
+
+ protected boolean includeMethod(DexEncodedMethod method) {
+ return !method.isInstanceInitializer();
+ }
+
protected CfCode getCode(String holderName, String methodName, CfCode code) {
return code;
}
+ protected DexEncodedMethod mapMethod(DexEncodedMethod method) {
+ return method;
+ }
+
// Running this method will regenerate / overwrite the content of the generated class.
protected void generateMethodsAndWriteThemToFile() throws IOException {
FileUtils.writeToFile(getGeneratedFile(), null, generateMethods().getBytes());
@@ -43,31 +78,47 @@
File tempFile = File.createTempFile("output-", ".java");
- readMethodTemplatesInto(codePrinter);
- generateRawOutput(codePrinter, tempFile.toPath());
+ Map<DexEncodedMethod, String> generatedMethods = new HashMap<>();
+ List<DexEncodedField> fields = new ArrayList<>();
+ readMethodTemplatesInto(codePrinter, generatedMethods::put, fields::add);
+ generateRawOutput(generatedMethods, fields, codePrinter, tempFile.toPath());
String result = formatRawOutput(tempFile.toPath());
tempFile.deleteOnExit();
return result;
}
- private void readMethodTemplatesInto(CfCodePrinter codePrinter) throws IOException {
+ private void readMethodTemplatesInto(
+ CfCodePrinter codePrinter,
+ BiConsumer<DexEncodedMethod, String> generatedMethods,
+ Consumer<DexEncodedField> generatedFields)
+ throws IOException {
InternalOptions options = new InternalOptions(factory, new Reporter());
options.testing.readInputStackMaps = true;
JarClassFileReader<DexProgramClass> reader =
new JarClassFileReader<>(
new JarApplicationReader(options),
clazz -> {
+ for (DexEncodedField field : clazz.fields()) {
+ DexEncodedField fieldToAddToClass = getField(field);
+ if (fieldToAddToClass != null) {
+ generatedFields.accept(fieldToAddToClass);
+ }
+ }
for (DexEncodedMethod method : clazz.allMethodsSorted()) {
- if (method.isInitializer()) {
+ if (!includeMethod(method)) {
continue;
}
String holderName = method.getHolderType().getName();
String methodName = method.getReference().name.toString();
+ if (methodName.equals("<init>")) {
+ methodName = "constructor_" + method.getProto().getArity();
+ }
String generatedMethodName = holderName + "_" + methodName;
CfCode code = getCode(holderName, methodName, method.getCode().asCfCode());
if (code != null) {
codePrinter.visitMethod(generatedMethodName, code);
+ generatedMethods.accept(method, generatedMethodName);
}
}
},
@@ -77,11 +128,222 @@
}
}
- private void generateRawOutput(CfCodePrinter codePrinter, Path tempFile) throws IOException {
+ private String createType(DexType type) {
+ if (type.isVoidType()) {
+ return "factory.voidType";
+ }
+ if (type.isBooleanType()) {
+ return "factory.booleanType";
+ }
+ if (type.isIntType()) {
+ return "factory.intType";
+ }
+ if (type.isLongType()) {
+ return "factory.longType";
+ }
+ if (type.descriptor.toString().equals("Ljava/lang/Object;")) {
+ return "factory.objectType";
+ }
+ return "factory.createType(factory.createString(\"" + type.getDescriptor() + "\"))";
+ }
+
+ private String createField(DexEncodedField field) {
+ return "factory.createField(\n"
+ + "builder.getType(),\n"
+ + createType(field.getType())
+ + ",\n"
+ + "factory.createString(\""
+ + field.getName()
+ + "\"))\n";
+ }
+
+ private String buildSyntheticMethod(
+ DexEncodedMethod method, String codeGenerator, Set<String> requiredImports) {
+ String name =
+ method.isInstanceInitializer()
+ ? "constructor_" + method.getProto().getArity()
+ : method.getName().toString();
+ requiredImports.add("com.android.tools.r8.graph.DexEncodedMethod");
+ requiredImports.add("com.android.tools.r8.graph.MethodAccessFlags");
+ requiredImports.add("com.android.tools.r8.dex.Constants");
+ return "DexEncodedMethod.syntheticBuilder()\n"
+ + " .setMethod("
+ + name
+ + ")\n"
+ + " .setAccessFlags(\n"
+ + " MethodAccessFlags.fromSharedAccessFlags(\n"
+ + " Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, "
+ + (method.isInstanceInitializer())
+ + "))\n"
+ + " .setCode("
+ + codeGenerator
+ + "(factory, "
+ + name
+ + "))\n"
+ + " .disableAndroidApiLevelCheck()\n"
+ + " .build()";
+ }
+
+ private String generateCreateProto(DexProto proto) {
+ StringBuilder builder = new StringBuilder("factory.createProto(");
+ builder.append(createType(proto.returnType));
+ proto.getParameters().forEach(type -> builder.append(", ").append(createType(type)));
+ builder.append(")");
+ return builder.toString();
+ }
+
+ private String generateCreateMethod(DexEncodedMethod method) {
+ return "factory.createMethod(\n"
+ + "builder.getType(), "
+ + generateCreateProto(method.getProto())
+ + ", factory.createString(\""
+ + method.getName()
+ + "\"));";
+ }
+
+ private void generateDexMethodLocals(
+ Map<DexEncodedMethod, String> generatedMethods, PrintStream printer, Class<?> clazz) {
+ // Generate local variable for a DexMethod:
+ // DexMethod name = factory.createMethod(
+ // builder.getType(), factory.createProto(...), factory.createString(...));
+
+ generatedMethods.forEach(
+ (method, codeGenerator) -> {
+ if (method.getHolderType().toSourceString().equals(clazz.getCanonicalName())) {
+ String name =
+ method.isInstanceInitializer()
+ ? "constructor_" + method.getProto().getArity()
+ : method.getName().toString();
+ printer.println("DexMethod " + name + " = " + generateCreateMethod(mapMethod(method)));
+ }
+ });
+ }
+
+ private void generateSyntheticMethodsList(
+ Map<DexEncodedMethod, String> generatedMethods,
+ Set<String> requiredImports,
+ PrintStream printer,
+ Class<?> clazz,
+ Predicate<DexEncodedMethod> filter) {
+ printer.println("ImmutableList.of(");
+ BooleanBox first = new BooleanBox(true);
+ generatedMethods.forEach(
+ (method, codeGenerator) -> {
+ if (method.getHolderType().toSourceString().equals(clazz.getCanonicalName())
+ && filter.test(method)) {
+ if (!first.get()) {
+ printer.println(",\n");
+ }
+ first.set(false);
+ printer.println(buildSyntheticMethod(method, codeGenerator, requiredImports));
+ }
+ });
+ printer.println(")");
+ }
+
+ private String buildSyntheticField(DexEncodedField field, Set<String> requiredImports) {
+ requiredImports.add("com.android.tools.r8.graph.DexEncodedField");
+ requiredImports.add("com.android.tools.r8.graph.FieldAccessFlags");
+ return " DexEncodedField.syntheticBuilder()\n"
+ + " .setField("
+ + createField(field)
+ + ")\n"
+ + " .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())\n"
+ + " .disableAndroidApiLevelCheck()\n"
+ + " .build()\n";
+ }
+
+ private void generateFieldList(
+ List<DexEncodedField> fields,
+ Set<String> requiredImports,
+ PrintStream printer,
+ Class<?> clazz) {
+ printer.println("ImmutableList.of(");
+ BooleanBox first = new BooleanBox(true);
+ for (DexEncodedField field : fields) {
+ if (field.getHolderType().toSourceString().equals(clazz.getCanonicalName())) {
+ if (!first.get()) {
+ printer.println(",\n");
+ }
+ first.set(false);
+ printer.println(buildSyntheticField(field, requiredImports));
+ }
+ }
+ printer.println(")");
+ }
+
+ private <K, V> Map<K, V> filterMapOnKey(Map<K, V> map, Predicate<K> predicate) {
+ return map.entrySet().stream()
+ .filter(entry -> predicate.test(entry.getKey()))
+ .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
+ }
+
+ private void generateGenerateClasses(
+ Map<DexEncodedMethod, String> allMethods,
+ List<DexEncodedField> allFields,
+ Set<String> requiredImports,
+ PrintStream printer) {
+ for (Class<?> clazz : getClassesToGenerate()) {
+ String simpleName =
+ clazz.getSimpleName(); // "DesugarVarHandle"; //name.substring(name.lastIndexOf('.'));
+ List<DexEncodedField> classFields =
+ ListUtils.filter(
+ allFields,
+ field -> field.getHolderType().toSourceString().equals(clazz.getCanonicalName()));
+ Map<DexEncodedMethod, String> classMethods =
+ filterMapOnKey(
+ allMethods,
+ method -> method.getHolderType().toSourceString().equals(clazz.getCanonicalName()));
+ requiredImports.add("com.android.tools.r8.synthesis.SyntheticProgramClassBuilder");
+ printer.println(
+ "public static void generate"
+ + simpleName
+ + "Class(SyntheticProgramClassBuilder builder, DexItemFactory factory) {");
+ printer.println("builder.setInstanceFields(");
+ generateFieldList(classFields, requiredImports, printer, clazz);
+ printer.println(");");
+ generateDexMethodLocals(classMethods, printer, clazz);
+ if (clazz.getSuperclass() != Object.class) {
+ printer.println(
+ " builder.setSuperType("
+ + createType(factory.createType(Reference.classFromClass(clazz.getSuperclass())))
+ + ");");
+ }
+ printer.println("builder.setDirectMethods(");
+ generateSyntheticMethodsList(
+ classMethods, requiredImports, printer, clazz, DexEncodedMethod::isInstanceInitializer);
+ printer.println(");");
+ printer.println("builder.setVirtualMethods(");
+ generateSyntheticMethodsList(
+ allMethods, requiredImports, printer, clazz, method -> !method.isInstanceInitializer());
+ printer.println(");");
+ printer.println("}");
+ }
+ }
+
+ private void generateRawOutput(
+ Map<DexEncodedMethod, String> generatedMethods,
+ List<DexEncodedField> fields,
+ CfCodePrinter codePrinter,
+ Path tempFile)
+ throws IOException {
try (PrintStream printer = new PrintStream(Files.newOutputStream(tempFile))) {
printer.print(getHeaderString());
- printer.println("import com.android.tools.r8.graph.DexItemFactory;");
- codePrinter.getImports().forEach(i -> printer.println("import " + i + ";"));
+
+ Set<String> imports = Sets.newHashSet();
+ imports.add("com.android.tools.r8.graph.DexItemFactory");
+ // TODO(b/260985726): Consider only calling generateGenerateClasses once.
+ // Generate classes into an unused PrintStream to collect imports.
+ generateGenerateClasses(
+ generatedMethods,
+ fields,
+ imports,
+ new PrintStream(new ByteArrayOutputStream(), true, StandardCharsets.UTF_8.name()));
+ imports.addAll(codePrinter.getImports());
+ ArrayList<String> sortedImports = new ArrayList<>(imports);
+ sortedImports.sort(String::compareTo);
+ sortedImports.forEach(i -> printer.println("import " + i + ";"));
+
printer.println("public final class " + getGeneratedClassName() + " {\n");
printer.println(
"public static void registerSynthesizedCodeReferences(DexItemFactory factory) {");
@@ -89,8 +351,12 @@
printer.println("factory.createSynthesizedType(\"" + type + "\");");
}
printer.println("}");
+ // TODO(b/260985726): Consider only calling generateGenerateClasses once.
+ // Generate classes ignoring the collected imports (they are already added to the file).
+ generateGenerateClasses(generatedMethods, fields, Sets.newHashSet(), printer);
codePrinter.getMethods().forEach(printer::println);
printer.println("}");
}
}
+
}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java
new file mode 100644
index 0000000..2a19a45
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java
@@ -0,0 +1,78 @@
+// 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.varhandle;
+
+import java.lang.reflect.Field;
+
+// Template class for desugaring VarHandle into com.android.tools.r8.DesugarVarHandle.
+public final class DesugarVarHandle {
+
+ // This only have methods found in libcore/libart/src/main/java/sun/misc/Unsafe.java for Lollipop.
+ private static class UnsafeStub {
+
+ public long objectFieldOffset(Field f) {
+ throw new RuntimeException("Stub called.");
+ }
+ }
+
+ private final UnsafeStub U;
+ private final Class<?> recv;
+ private final Class<?> type;
+ private final long offset;
+
+ DesugarVarHandle(Class<?> recv, String name, Class<?> type) throws Exception {
+ Field theUnsafe = UnsafeStub.class.getDeclaredField("theUnsafe");
+ theUnsafe.setAccessible(true);
+ U = (UnsafeStub) theUnsafe.get(null);
+ this.recv = recv;
+ Field field = recv.getDeclaredField(name);
+ this.type = field.getType();
+ this.offset = U.objectFieldOffset(recv.getDeclaredField(name));
+ }
+
+ // get variants.
+ Object get(Object ct1) {
+ // TODO(b/247076137): Implement.
+ return null;
+ }
+
+ int getInt(Object ct1) {
+ // TODO(b/247076137): Implement.
+ return -1;
+ }
+
+ long getLong(Object ct1) {
+ // TODO(b/247076137): Implement.
+ return -1L;
+ }
+
+ // set variants.
+ void set(Object ct1, Object newValue) {
+ // TODO(b/247076137): Implement.
+ }
+
+ void setInt(Object ct1, int newValue) {
+ // TODO(b/247076137): Implement.
+ }
+
+ void setLong(Object ct1, long newValue) {
+ // TODO(b/247076137): Implement.
+ }
+
+ boolean compareAndSet(Object ct1, Object expectedValue, Object newValue) {
+ // TODO(b/247076137): Implement.
+ return false;
+ }
+
+ boolean compareAndSetInt(Object ct1, int expectedValue, int newValue) {
+ // TODO(b/247076137): Implement.
+ return false;
+ }
+
+ boolean compareAndSetLong(Object ct1, long expectedValue, long newValue) {
+ // TODO(b/247076137): Implement.
+ return false;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/GenerateVarHandleMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/GenerateVarHandleMethods.java
new file mode 100644
index 0000000..e691744
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/GenerateVarHandleMethods.java
@@ -0,0 +1,208 @@
+// 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.varhandle;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+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.cfmethodgeneration.MethodGenerationBase;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+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 GenerateVarHandleMethods extends MethodGenerationBase {
+
+ private final DexType GENERATED_TYPE =
+ factory.createType("Lcom/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods;");
+ private final List<Class<?>> METHOD_TEMPLATE_CLASSES = ImmutableList.of(DesugarVarHandle.class);
+
+ protected final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withCfRuntime(CfVm.JDK9).build();
+ }
+
+ public GenerateVarHandleMethods(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Override
+ protected DexType getGeneratedType() {
+ return GENERATED_TYPE;
+ }
+
+ @Override
+ protected List<Class<?>> getMethodTemplateClasses() {
+ return METHOD_TEMPLATE_CLASSES;
+ }
+
+ @Override
+ protected List<Class<?>> getClassesToGenerate() {
+ return ImmutableList.of(DesugarVarHandle.class);
+ }
+
+ @Override
+ protected boolean includeMethod(DexEncodedMethod method) {
+ // Include all methods, including constructors.
+ return true;
+ }
+
+ @Override
+ protected int getYear() {
+ return 2022;
+ }
+
+ private static CfInstruction rewriteToUnsafe(DexItemFactory factory, CfInstruction instruction) {
+ DexType unsafe = factory.unsafeType;
+ DexType unsafeStub =
+ factory.createType(
+ "L" + DesugarVarHandle.class.getTypeName().replace('.', '/') + "$UnsafeStub;");
+ // Rewrite references to UnsafeStub to sun.misc.Unsafe
+ if (instruction.isTypeInstruction()
+ && instruction.asTypeInstruction().getType() == unsafeStub) {
+ return instruction.asTypeInstruction().withType(unsafe);
+ }
+ if (instruction.isFieldInstruction()) {
+ CfFieldInstruction fieldInstruction = instruction.asFieldInstruction();
+ if (fieldInstruction.getField().getType() == unsafeStub) {
+ return fieldInstruction.createWithField(
+ factory.createField(
+ fieldInstruction.getField().getHolderType(),
+ unsafe,
+ fieldInstruction.getField().name));
+ }
+ }
+ if (instruction.isInvoke()) {
+ CfInvoke invoke = instruction.asInvoke();
+ DexMethod method = invoke.getMethod();
+ String name = method.getName().toString();
+ if (invoke.getMethod().getHolderType() == unsafeStub) {
+ return new CfInvoke(
+ invoke.getOpcode(),
+ factory.createMethod(unsafe, invoke.getMethod().getProto(), factory.createString(name)),
+ invoke.isInterface());
+ }
+ }
+ if (instruction.isFrame()) {
+ return instruction.asFrame().mapReferenceTypes(type -> (type == unsafeStub) ? unsafe : type);
+ }
+ return instruction;
+ }
+
+ private static CfInstruction rewriteDesugarVarHandle(
+ DexItemFactory factory, CfInstruction instruction) {
+ // Rewrite references to com.android.tools.r8.ir.desugar.varhandle.DesugarVarHandle to
+ // com.android.tools.r8.DesugarVarHandle.
+ DexType desugarVarHandle = factory.desugarVarHandleType;
+ DexType desugarVarHandleStub =
+ factory.createType("L" + DesugarVarHandle.class.getTypeName().replace('.', '/') + ";");
+ if (instruction.isFieldInstruction()) {
+ CfFieldInstruction fieldInstruction = instruction.asFieldInstruction();
+ if (fieldInstruction.getField().getHolderType() == desugarVarHandleStub) {
+ return fieldInstruction.createWithField(
+ factory.createField(
+ desugarVarHandle,
+ fieldInstruction.getField().getType(),
+ fieldInstruction.getField().name));
+ }
+ }
+ return instruction;
+ }
+
+ @Override
+ protected DexEncodedField getField(DexEncodedField field) {
+ if (field.getType().getTypeName().endsWith("$UnsafeStub")) {
+ return DexEncodedField.builder(field)
+ .setField(
+ factory.createField(
+ field.getHolderType(), factory.createType("Lsun/misc/Unsafe;"), field.getName()))
+ .build();
+ }
+ return field;
+ }
+
+ @Override
+ protected CfCode getCode(String holderName, String methodName, CfCode code) {
+ if (methodName.endsWith("Stub")) {
+ // Don't include stubs targeted only for rewriting in the generated code.
+ return null;
+ }
+ if (!holderName.equals("DesugarVarHandle")) {
+ throw new RuntimeException("Unexpected");
+ }
+ code.setInstructions(
+ code.getInstructions().stream()
+ .map(instruction -> rewriteToUnsafe(factory, instruction))
+ .map(instruction -> rewriteDesugarVarHandle(factory, instruction))
+ .collect(Collectors.toList()));
+ return code;
+ }
+
+ private DexEncodedMethod methodWithName(DexEncodedMethod method, String name) {
+ DexType holder = method.getHolderType();
+ DexType desugarVarHandle = factory.desugarVarHandleType;
+ DexType desugarVarHandleStub =
+ factory.createType("L" + DesugarVarHandle.class.getTypeName().replace('.', '/') + ";");
+ // Map methods to be on the final DesugarVarHandle class.
+ if (holder == desugarVarHandleStub) {
+ holder = desugarVarHandle;
+ }
+ DexProto proto = method.getProto();
+ if (proto.getReturnType() == desugarVarHandleStub) {
+ proto = factory.createProto(desugarVarHandle, proto.parameters);
+ }
+ return DexEncodedMethod.syntheticBuilder(method)
+ .setMethod(factory.createMethod(holder, proto, factory.createString(name)))
+ .build();
+ }
+
+ @Override
+ protected DexEncodedMethod mapMethod(DexEncodedMethod method) {
+ // Map VarHandle access mode methods to not have the Int/Long postfix.
+ for (String prefix : ImmutableList.of("get", "set", "compareAndSet")) {
+ if (method.getName().startsWith(prefix)) {
+ assert method.getName().toString().substring(prefix.length()).equals("Int")
+ || method.getName().toString().substring(prefix.length()).equals("Long");
+ return methodWithName(method, prefix);
+ }
+ }
+ return methodWithName(method, method.getName().toString());
+ }
+
+ @Test
+ public void testVarHandleDesugaringGenerated() throws Exception {
+ ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
+ sorted.sort(Comparator.comparing(Class::getTypeName));
+ assertEquals("Classes should be listed in sorted order", sorted, getMethodTemplateClasses());
+ assertEquals(
+ FileUtils.readTextFile(getGeneratedFile(), StandardCharsets.UTF_8), generateMethods());
+ }
+
+ public static void main(String[] args) throws Exception {
+ new GenerateVarHandleMethods(null).generateMethodsAndWriteThemToFile();
+ }
+}