From 71324c080cf87c2066c547b13441f7ebb8c5e5f5 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 18 Nov 2022 15:34:20 -0800 Subject: [PATCH 1/3] Allow constant and invoke dynamic with arbitrary bootstrap method --- .../io/airlift/bytecode/BytecodeBlock.java | 11 +++ .../expression/BytecodeExpressions.java | 40 ++++++++- .../InvokeDynamicBytecodeExpression.java | 6 +- .../bytecode/instruction/BootstrapMethod.java | 85 +++++++++++++++++++ .../bytecode/instruction/Constant.java | 23 +++-- .../instruction/InvokeInstruction.java | 32 +++++-- 6 files changed, 178 insertions(+), 19 deletions(-) create mode 100644 src/main/java/io/airlift/bytecode/instruction/BootstrapMethod.java diff --git a/src/main/java/io/airlift/bytecode/BytecodeBlock.java b/src/main/java/io/airlift/bytecode/BytecodeBlock.java index 160b519..dfab33f 100644 --- a/src/main/java/io/airlift/bytecode/BytecodeBlock.java +++ b/src/main/java/io/airlift/bytecode/BytecodeBlock.java @@ -15,6 +15,7 @@ import com.google.common.collect.ImmutableList; import io.airlift.bytecode.debug.LineNumberNode; +import io.airlift.bytecode.instruction.BootstrapMethod; import io.airlift.bytecode.instruction.Constant; import io.airlift.bytecode.instruction.InvokeInstruction; import io.airlift.bytecode.instruction.JumpInstruction; @@ -494,6 +495,16 @@ public BytecodeNode invokeDynamic(String name, return this; } + public BytecodeNode invokeDynamic(String name, + ParameterizedType returnType, + Iterable parameterTypes, + BootstrapMethod bootstrapMethod, + List bootstrapArgs) + { + nodes.add(InvokeInstruction.invokeDynamic(name, returnType, parameterTypes, bootstrapMethod, bootstrapArgs)); + return this; + } + public BytecodeBlock ret(Class type) { if (type == long.class) { diff --git a/src/main/java/io/airlift/bytecode/expression/BytecodeExpressions.java b/src/main/java/io/airlift/bytecode/expression/BytecodeExpressions.java index 1bcf98f..93f01e9 100644 --- a/src/main/java/io/airlift/bytecode/expression/BytecodeExpressions.java +++ b/src/main/java/io/airlift/bytecode/expression/BytecodeExpressions.java @@ -18,6 +18,7 @@ import io.airlift.bytecode.MethodDefinition; import io.airlift.bytecode.OpCode; import io.airlift.bytecode.ParameterizedType; +import io.airlift.bytecode.instruction.BootstrapMethod; import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; @@ -27,7 +28,6 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.Iterables.toArray; import static io.airlift.bytecode.ParameterizedType.type; import static io.airlift.bytecode.expression.ArithmeticBytecodeExpression.createArithmeticBytecodeExpression; import static io.airlift.bytecode.instruction.Constant.loadBoolean; @@ -176,7 +176,26 @@ public static BytecodeExpression constantDynamic( name, type, bootstrapMethod, - toArray(bootstrapArgs, Object.class))); + ImmutableList.copyOf(bootstrapArgs))); + } + + public static BytecodeExpression constantDynamic( + String name, + ParameterizedType type, + BootstrapMethod bootstrapMethod, + Iterable bootstrapArgs) + { + requireNonNull(name, "name is null"); + requireNonNull(type, "type is null"); + requireNonNull(bootstrapMethod, "bootstrapMethod is null"); + requireNonNull(bootstrapArgs, "bootstrapArgs is null"); + return new ConstantBytecodeExpression( + type, + loadDynamic( + name, + type, + bootstrapMethod, + ImmutableList.copyOf(bootstrapArgs))); } public static BytecodeExpression defaultValue(ParameterizedType type) @@ -575,6 +594,23 @@ public static BytecodeExpression invokeDynamic( ParameterizedType returnType, Iterable parameterTypes, Iterable parameters) + { + return new InvokeDynamicBytecodeExpression( + BootstrapMethod.from(bootstrapMethod), + bootstrapArgs, + methodName, + returnType, + parameters, + parameterTypes); + } + + public static BytecodeExpression invokeDynamic( + BootstrapMethod bootstrapMethod, + Iterable bootstrapArgs, + String methodName, + ParameterizedType returnType, + Iterable parameterTypes, + Iterable parameters) { return new InvokeDynamicBytecodeExpression( bootstrapMethod, diff --git a/src/main/java/io/airlift/bytecode/expression/InvokeDynamicBytecodeExpression.java b/src/main/java/io/airlift/bytecode/expression/InvokeDynamicBytecodeExpression.java index fb11a38..7d42a4e 100644 --- a/src/main/java/io/airlift/bytecode/expression/InvokeDynamicBytecodeExpression.java +++ b/src/main/java/io/airlift/bytecode/expression/InvokeDynamicBytecodeExpression.java @@ -19,8 +19,8 @@ import io.airlift.bytecode.BytecodeNode; import io.airlift.bytecode.MethodGenerationContext; import io.airlift.bytecode.ParameterizedType; +import io.airlift.bytecode.instruction.BootstrapMethod; -import java.lang.reflect.Method; import java.util.List; import java.util.stream.Collectors; @@ -29,7 +29,7 @@ class InvokeDynamicBytecodeExpression extends BytecodeExpression { - private final Method bootstrapMethod; + private final BootstrapMethod bootstrapMethod; private final List bootstrapArgs; private final String methodName; private final ParameterizedType returnType; @@ -37,7 +37,7 @@ class InvokeDynamicBytecodeExpression private final List parameterTypes; InvokeDynamicBytecodeExpression( - Method bootstrapMethod, + BootstrapMethod bootstrapMethod, Iterable bootstrapArgs, String methodName, ParameterizedType returnType, diff --git a/src/main/java/io/airlift/bytecode/instruction/BootstrapMethod.java b/src/main/java/io/airlift/bytecode/instruction/BootstrapMethod.java new file mode 100644 index 0000000..4ccd1b9 --- /dev/null +++ b/src/main/java/io/airlift/bytecode/instruction/BootstrapMethod.java @@ -0,0 +1,85 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.airlift.bytecode.instruction; + +import com.google.common.collect.ImmutableList; +import io.airlift.bytecode.MethodDefinition; +import io.airlift.bytecode.Parameter; +import io.airlift.bytecode.ParameterizedType; + +import java.lang.reflect.Method; +import java.util.List; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.airlift.bytecode.ParameterizedType.type; +import static java.util.Arrays.stream; +import static java.util.Objects.requireNonNull; + +public class BootstrapMethod +{ + private final ParameterizedType ownerClass; + private final String name; + private final ParameterizedType returnType; + private final List parameterTypes; + + public BootstrapMethod(ParameterizedType ownerClass, String name, ParameterizedType returnType, List parameterTypes) + { + this.ownerClass = requireNonNull(ownerClass, "ownerClass is null"); + this.name = requireNonNull(name, "name is null"); + this.returnType = requireNonNull(returnType, "returnType is null"); + this.parameterTypes = ImmutableList.copyOf(requireNonNull(parameterTypes, "parameterTypes is null")); + } + + public ParameterizedType getOwnerClass() + { + return ownerClass; + } + + public String getName() + { + return name; + } + + public ParameterizedType getReturnType() + { + return returnType; + } + + public List getParameterTypes() + { + return parameterTypes; + } + + public static BootstrapMethod from(Method method) + { + return new BootstrapMethod( + type(method.getDeclaringClass()), + method.getName(), + type(method.getReturnType()), + stream(method.getParameterTypes()) + .map(ParameterizedType::type) + .collect(toImmutableList())); + } + + public static BootstrapMethod from(MethodDefinition method) + { + return new BootstrapMethod( + method.getDeclaringClass().getType(), + method.getName(), + method.getReturnType(), + method.getParameters().stream() + .map(Parameter::getType) + .collect(toImmutableList())); + } +} diff --git a/src/main/java/io/airlift/bytecode/instruction/Constant.java b/src/main/java/io/airlift/bytecode/instruction/Constant.java index 164fce0..ba97680 100644 --- a/src/main/java/io/airlift/bytecode/instruction/Constant.java +++ b/src/main/java/io/airlift/bytecode/instruction/Constant.java @@ -162,7 +162,7 @@ public static Constant loadDynamic( return new DynamicConstant( name, type, - bootstrapMethod, + BootstrapMethod.from(bootstrapMethod), ImmutableList.copyOf(bootstrapArguments)); } @@ -171,6 +171,19 @@ public static Constant loadDynamic( ParameterizedType type, Method bootstrapMethod, Object... bootstrapArguments) + { + return new DynamicConstant( + name, + type, + BootstrapMethod.from(bootstrapMethod), + ImmutableList.copyOf(bootstrapArguments)); + } + + public static Constant loadDynamic( + String name, + ParameterizedType type, + BootstrapMethod bootstrapMethod, + Iterable bootstrapArguments) { return new DynamicConstant( name, @@ -664,10 +677,10 @@ public static class DynamicConstant { private final String name; private final ParameterizedType type; - private final Method bootstrapMethod; + private final BootstrapMethod bootstrapMethod; private final List bootstrapArguments; - public DynamicConstant(String name, ParameterizedType type, Method bootstrapMethod, List bootstrapArguments) + public DynamicConstant(String name, ParameterizedType type, BootstrapMethod bootstrapMethod, List bootstrapArguments) { this.name = name; this.type = type; @@ -686,7 +699,7 @@ public String getName() return name; } - public Method getBootstrapMethod() + public BootstrapMethod getBootstrapMethod() { return bootstrapMethod; } @@ -701,7 +714,7 @@ public void accept(MethodVisitor visitor, MethodGenerationContext generationCont { Handle bootstrapMethodHandle = new Handle( Opcodes.H_INVOKESTATIC, - type(bootstrapMethod.getDeclaringClass()).getClassName(), + bootstrapMethod.getOwnerClass().getClassName(), bootstrapMethod.getName(), methodDescription( bootstrapMethod.getReturnType(), diff --git a/src/main/java/io/airlift/bytecode/instruction/InvokeInstruction.java b/src/main/java/io/airlift/bytecode/instruction/InvokeInstruction.java index 3ac15f7..5246c36 100644 --- a/src/main/java/io/airlift/bytecode/instruction/InvokeInstruction.java +++ b/src/main/java/io/airlift/bytecode/instruction/InvokeInstruction.java @@ -259,7 +259,7 @@ private static InstructionNode invoke(OpCode invocationType, Class target, St public static InstructionNode invokeDynamic(String name, ParameterizedType returnType, Iterable parameterTypes, - Method bootstrapMethod, + BootstrapMethod bootstrapMethod, Iterable bootstrapArguments) { return new InvokeDynamicInstruction(name, @@ -269,6 +269,19 @@ public static InstructionNode invokeDynamic(String name, ImmutableList.copyOf(bootstrapArguments)); } + public static InstructionNode invokeDynamic(String name, + ParameterizedType returnType, + Iterable parameterTypes, + Method bootstrapMethod, + Iterable bootstrapArguments) + { + return new InvokeDynamicInstruction(name, + returnType, + parameterTypes, + BootstrapMethod.from(bootstrapMethod), + ImmutableList.copyOf(bootstrapArguments)); + } + public static InstructionNode invokeDynamic(String name, ParameterizedType returnType, Iterable parameterTypes, @@ -278,7 +291,7 @@ public static InstructionNode invokeDynamic(String name, return new InvokeDynamicInstruction(name, returnType, parameterTypes, - bootstrapMethod, + BootstrapMethod.from(bootstrapMethod), ImmutableList.copyOf(bootstrapArguments)); } @@ -290,7 +303,7 @@ public static InstructionNode invokeDynamic(String name, return new InvokeDynamicInstruction(name, type(methodType.returnType()), methodType.parameterList().stream().map(ParameterizedType::type).collect(toImmutableList()), - bootstrapMethod, + BootstrapMethod.from(bootstrapMethod), ImmutableList.copyOf(bootstrapArguments)); } @@ -302,7 +315,7 @@ public static InstructionNode invokeDynamic(String name, return new InvokeDynamicInstruction(name, type(methodType.returnType()), methodType.parameterList().stream().map(ParameterizedType::type).collect(toImmutableList()), - bootstrapMethod, + BootstrapMethod.from(bootstrapMethod), ImmutableList.copyOf(bootstrapArguments)); } @@ -377,13 +390,13 @@ public T accept(BytecodeNode parent, BytecodeVisitor visitor) public static class InvokeDynamicInstruction extends InvokeInstruction { - private final Method bootstrapMethod; + private final BootstrapMethod bootstrapMethod; private final List bootstrapArguments; public InvokeDynamicInstruction(String name, ParameterizedType returnType, Iterable parameterTypes, - Method bootstrapMethod, + BootstrapMethod bootstrapMethod, List bootstrapArguments) { super(INVOKEDYNAMIC, null, name, returnType, parameterTypes); @@ -394,8 +407,9 @@ public InvokeDynamicInstruction(String name, @Override public void accept(MethodVisitor visitor, MethodGenerationContext generationContext) { - Handle bootstrapMethodHandle = new Handle(Opcodes.H_INVOKESTATIC, - type(bootstrapMethod.getDeclaringClass()).getClassName(), + Handle bootstrapMethodHandle = new Handle( + Opcodes.H_INVOKESTATIC, + bootstrapMethod.getOwnerClass().getClassName(), bootstrapMethod.getName(), methodDescription( bootstrapMethod.getReturnType(), @@ -408,7 +422,7 @@ public void accept(MethodVisitor visitor, MethodGenerationContext generationCont bootstrapArguments.toArray(new Object[0])); } - public Method getBootstrapMethod() + public BootstrapMethod getBootstrapMethod() { return bootstrapMethod; } From 0aca0faf182bcd59924d4e715123f2eaa73916ab Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 10 Aug 2023 10:56:39 -0700 Subject: [PATCH 2/3] Rename HiddenClassGenerator to SingleClassGenerator --- ...nerator.java => SingleClassGenerator.java} | 30 +++++++++---------- ...tor.java => TestSingleClassGenerator.java} | 6 ++-- 2 files changed, 18 insertions(+), 18 deletions(-) rename src/main/java/io/airlift/bytecode/{HiddenClassGenerator.java => SingleClassGenerator.java} (75%) rename src/test/java/io/airlift/bytecode/{TestHiddenClassGenerator.java => TestSingleClassGenerator.java} (95%) diff --git a/src/main/java/io/airlift/bytecode/HiddenClassGenerator.java b/src/main/java/io/airlift/bytecode/SingleClassGenerator.java similarity index 75% rename from src/main/java/io/airlift/bytecode/HiddenClassGenerator.java rename to src/main/java/io/airlift/bytecode/SingleClassGenerator.java index e469716..1d5a5dc 100644 --- a/src/main/java/io/airlift/bytecode/HiddenClassGenerator.java +++ b/src/main/java/io/airlift/bytecode/SingleClassGenerator.java @@ -20,50 +20,50 @@ import static io.airlift.bytecode.ClassInfoLoader.createClassInfoLoader; -public class HiddenClassGenerator +public class SingleClassGenerator { private final Lookup lookup; private final ByteCodeGenerator byteCodeGenerator; - public static HiddenClassGenerator hiddenClassGenerator(Lookup lookup) + public static SingleClassGenerator singleClassGenerator(Lookup lookup) { - return new HiddenClassGenerator(lookup, ByteCodeGenerator.byteCodeGenerator()); + return new SingleClassGenerator(lookup, ByteCodeGenerator.byteCodeGenerator()); } - private HiddenClassGenerator(Lookup lookup, ByteCodeGenerator byteCodeGenerator) + private SingleClassGenerator(Lookup lookup, ByteCodeGenerator byteCodeGenerator) { this.lookup = lookup; this.byteCodeGenerator = byteCodeGenerator; } - public HiddenClassGenerator fakeLineNumbers(boolean fakeLineNumbers) + public SingleClassGenerator fakeLineNumbers(boolean fakeLineNumbers) { - return new HiddenClassGenerator(lookup, byteCodeGenerator.fakeLineNumbers(fakeLineNumbers)); + return new SingleClassGenerator(lookup, byteCodeGenerator.fakeLineNumbers(fakeLineNumbers)); } - public HiddenClassGenerator runAsmVerifier(boolean runAsmVerifier) + public SingleClassGenerator runAsmVerifier(boolean runAsmVerifier) { - return new HiddenClassGenerator(lookup, byteCodeGenerator.runAsmVerifier(runAsmVerifier ? new LookupClassLoader(lookup) : null)); + return new SingleClassGenerator(lookup, byteCodeGenerator.runAsmVerifier(runAsmVerifier ? new LookupClassLoader(lookup) : null)); } - public HiddenClassGenerator dumpRawBytecode(boolean dumpRawBytecode) + public SingleClassGenerator dumpRawBytecode(boolean dumpRawBytecode) { - return new HiddenClassGenerator(lookup, byteCodeGenerator.dumpRawBytecode(dumpRawBytecode)); + return new SingleClassGenerator(lookup, byteCodeGenerator.dumpRawBytecode(dumpRawBytecode)); } - public HiddenClassGenerator outputTo(Writer output) + public SingleClassGenerator outputTo(Writer output) { - return new HiddenClassGenerator(lookup, byteCodeGenerator.outputTo(output)); + return new SingleClassGenerator(lookup, byteCodeGenerator.outputTo(output)); } - public HiddenClassGenerator dumpClassFilesTo(Path dumpClassPath) + public SingleClassGenerator dumpClassFilesTo(Path dumpClassPath) { return dumpClassFilesTo(Optional.of(dumpClassPath)); } - public HiddenClassGenerator dumpClassFilesTo(Optional dumpClassPath) + public SingleClassGenerator dumpClassFilesTo(Optional dumpClassPath) { - return new HiddenClassGenerator(lookup, byteCodeGenerator.dumpClassFilesTo(dumpClassPath)); + return new SingleClassGenerator(lookup, byteCodeGenerator.dumpClassFilesTo(dumpClassPath)); } public Class defineHiddenClass(ClassDefinition classDefinition, Class superType, Optional classData) diff --git a/src/test/java/io/airlift/bytecode/TestHiddenClassGenerator.java b/src/test/java/io/airlift/bytecode/TestSingleClassGenerator.java similarity index 95% rename from src/test/java/io/airlift/bytecode/TestHiddenClassGenerator.java rename to src/test/java/io/airlift/bytecode/TestSingleClassGenerator.java index 355968d..a5d3834 100644 --- a/src/test/java/io/airlift/bytecode/TestHiddenClassGenerator.java +++ b/src/test/java/io/airlift/bytecode/TestSingleClassGenerator.java @@ -27,16 +27,16 @@ import static io.airlift.bytecode.Access.PUBLIC; import static io.airlift.bytecode.Access.STATIC; import static io.airlift.bytecode.Access.a; -import static io.airlift.bytecode.HiddenClassGenerator.hiddenClassGenerator; import static io.airlift.bytecode.Parameter.arg; import static io.airlift.bytecode.ParameterizedType.type; +import static io.airlift.bytecode.SingleClassGenerator.singleClassGenerator; import static io.airlift.bytecode.expression.BytecodeExpressions.add; import static java.lang.invoke.MethodHandles.lookup; import static java.nio.file.Files.createTempDirectory; import static org.assertj.core.api.Assertions.assertThat; import static org.testng.Assert.assertEquals; -public class TestHiddenClassGenerator +public class TestSingleClassGenerator { @Test public void testGenerator() @@ -65,7 +65,7 @@ public void testGenerator() try { StringWriter writer = new StringWriter(); - Class clazz = hiddenClassGenerator(lookup()) + Class clazz = singleClassGenerator(lookup()) .fakeLineNumbers(true) .runAsmVerifier(true) .dumpRawBytecode(true) From 2cee19d2c04500b0d1b20ab20926c79524461297 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 10 Aug 2023 11:05:57 -0700 Subject: [PATCH 3/3] Add support for defining standard classes to SingleClassGenerator --- .../bytecode/SingleClassGenerator.java | 98 +++++++++++++++++++ .../bytecode/TestSingleClassGenerator.java | 88 +++++++++++++++++ 2 files changed, 186 insertions(+) diff --git a/src/main/java/io/airlift/bytecode/SingleClassGenerator.java b/src/main/java/io/airlift/bytecode/SingleClassGenerator.java index 1d5a5dc..d0928da 100644 --- a/src/main/java/io/airlift/bytecode/SingleClassGenerator.java +++ b/src/main/java/io/airlift/bytecode/SingleClassGenerator.java @@ -13,12 +13,26 @@ */ package io.airlift.bytecode; +import com.google.common.collect.ImmutableList; +import com.google.common.reflect.Reflection; +import io.airlift.bytecode.instruction.BootstrapMethod; + import java.io.Writer; import java.lang.invoke.MethodHandles.Lookup; +import java.lang.reflect.Method; import java.nio.file.Path; +import java.util.EnumSet; +import java.util.List; import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; +import static io.airlift.bytecode.Access.PRIVATE; +import static io.airlift.bytecode.Access.STATIC; import static io.airlift.bytecode.ClassInfoLoader.createClassInfoLoader; +import static io.airlift.bytecode.ParameterizedType.type; +import static io.airlift.bytecode.expression.BytecodeExpressions.constantClass; +import static io.airlift.bytecode.expression.BytecodeExpressions.constantString; +import static io.airlift.bytecode.expression.BytecodeExpressions.newArray; public class SingleClassGenerator { @@ -66,6 +80,22 @@ public SingleClassGenerator dumpClassFilesTo(Optional dumpClassPath) return new SingleClassGenerator(lookup, byteCodeGenerator.dumpClassFilesTo(dumpClassPath)); } + public Class defineStandardClass(ClassDefinition classDefinition, Class superType, Optional classData) + { + ClassInfoLoader classInfoLoader = createClassInfoLoader(classDefinition, lookup); + byte[] bytecode = byteCodeGenerator.generateByteCode(classInfoLoader, classDefinition); + + Class clazz = StandardClassLoader.defineSingleClass(lookup, classDefinition.getType().getJavaClassName(), bytecode, classData); + + try { + Reflection.initialize(clazz); + } + catch (VerifyError e) { + throw new RuntimeException(e); + } + return clazz.asSubclass(superType); + } + public Class defineHiddenClass(ClassDefinition classDefinition, Class superType, Optional classData) { ClassInfoLoader classInfoLoader = createClassInfoLoader(classDefinition, lookup); @@ -86,6 +116,36 @@ public Class defineHiddenClass(ClassDefinition classDefinition, return definedClassLookup.lookupClass().asSubclass(superType); } + /** + * Creates a bootstrap method that can load class data for a standard class. This is has the same + * behavior as {@link java.lang.invoke.MethodHandles#classDataAt(Lookup, String, Class, int)} except + * that the it works for standard classed created by {@link #defineStandardClass(ClassDefinition, Class, Optional)} + */ + public static BootstrapMethod declareStandardClassDataAtBootstrapMethod(ClassDefinition definition) + { + // Generate a bootstrap method that loads constants from the StandardClassLoader + // The generated code uses reflection to call StandardClassLoader.classDataAt(), + // so the generated code does not need access to the StandardClassLoader class + Parameter lookupParam = Parameter.arg("lookup", Lookup.class); + Parameter nameParam = Parameter.arg("name", String.class); + Parameter typeParam = Parameter.arg("type", Class.class); + Parameter indexParam = Parameter.arg("index", int.class); + + String randomName = "$$bootstrap_" + ThreadLocalRandom.current().nextInt(1_000_000); + MethodDefinition bootstrap = definition.declareMethod(EnumSet.of(PRIVATE, STATIC), randomName, type(Object.class), lookupParam, nameParam, typeParam, indexParam); + bootstrap.addException(Throwable.class); + Scope scope = bootstrap.getScope(); + Variable classLoader = scope.declareVariable("classLoader", bootstrap.getBody(), lookupParam.invoke("lookupClass", Class.class).invoke("getClassLoader", ClassLoader.class)); + bootstrap + .getBody() + .append(classLoader.invoke("getClass", Class.class) + .invoke("getMethod", Method.class, constantString("classDataAt"), newArray(type(Class[].class), ImmutableList.of(constantClass(int.class)))) + .invoke("invoke", Object.class, classLoader.cast(Object.class), newArray(type(Object[].class), ImmutableList.of(indexParam.cast(Integer.class)))) + .ret()); + + return BootstrapMethod.from(bootstrap); + } + private static class LookupClassLoader extends ClassLoader { @@ -108,4 +168,42 @@ protected Class findClass(String name) } } } + + public static class StandardClassLoader + extends ClassLoader + { + public static Class defineSingleClass(Lookup lookup, String className, byte[] bytecode, Optional classData) + { + return new StandardClassLoader(lookup.lookupClass().getClassLoader(), classData).defineClass(className, bytecode); + } + + private final Object classData; + + private StandardClassLoader(ClassLoader parentClassLoader, Optional classData) + { + super(parentClassLoader); + this.classData = classData.orElse(null); + } + + private Class defineClass(String className, byte[] bytecode) + { + return defineClass(className, bytecode, 0, bytecode.length); + } + + public Object classData() + { + return classData; + } + + public Object classDataAt(int index) + { + // this is the same behavior as Lookup.classDataAt + @SuppressWarnings("unchecked") List classData = (List) classData(); + if (classData == null) { + return null; + } + + return classData.get(index); + } + } } diff --git a/src/test/java/io/airlift/bytecode/TestSingleClassGenerator.java b/src/test/java/io/airlift/bytecode/TestSingleClassGenerator.java index a5d3834..edd0859 100644 --- a/src/test/java/io/airlift/bytecode/TestSingleClassGenerator.java +++ b/src/test/java/io/airlift/bytecode/TestSingleClassGenerator.java @@ -14,11 +14,15 @@ package io.airlift.bytecode; import com.google.common.collect.ImmutableList; +import io.airlift.bytecode.SingleClassGenerator.StandardClassLoader; +import io.airlift.bytecode.instruction.BootstrapMethod; import org.testng.annotations.Test; import java.io.StringWriter; +import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; import java.nio.file.Path; +import java.util.List; import java.util.Optional; import static com.google.common.io.MoreFiles.deleteRecursively; @@ -29,12 +33,17 @@ import static io.airlift.bytecode.Access.a; import static io.airlift.bytecode.Parameter.arg; import static io.airlift.bytecode.ParameterizedType.type; +import static io.airlift.bytecode.SingleClassGenerator.declareStandardClassDataAtBootstrapMethod; import static io.airlift.bytecode.SingleClassGenerator.singleClassGenerator; import static io.airlift.bytecode.expression.BytecodeExpressions.add; +import static io.airlift.bytecode.expression.BytecodeExpressions.constantDynamic; import static java.lang.invoke.MethodHandles.lookup; import static java.nio.file.Files.createTempDirectory; import static org.assertj.core.api.Assertions.assertThat; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; public class TestSingleClassGenerator { @@ -88,4 +97,83 @@ public void testGenerator() deleteRecursively(tempDir, ALLOW_INSECURE); } } + + @Test + public void testStandardClassData() + throws Exception + { + ClassDefinition classDefinition = new ClassDefinition( + a(PUBLIC, FINAL), + "io/airlift/bytecode/Example", + type(Object.class)); + + BootstrapMethod bootstrapMethod = declareStandardClassDataAtBootstrapMethod(classDefinition); + + classDefinition.declareMethod(a(PUBLIC, STATIC), "first", type(Object.class)) + .getBody() + .append(constantDynamic("_", type(Object.class), bootstrapMethod, ImmutableList.of(0)).ret()); + + classDefinition.declareMethod(a(PUBLIC, STATIC), "second", type(Object.class)) + .getBody() + .append(constantDynamic("_", type(Object.class), bootstrapMethod, ImmutableList.of(1)).ret()); + + classDefinition.declareMethod(a(PUBLIC, STATIC), "third", type(Object.class)) + .getBody() + .append(constantDynamic("_", type(Object.class), bootstrapMethod, ImmutableList.of(2)).ret()); + + List classData = List.of("a", "b"); + Class clazz = singleClassGenerator(lookup()) + .defineStandardClass(classDefinition, Object.class, Optional.of(classData)); + + assertTrue(clazz.getClassLoader() instanceof StandardClassLoader); + StandardClassLoader standardClassLoader = (StandardClassLoader) clazz.getClassLoader(); + assertSame(standardClassLoader.classData(), classData); + assertEquals(standardClassLoader.classDataAt(0), "a"); + assertEquals(standardClassLoader.classDataAt(1), "b"); + + assertEquals(clazz.getMethod("first").invoke(null), "a"); + assertEquals(clazz.getMethod("second").invoke(null), "b"); + + clazz = singleClassGenerator(lookup()) + .defineStandardClass(classDefinition, Object.class, Optional.empty()); + + assertNull(clazz.getMethod("first").invoke(null)); + assertNull(clazz.getMethod("second").invoke(null)); + } + + @Test + public void testHiddenClassData() + throws Exception + { + ClassDefinition classDefinition = new ClassDefinition( + a(PUBLIC, FINAL), + "io/airlift/bytecode/Example", + type(Object.class)); + + BootstrapMethod bootstrapMethod = BootstrapMethod.from(MethodHandles.class.getMethod("classDataAt", MethodHandles.Lookup.class, String.class, Class.class, int.class)); + + classDefinition.declareMethod(a(PUBLIC, STATIC), "first", type(Object.class)) + .getBody() + .append(constantDynamic("_", type(Object.class), bootstrapMethod, ImmutableList.of(0)).ret()); + + classDefinition.declareMethod(a(PUBLIC, STATIC), "second", type(Object.class)) + .getBody() + .append(constantDynamic("_", type(Object.class), bootstrapMethod, ImmutableList.of(1)).ret()); + + classDefinition.declareMethod(a(PUBLIC, STATIC), "third", type(Object.class)) + .getBody() + .append(constantDynamic("_", type(Object.class), bootstrapMethod, ImmutableList.of(2)).ret()); + + Class clazz = singleClassGenerator(lookup()) + .defineHiddenClass(classDefinition, Object.class, Optional.of(List.of("a", "b"))); + + assertEquals(clazz.getMethod("first").invoke(null), "a"); + assertEquals(clazz.getMethod("second").invoke(null), "b"); + + clazz = singleClassGenerator(lookup()) + .defineHiddenClass(classDefinition, Object.class, Optional.empty()); + + assertNull(clazz.getMethod("first").invoke(null)); + assertNull(clazz.getMethod("second").invoke(null)); + } }