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/HiddenClassGenerator.java b/src/main/java/io/airlift/bytecode/HiddenClassGenerator.java deleted file mode 100644 index e469716..0000000 --- a/src/main/java/io/airlift/bytecode/HiddenClassGenerator.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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; - -import java.io.Writer; -import java.lang.invoke.MethodHandles.Lookup; -import java.nio.file.Path; -import java.util.Optional; - -import static io.airlift.bytecode.ClassInfoLoader.createClassInfoLoader; - -public class HiddenClassGenerator -{ - private final Lookup lookup; - private final ByteCodeGenerator byteCodeGenerator; - - public static HiddenClassGenerator hiddenClassGenerator(Lookup lookup) - { - return new HiddenClassGenerator(lookup, ByteCodeGenerator.byteCodeGenerator()); - } - - private HiddenClassGenerator(Lookup lookup, ByteCodeGenerator byteCodeGenerator) - { - this.lookup = lookup; - this.byteCodeGenerator = byteCodeGenerator; - } - - public HiddenClassGenerator fakeLineNumbers(boolean fakeLineNumbers) - { - return new HiddenClassGenerator(lookup, byteCodeGenerator.fakeLineNumbers(fakeLineNumbers)); - } - - public HiddenClassGenerator runAsmVerifier(boolean runAsmVerifier) - { - return new HiddenClassGenerator(lookup, byteCodeGenerator.runAsmVerifier(runAsmVerifier ? new LookupClassLoader(lookup) : null)); - } - - public HiddenClassGenerator dumpRawBytecode(boolean dumpRawBytecode) - { - return new HiddenClassGenerator(lookup, byteCodeGenerator.dumpRawBytecode(dumpRawBytecode)); - } - - public HiddenClassGenerator outputTo(Writer output) - { - return new HiddenClassGenerator(lookup, byteCodeGenerator.outputTo(output)); - } - - public HiddenClassGenerator dumpClassFilesTo(Path dumpClassPath) - { - return dumpClassFilesTo(Optional.of(dumpClassPath)); - } - - public HiddenClassGenerator dumpClassFilesTo(Optional dumpClassPath) - { - return new HiddenClassGenerator(lookup, byteCodeGenerator.dumpClassFilesTo(dumpClassPath)); - } - - public Class defineHiddenClass(ClassDefinition classDefinition, Class superType, Optional classData) - { - ClassInfoLoader classInfoLoader = createClassInfoLoader(classDefinition, lookup); - byte[] bytecode = byteCodeGenerator.generateByteCode(classInfoLoader, classDefinition); - - Lookup definedClassLookup; - try { - if (classData.isEmpty()) { - definedClassLookup = lookup.defineHiddenClass(bytecode, true); - } - else { - definedClassLookup = lookup.defineHiddenClassWithClassData(bytecode, classData.get(), true); - } - } - catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - return definedClassLookup.lookupClass().asSubclass(superType); - } - - private static class LookupClassLoader - extends ClassLoader - { - private final Lookup lookup; - - public LookupClassLoader(Lookup lookup) - { - this.lookup = lookup; - } - - @Override - protected Class findClass(String name) - throws ClassNotFoundException - { - try { - return lookup.findClass(name); - } - catch (IllegalAccessException e) { - throw new ClassNotFoundException(name, e); - } - } - } -} diff --git a/src/main/java/io/airlift/bytecode/SingleClassGenerator.java b/src/main/java/io/airlift/bytecode/SingleClassGenerator.java new file mode 100644 index 0000000..d0928da --- /dev/null +++ b/src/main/java/io/airlift/bytecode/SingleClassGenerator.java @@ -0,0 +1,209 @@ +/* + * 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; + +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 +{ + private final Lookup lookup; + private final ByteCodeGenerator byteCodeGenerator; + + public static SingleClassGenerator singleClassGenerator(Lookup lookup) + { + return new SingleClassGenerator(lookup, ByteCodeGenerator.byteCodeGenerator()); + } + + private SingleClassGenerator(Lookup lookup, ByteCodeGenerator byteCodeGenerator) + { + this.lookup = lookup; + this.byteCodeGenerator = byteCodeGenerator; + } + + public SingleClassGenerator fakeLineNumbers(boolean fakeLineNumbers) + { + return new SingleClassGenerator(lookup, byteCodeGenerator.fakeLineNumbers(fakeLineNumbers)); + } + + public SingleClassGenerator runAsmVerifier(boolean runAsmVerifier) + { + return new SingleClassGenerator(lookup, byteCodeGenerator.runAsmVerifier(runAsmVerifier ? new LookupClassLoader(lookup) : null)); + } + + public SingleClassGenerator dumpRawBytecode(boolean dumpRawBytecode) + { + return new SingleClassGenerator(lookup, byteCodeGenerator.dumpRawBytecode(dumpRawBytecode)); + } + + public SingleClassGenerator outputTo(Writer output) + { + return new SingleClassGenerator(lookup, byteCodeGenerator.outputTo(output)); + } + + public SingleClassGenerator dumpClassFilesTo(Path dumpClassPath) + { + return dumpClassFilesTo(Optional.of(dumpClassPath)); + } + + 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); + byte[] bytecode = byteCodeGenerator.generateByteCode(classInfoLoader, classDefinition); + + Lookup definedClassLookup; + try { + if (classData.isEmpty()) { + definedClassLookup = lookup.defineHiddenClass(bytecode, true); + } + else { + definedClassLookup = lookup.defineHiddenClassWithClassData(bytecode, classData.get(), true); + } + } + catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + 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 + { + private final Lookup lookup; + + public LookupClassLoader(Lookup lookup) + { + this.lookup = lookup; + } + + @Override + protected Class findClass(String name) + throws ClassNotFoundException + { + try { + return lookup.findClass(name); + } + catch (IllegalAccessException e) { + throw new ClassNotFoundException(name, e); + } + } + } + + 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/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; } diff --git a/src/test/java/io/airlift/bytecode/TestHiddenClassGenerator.java b/src/test/java/io/airlift/bytecode/TestHiddenClassGenerator.java deleted file mode 100644 index 355968d..0000000 --- a/src/test/java/io/airlift/bytecode/TestHiddenClassGenerator.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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; - -import com.google.common.collect.ImmutableList; -import org.testng.annotations.Test; - -import java.io.StringWriter; -import java.lang.reflect.Method; -import java.nio.file.Path; -import java.util.Optional; - -import static com.google.common.io.MoreFiles.deleteRecursively; -import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; -import static io.airlift.bytecode.Access.FINAL; -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.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 -{ - @Test - public void testGenerator() - throws Exception - { - ClassDefinition classDefinition = new ClassDefinition( - a(PUBLIC, FINAL), - "io/airlift/bytecode/Example", - type(Object.class)); - - Parameter argA = arg("a", int.class); - Parameter argB = arg("b", int.class); - - MethodDefinition method = classDefinition.declareMethod( - a(PUBLIC, STATIC), - "add", - type(int.class), - ImmutableList.of(argA, argB)); - - method.getBody() - .append(add(argA, argB)) - .retInt(); - - Path tempDir = createTempDirectory("test"); - - try { - StringWriter writer = new StringWriter(); - - Class clazz = hiddenClassGenerator(lookup()) - .fakeLineNumbers(true) - .runAsmVerifier(true) - .dumpRawBytecode(true) - .outputTo(writer) - .dumpClassFilesTo(tempDir) - .defineHiddenClass(classDefinition, Object.class, Optional.of("class data")); - - Method add = clazz.getMethod("add", int.class, int.class); - assertEquals(add.invoke(null, 13, 42), 55); - - assertThat(writer.toString()) - .contains("00002 I I : I I : IADD") - .contains("public final class io/airlift/bytecode/Example {") - .contains("// declaration: int add(int, int)") - .contains("LINENUMBER 2002 L1"); - - assertThat(tempDir.resolve("io/airlift/bytecode/Example.class")).isRegularFile(); - } - finally { - deleteRecursively(tempDir, ALLOW_INSECURE); - } - } -} diff --git a/src/test/java/io/airlift/bytecode/TestSingleClassGenerator.java b/src/test/java/io/airlift/bytecode/TestSingleClassGenerator.java new file mode 100644 index 0000000..edd0859 --- /dev/null +++ b/src/test/java/io/airlift/bytecode/TestSingleClassGenerator.java @@ -0,0 +1,179 @@ +/* + * 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; + +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; +import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; +import static io.airlift.bytecode.Access.FINAL; +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.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 +{ + @Test + public void testGenerator() + throws Exception + { + ClassDefinition classDefinition = new ClassDefinition( + a(PUBLIC, FINAL), + "io/airlift/bytecode/Example", + type(Object.class)); + + Parameter argA = arg("a", int.class); + Parameter argB = arg("b", int.class); + + MethodDefinition method = classDefinition.declareMethod( + a(PUBLIC, STATIC), + "add", + type(int.class), + ImmutableList.of(argA, argB)); + + method.getBody() + .append(add(argA, argB)) + .retInt(); + + Path tempDir = createTempDirectory("test"); + + try { + StringWriter writer = new StringWriter(); + + Class clazz = singleClassGenerator(lookup()) + .fakeLineNumbers(true) + .runAsmVerifier(true) + .dumpRawBytecode(true) + .outputTo(writer) + .dumpClassFilesTo(tempDir) + .defineHiddenClass(classDefinition, Object.class, Optional.of("class data")); + + Method add = clazz.getMethod("add", int.class, int.class); + assertEquals(add.invoke(null, 13, 42), 55); + + assertThat(writer.toString()) + .contains("00002 I I : I I : IADD") + .contains("public final class io/airlift/bytecode/Example {") + .contains("// declaration: int add(int, int)") + .contains("LINENUMBER 2002 L1"); + + assertThat(tempDir.resolve("io/airlift/bytecode/Example.class")).isRegularFile(); + } + finally { + 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)); + } +}