From fe019e367052b8a83ff22617669e4c61775208c0 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 12 Dec 2020 16:36:14 -0800 Subject: [PATCH 1/9] Static import Preconditions --- src/main/java/io/airlift/bytecode/AnnotationDefinition.java | 6 +++--- src/main/java/io/airlift/bytecode/ClassInfo.java | 4 ++-- src/main/java/io/airlift/bytecode/DynamicClassLoader.java | 5 +++-- .../airlift/bytecode/instruction/VariableInstruction.java | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/airlift/bytecode/AnnotationDefinition.java b/src/main/java/io/airlift/bytecode/AnnotationDefinition.java index d46bd79..b8370af 100644 --- a/src/main/java/io/airlift/bytecode/AnnotationDefinition.java +++ b/src/main/java/io/airlift/bytecode/AnnotationDefinition.java @@ -13,7 +13,6 @@ */ package io.airlift.bytecode; -import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.common.primitives.Primitives; import org.objectweb.asm.AnnotationVisitor; @@ -28,6 +27,7 @@ import java.util.Map.Entry; import java.util.Set; +import static com.google.common.base.Preconditions.checkArgument; import static io.airlift.bytecode.ParameterizedType.type; import static java.util.Objects.requireNonNull; @@ -152,14 +152,14 @@ private static void isValidType(Object value) if (value instanceof List) { // todo verify list contains single type for (Object v : (List) value) { - Preconditions.checkArgument(ALLOWED_TYPES.contains(v.getClass()), "List contains invalid type %s", v.getClass()); + checkArgument(ALLOWED_TYPES.contains(v.getClass()), "List contains invalid type %s", v.getClass()); if (v instanceof List) { isValidType(value); } } } else { - Preconditions.checkArgument(ALLOWED_TYPES.contains(value.getClass()), "Invalid value type %s", value.getClass()); + checkArgument(ALLOWED_TYPES.contains(value.getClass()), "Invalid value type %s", value.getClass()); } } diff --git a/src/main/java/io/airlift/bytecode/ClassInfo.java b/src/main/java/io/airlift/bytecode/ClassInfo.java index 2cd566c..12087e7 100644 --- a/src/main/java/io/airlift/bytecode/ClassInfo.java +++ b/src/main/java/io/airlift/bytecode/ClassInfo.java @@ -29,7 +29,6 @@ */ package io.airlift.bytecode; -import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.ClassNode; @@ -37,6 +36,7 @@ import java.util.List; +import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.transform; import static io.airlift.bytecode.ParameterizedType.type; import static io.airlift.bytecode.ParameterizedType.typeFromPathName; @@ -126,7 +126,7 @@ public List getInterfaces() public List getMethods() { - Preconditions.checkState(methods != null, "Methods were not loaded for type %s", type); + checkState(methods != null, "Methods were not loaded for type %s", type); return methods; } diff --git a/src/main/java/io/airlift/bytecode/DynamicClassLoader.java b/src/main/java/io/airlift/bytecode/DynamicClassLoader.java index df3bba2..9ce732c 100644 --- a/src/main/java/io/airlift/bytecode/DynamicClassLoader.java +++ b/src/main/java/io/airlift/bytecode/DynamicClassLoader.java @@ -13,7 +13,6 @@ */ package io.airlift.bytecode; -import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import com.google.common.collect.Sets.SetView; @@ -25,6 +24,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import static com.google.common.base.Preconditions.checkArgument; + public class DynamicClassLoader extends ClassLoader { @@ -59,7 +60,7 @@ public Class defineClass(String className, byte[] bytecode) public Map> defineClasses(Map newClasses) { SetView conflicts = Sets.intersection(pendingClasses.keySet(), newClasses.keySet()); - Preconditions.checkArgument(conflicts.isEmpty(), "The classes %s have already been defined", conflicts); + checkArgument(conflicts.isEmpty(), "The classes %s have already been defined", conflicts); pendingClasses.putAll(newClasses); try { diff --git a/src/main/java/io/airlift/bytecode/instruction/VariableInstruction.java b/src/main/java/io/airlift/bytecode/instruction/VariableInstruction.java index a2f2359..b8b1fd9 100644 --- a/src/main/java/io/airlift/bytecode/instruction/VariableInstruction.java +++ b/src/main/java/io/airlift/bytecode/instruction/VariableInstruction.java @@ -13,7 +13,6 @@ */ package io.airlift.bytecode.instruction; -import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import io.airlift.bytecode.BytecodeNode; import io.airlift.bytecode.BytecodeVisitor; @@ -25,6 +24,7 @@ import java.util.List; import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Preconditions.checkArgument; import static io.airlift.bytecode.OpCode.ILOAD; import static io.airlift.bytecode.OpCode.ISTORE; @@ -129,7 +129,7 @@ public IncrementVariableInstruction(Variable variable, byte increment) { super(variable); String type = variable.getType().getClassName(); - Preconditions.checkArgument(ImmutableList.of("byte", "short", "int").contains(type), "variable must be an byte, short or int, but is %s", type); + checkArgument(ImmutableList.of("byte", "short", "int").contains(type), "variable must be an byte, short or int, but is %s", type); this.increment = increment; } From e3a158c9a66b6a113b1cae33ef687af24032b8fd Mon Sep 17 00:00:00 2001 From: David Phillips Date: Thu, 10 Dec 2020 13:53:16 -0800 Subject: [PATCH 2/9] Update to Java 11 and ASM 9.0 --- .github/workflows/ci.yml | 2 +- pom.xml | 7 +++++-- src/test/java/io/airlift/bytecode/TestClassGenerator.java | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b73652..573355d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: ['8', '11', '15'] + java: ['11', '15'] steps: - uses: actions/checkout@v2 - name: Setup JDK ${{ matrix.java }} diff --git a/pom.xml b/pom.xml index 28b2b4a..7f8032e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ io.airlift airbase - 83 + 104 bytecode @@ -20,7 +20,10 @@ true true - 6.2.1 + 11 + 8 + + 9.0 diff --git a/src/test/java/io/airlift/bytecode/TestClassGenerator.java b/src/test/java/io/airlift/bytecode/TestClassGenerator.java index dd7cc46..6e25595 100644 --- a/src/test/java/io/airlift/bytecode/TestClassGenerator.java +++ b/src/test/java/io/airlift/bytecode/TestClassGenerator.java @@ -77,7 +77,7 @@ public void testGenerator() assertThat(writer.toString()) .contains("00002 I I : I I : IADD") .contains("public final class test/Example {") - .contains("// declaration: int (int, int)") + .contains("// declaration: int add(int, int)") .contains("LINENUMBER 2002 L1"); assertThat(tempDir.resolve("test/Example.class")).isRegularFile(); From f8357a09f0fe43eb987a324f8a3a56f4d1cd92c3 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Wed, 9 Dec 2020 16:42:41 -0800 Subject: [PATCH 3/9] Allow null parent for DynamicClassLoader --- .../java/io/airlift/bytecode/DynamicClassLoader.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/airlift/bytecode/DynamicClassLoader.java b/src/main/java/io/airlift/bytecode/DynamicClassLoader.java index 9ce732c..f0b97b7 100644 --- a/src/main/java/io/airlift/bytecode/DynamicClassLoader.java +++ b/src/main/java/io/airlift/bytecode/DynamicClassLoader.java @@ -128,8 +128,15 @@ protected Class loadClass(String name, boolean resolve) } } - Class clazz = getParent().loadClass(name); - return resolveClass(clazz, resolve); + // try parent if present + ClassLoader parent = getParent(); + if (parent != null) { + Class clazz = parent.loadClass(name); + return resolveClass(clazz, resolve); + } + + // try bootstrap class loader + return super.loadClass(name, resolve); } } From 688bf96bf42895d6167d40fb9c1b49a9014138e1 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Wed, 9 Dec 2020 16:58:35 -0800 Subject: [PATCH 4/9] Allow invokedynamic with arbitrary bootstrap method --- .../io/airlift/bytecode/BytecodeBlock.java | 12 +++ .../expression/BytecodeExpressions.java | 18 +++++ .../InvokeDynamicBytecodeExpression.java | 6 +- .../instruction/InvokeInstruction.java | 80 ++++++++++++++++--- 4 files changed, 103 insertions(+), 13 deletions(-) diff --git a/src/main/java/io/airlift/bytecode/BytecodeBlock.java b/src/main/java/io/airlift/bytecode/BytecodeBlock.java index 160b519..6a84c63 100644 --- a/src/main/java/io/airlift/bytecode/BytecodeBlock.java +++ b/src/main/java/io/airlift/bytecode/BytecodeBlock.java @@ -17,6 +17,7 @@ import io.airlift.bytecode.debug.LineNumberNode; import io.airlift.bytecode.instruction.Constant; import io.airlift.bytecode.instruction.InvokeInstruction; +import io.airlift.bytecode.instruction.InvokeInstruction.BootstrapMethod; import io.airlift.bytecode.instruction.JumpInstruction; import io.airlift.bytecode.instruction.LabelNode; import io.airlift.bytecode.instruction.TypeInstruction; @@ -494,6 +495,17 @@ public BytecodeNode invokeDynamic(String name, return this; } + public BytecodeBlock 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 39ca494..e929041 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.InvokeInstruction.BootstrapMethod; import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; @@ -524,6 +525,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 86c061a..652a7c9 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.InvokeInstruction.BootstrapMethod; -import java.lang.reflect.Method; import java.util.List; import static com.google.common.collect.Iterables.transform; @@ -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/InvokeInstruction.java b/src/main/java/io/airlift/bytecode/instruction/InvokeInstruction.java index cf50813..6ae97dc 100644 --- a/src/main/java/io/airlift/bytecode/instruction/InvokeInstruction.java +++ b/src/main/java/io/airlift/bytecode/instruction/InvokeInstruction.java @@ -31,6 +31,7 @@ import java.util.List; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.Iterables.transform; import static io.airlift.bytecode.MethodDefinition.methodDescription; import static io.airlift.bytecode.OpCode.INVOKEDYNAMIC; @@ -39,6 +40,7 @@ import static io.airlift.bytecode.OpCode.INVOKESTATIC; import static io.airlift.bytecode.OpCode.INVOKEVIRTUAL; import static io.airlift.bytecode.ParameterizedType.type; +import static java.util.Arrays.stream; import static java.util.Objects.requireNonNull; @SuppressWarnings("UnusedDeclaration") @@ -263,7 +265,7 @@ public static InstructionNode invokeDynamic(String name, return new InvokeDynamicInstruction(name, returnType, parameterTypes, - bootstrapMethod, + BootstrapMethod.from(bootstrapMethod), ImmutableList.copyOf(bootstrapArguments)); } @@ -276,7 +278,7 @@ public static InstructionNode invokeDynamic(String name, return new InvokeDynamicInstruction(name, returnType, parameterTypes, - bootstrapMethod, + BootstrapMethod.from(bootstrapMethod), ImmutableList.copyOf(bootstrapArguments)); } @@ -288,7 +290,7 @@ public static InstructionNode invokeDynamic(String name, return new InvokeDynamicInstruction(name, type(methodType.returnType()), transform(methodType.parameterList(), ParameterizedType::type), - bootstrapMethod, + BootstrapMethod.from(bootstrapMethod), ImmutableList.copyOf(bootstrapArguments)); } @@ -300,6 +302,19 @@ public static InstructionNode invokeDynamic(String name, return new InvokeDynamicInstruction(name, type(methodType.returnType()), transform(methodType.parameterList(), ParameterizedType::type), + BootstrapMethod.from(bootstrapMethod), + ImmutableList.copyOf(bootstrapArguments)); + } + + public static InstructionNode invokeDynamic(String name, + ParameterizedType returnType, + Iterable parameterTypes, + BootstrapMethod bootstrapMethod, + Iterable bootstrapArguments) + { + return new InvokeDynamicInstruction(name, + returnType, + parameterTypes, bootstrapMethod, ImmutableList.copyOf(bootstrapArguments)); } @@ -375,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); @@ -393,11 +408,9 @@ public InvokeDynamicInstruction(String name, public void accept(MethodVisitor visitor, MethodGenerationContext generationContext) { Handle bootstrapMethodHandle = new Handle(Opcodes.H_INVOKESTATIC, - type(bootstrapMethod.getDeclaringClass()).getClassName(), + bootstrapMethod.getOwnerClass().getClassName(), bootstrapMethod.getName(), - methodDescription( - bootstrapMethod.getReturnType(), - bootstrapMethod.getParameterTypes()), + methodDescription(bootstrapMethod.getReturnType(), bootstrapMethod.getParameterType()), false); visitor.visitInvokeDynamicInsn(getName(), @@ -406,7 +419,7 @@ public void accept(MethodVisitor visitor, MethodGenerationContext generationCont bootstrapArguments.toArray(new Object[bootstrapArguments.size()])); } - public Method getBootstrapMethod() + public BootstrapMethod getBootstrapMethod() { return bootstrapMethod; } @@ -440,4 +453,51 @@ private static void checkUnqualifiedName(String name) CharMatcher invalid = CharMatcher.anyOf(".;[/<>"); checkArgument(invalid.matchesNoneOf(name), "invalid name: %s", name); } + + public static class BootstrapMethod + { + private final ParameterizedType ownerClass; + private final String name; + private final ParameterizedType returnType; + private final List parameterType; + + public BootstrapMethod(ParameterizedType ownerClass, String name, ParameterizedType returnType, List parameterType) + { + this.ownerClass = requireNonNull(ownerClass, "ownerClass is null"); + this.name = requireNonNull(name, "name is null"); + this.returnType = requireNonNull(returnType, "returnType is null"); + this.parameterType = ImmutableList.copyOf(requireNonNull(parameterType, "parameterType is null")); + } + + public ParameterizedType getOwnerClass() + { + return ownerClass; + } + + public String getName() + { + return name; + } + + public ParameterizedType getReturnType() + { + return returnType; + } + + public List getParameterType() + { + return parameterType; + } + + 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())); + } + } } From 5a812a3eabc5f2bec34b40e3f3667c3c03db5e08 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Wed, 9 Dec 2020 21:15:18 -0800 Subject: [PATCH 5/9] Improve default proxy class names --- .../java/io/airlift/bytecode/FastMethodHandleProxies.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java b/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java index b32d41d..3c86b0e 100644 --- a/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java +++ b/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java @@ -30,7 +30,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.concurrent.atomic.AtomicLong; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.MoreCollectors.onlyElement; @@ -38,6 +37,7 @@ import static io.airlift.bytecode.Access.PUBLIC; import static io.airlift.bytecode.Access.SYNTHETIC; import static io.airlift.bytecode.Access.a; +import static io.airlift.bytecode.BytecodeUtils.uniqueClassName; import static io.airlift.bytecode.ClassGenerator.classGenerator; import static io.airlift.bytecode.FastMethodHandleProxies.Bootstrap.BOOTSTRAP_METHOD; import static io.airlift.bytecode.Parameter.arg; @@ -49,7 +49,7 @@ public final class FastMethodHandleProxies { - private static final AtomicLong CLASS_ID = new AtomicLong(); + private static final String BASE_PACKAGE = FastMethodHandleProxies.class.getPackage().getName() + ".proxy"; private FastMethodHandleProxies() {} @@ -63,7 +63,7 @@ private FastMethodHandleProxies() {} */ public static T asInterfaceInstance(Class type, MethodHandle target) { - String className = "$gen." + type.getName() + "_" + CLASS_ID.incrementAndGet(); + String className = uniqueClassName(BASE_PACKAGE, type.getSimpleName()).getClassName(); return asInterfaceInstance(className, type, target); } From 3cfe575131e387c4e9ddd0deab4a0d8a90bf5fd5 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Wed, 9 Dec 2020 22:15:08 -0800 Subject: [PATCH 6/9] Extract method for generating proxy method --- .../bytecode/FastMethodHandleProxies.java | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java b/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java index 3c86b0e..dc878a7 100644 --- a/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java +++ b/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java @@ -89,32 +89,49 @@ public static T asInterfaceInstance(String className, Class type, MethodH classDefinition.declareDefaultConstructor(a(PUBLIC)); Method method = getSingleAbstractMethod(type); - Class[] parameterTypes = method.getParameterTypes(); - MethodHandle adaptedTarget = target.asType(methodType(method.getReturnType(), parameterTypes)); + target = target.asType(methodType(method.getReturnType(), method.getParameterTypes())); + defineProxyMethod(classDefinition, method); + + // note this will not work if interface class is not visible from this class loader, + // but we must use this class loader to ensure the bootstrap method is visible + ClassLoader targetClassLoader = FastMethodHandleProxies.class.getClassLoader(); + DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(targetClassLoader, ImmutableMap.of(0L, target)); + Class newClass = classGenerator(dynamicClassLoader).defineClass(classDefinition, type); + try { + return newClass.getDeclaredConstructor().newInstance(); + } + catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + private static void defineProxyMethod(ClassDefinition classDefinition, Method target) + { List parameters = new ArrayList<>(); + Class[] parameterTypes = target.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { parameters.add(arg("arg" + i, parameterTypes[i])); } - MethodDefinition methodDefinition = classDefinition.declareMethod( + MethodDefinition method = classDefinition.declareMethod( a(PUBLIC), - method.getName(), - type(method.getReturnType()), + target.getName(), + type(target.getReturnType()), parameters); BytecodeNode invocation = invokeDynamic( BOOTSTRAP_METHOD, ImmutableList.of(), - method.getName(), - method.getReturnType(), + target.getName(), + target.getReturnType(), parameters) .ret(); ImmutableList.Builder exceptionTypes = ImmutableList.builder(); exceptionTypes.add(type(RuntimeException.class), type(Error.class)); - for (Class exceptionType : method.getExceptionTypes()) { - methodDefinition.addException(exceptionType.asSubclass(Throwable.class)); + for (Class exceptionType : target.getExceptionTypes()) { + method.addException(exceptionType.asSubclass(Throwable.class)); exceptionTypes.add(type(exceptionType)); } @@ -129,19 +146,7 @@ public static T asInterfaceInstance(String className, Class type, MethodH new CatchBlock(new BytecodeBlock().throwObject(), exceptionTypes.build()), new CatchBlock(throwUndeclared, ImmutableList.of()))); - methodDefinition.getBody().append(invocation); - - // note this will not work if interface class is not visible from this class loader, - // but we must use this class loader to ensure the bootstrap method is visible - ClassLoader targetClassLoader = FastMethodHandleProxies.class.getClassLoader(); - DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(targetClassLoader, ImmutableMap.of(0L, adaptedTarget)); - Class newClass = classGenerator(dynamicClassLoader).defineClass(classDefinition, type); - try { - return newClass.getDeclaredConstructor().newInstance(); - } - catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } + method.getBody().append(invocation); } private static Method getSingleAbstractMethod(Class type) From cb8be037e580a7db2367bfcd2dd04bc756082374 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Wed, 9 Dec 2020 22:18:49 -0800 Subject: [PATCH 7/9] Generate bootstrap method inside proxy class --- .../bytecode/FastMethodHandleProxies.java | 114 +++++++++++++----- 1 file changed, 83 insertions(+), 31 deletions(-) diff --git a/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java b/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java index dc878a7..4c59122 100644 --- a/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java +++ b/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java @@ -17,12 +17,14 @@ import com.google.common.collect.ImmutableMap; import io.airlift.bytecode.control.TryCatch; import io.airlift.bytecode.control.TryCatch.CatchBlock; +import io.airlift.bytecode.expression.BytecodeExpression; +import io.airlift.bytecode.instruction.InvokeInstruction.BootstrapMethod; import java.lang.invoke.CallSite; import java.lang.invoke.ConstantCallSite; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandleProxies; -import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -30,20 +32,28 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.MoreCollectors.onlyElement; 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.SYNTHETIC; import static io.airlift.bytecode.Access.a; import static io.airlift.bytecode.BytecodeUtils.uniqueClassName; import static io.airlift.bytecode.ClassGenerator.classGenerator; -import static io.airlift.bytecode.FastMethodHandleProxies.Bootstrap.BOOTSTRAP_METHOD; import static io.airlift.bytecode.Parameter.arg; import static io.airlift.bytecode.ParameterizedType.type; import static io.airlift.bytecode.ParameterizedType.typeFromJavaClassName; +import static io.airlift.bytecode.expression.BytecodeExpressions.constantClass; +import static io.airlift.bytecode.expression.BytecodeExpressions.constantLong; +import static io.airlift.bytecode.expression.BytecodeExpressions.constantString; import static io.airlift.bytecode.expression.BytecodeExpressions.invokeDynamic; +import static io.airlift.bytecode.expression.BytecodeExpressions.invokeStatic; +import static io.airlift.bytecode.expression.BytecodeExpressions.newArray; +import static io.airlift.bytecode.expression.BytecodeExpressions.newInstance; import static java.lang.invoke.MethodType.methodType; import static java.util.Arrays.stream; @@ -51,6 +61,29 @@ public final class FastMethodHandleProxies { private static final String BASE_PACKAGE = FastMethodHandleProxies.class.getPackage().getName() + ".proxy"; + private static final Method LOOKUP_FIND_VIRTUAL; + private static final Method LOOKUP_LOOKUP_CLASS; + private static final Method CLASS_GET_CLASSLOADER; + private static final Method CLASSLOADER_GET_CLASS; + private static final Method METHODTYPE; + private static final Method METHODHANDLE_INVOKE; + private static final Method MAP_GET; + + static { + try { + LOOKUP_FIND_VIRTUAL = Lookup.class.getMethod("findVirtual", Class.class, String.class, MethodType.class); + LOOKUP_LOOKUP_CLASS = Lookup.class.getMethod("lookupClass"); + CLASS_GET_CLASSLOADER = Class.class.getMethod("getClassLoader"); + CLASSLOADER_GET_CLASS = ClassLoader.class.getMethod("getClass"); + METHODTYPE = MethodType.class.getMethod("methodType", Class.class); + METHODHANDLE_INVOKE = MethodHandle.class.getMethod("invokeWithArguments", Object[].class); + MAP_GET = Map.class.getMethod("get", Object.class); + } + catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } + private FastMethodHandleProxies() {} /** @@ -93,10 +126,9 @@ public static T asInterfaceInstance(String className, Class type, MethodH defineProxyMethod(classDefinition, method); - // note this will not work if interface class is not visible from this class loader, - // but we must use this class loader to ensure the bootstrap method is visible - ClassLoader targetClassLoader = FastMethodHandleProxies.class.getClassLoader(); - DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(targetClassLoader, ImmutableMap.of(0L, target)); + defineBootstrapMethod(classDefinition); + + DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(type.getClassLoader(), ImmutableMap.of(0L, target)); Class newClass = classGenerator(dynamicClassLoader).defineClass(classDefinition, type); try { return newClass.getDeclaredConstructor().newInstance(); @@ -121,10 +153,17 @@ private static void defineProxyMethod(ClassDefinition classDefinition, Method ta parameters); BytecodeNode invocation = invokeDynamic( - BOOTSTRAP_METHOD, + new BootstrapMethod( + classDefinition.getType(), + "$bootstrap", + type(CallSite.class), + ImmutableList.of(type(Lookup.class), type(String.class), type(MethodType.class))), ImmutableList.of(), target.getName(), - target.getReturnType(), + type(target.getReturnType()), + parameters.stream() + .map(BytecodeExpression::getType) + .collect(toImmutableList()), parameters) .ret(); @@ -149,6 +188,42 @@ private static void defineProxyMethod(ClassDefinition classDefinition, Method ta method.getBody().append(invocation); } + private static void defineBootstrapMethod(ClassDefinition classDefinition) + { + Parameter callerLookup = arg("callerLookup", Lookup.class); + + MethodDefinition method = classDefinition.declareMethod( + a(PUBLIC, STATIC), + "$bootstrap", + type(CallSite.class), + callerLookup, + arg("name", String.class), + arg("type", MethodType.class)); + + BytecodeExpression classLoader = callerLookup + .invoke(LOOKUP_LOOKUP_CLASS) + .invoke(CLASS_GET_CLASSLOADER); + + BytecodeExpression getCallSiteBindingsHandle = callerLookup.invoke( + LOOKUP_FIND_VIRTUAL, + classLoader.invoke(CLASSLOADER_GET_CLASS), + constantString("getCallSiteBindings"), + invokeStatic(METHODTYPE, constantClass(Map.class))); + + BytecodeExpression callSiteBindings = getCallSiteBindingsHandle.invoke( + METHODHANDLE_INVOKE, + newArray(type(Object[].class), ImmutableList.of(classLoader))); + + BytecodeExpression binding = callSiteBindings + .cast(Map.class) + .invoke(MAP_GET, constantLong(0).cast(Object.class)) + .cast(MethodHandle.class); + + BytecodeExpression callSite = newInstance(ConstantCallSite.class, binding); + + method.getBody().append(callSite.ret()); + } + private static Method getSingleAbstractMethod(Class type) { return stream(type.getMethods()) @@ -173,27 +248,4 @@ private static boolean notMethodMatches(Method method, String name, Class ret !name.equals(method.getName()) || !Arrays.equals(method.getParameterTypes(), parameterTypes); } - - public static final class Bootstrap - { - public static final Method BOOTSTRAP_METHOD; - - static { - try { - BOOTSTRAP_METHOD = Bootstrap.class.getMethod("bootstrap", MethodHandles.Lookup.class, String.class, MethodType.class); - } - catch (NoSuchMethodException e) { - throw new AssertionError(e); - } - } - - private Bootstrap() {} - - @SuppressWarnings("unused") - public static CallSite bootstrap(MethodHandles.Lookup callerLookup, String name, MethodType type) - { - DynamicClassLoader classLoader = (DynamicClassLoader) callerLookup.lookupClass().getClassLoader(); - return new ConstantCallSite(classLoader.getCallSiteBindings().get(0L)); - } - } } From 6ae712beadffa7410ee8a1f27939cbe73d987342 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Thu, 10 Dec 2020 18:50:37 -0800 Subject: [PATCH 8/9] Make getSingleAbstractMethod public --- src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java b/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java index 4c59122..0bcff98 100644 --- a/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java +++ b/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java @@ -224,7 +224,7 @@ private static void defineBootstrapMethod(ClassDefinition classDefinition) method.getBody().append(callSite.ret()); } - private static Method getSingleAbstractMethod(Class type) + public static Method getSingleAbstractMethod(Class type) { return stream(type.getMethods()) .filter(method -> Modifier.isAbstract(method.getModifiers())) From ea38d1db612e77fbf181845c7ae22497cef6136e Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 12 Dec 2020 15:48:28 -0800 Subject: [PATCH 9/9] Add toDirectMethodHandle --- .../bytecode/FastMethodHandleProxies.java | 80 +++++++++++++++++-- .../bytecode/TestFastMethodHandleProxies.java | 65 ++++++++++++--- 2 files changed, 128 insertions(+), 17 deletions(-) diff --git a/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java b/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java index 0bcff98..f60d2e1 100644 --- a/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java +++ b/src/main/java/io/airlift/bytecode/FastMethodHandleProxies.java @@ -54,6 +54,7 @@ import static io.airlift.bytecode.expression.BytecodeExpressions.invokeStatic; import static io.airlift.bytecode.expression.BytecodeExpressions.newArray; import static io.airlift.bytecode.expression.BytecodeExpressions.newInstance; +import static java.lang.invoke.MethodHandles.lookup; import static java.lang.invoke.MethodType.methodType; import static java.util.Arrays.stream; @@ -61,6 +62,8 @@ public final class FastMethodHandleProxies { private static final String BASE_PACKAGE = FastMethodHandleProxies.class.getPackage().getName() + ".proxy"; + private static final String DIRECT_METHOD_NAME = "invokeDirect"; + private static final Method LOOKUP_FIND_VIRTUAL; private static final Method LOOKUP_LOOKUP_CLASS; private static final Method CLASS_GET_CLASSLOADER; @@ -153,11 +156,7 @@ private static void defineProxyMethod(ClassDefinition classDefinition, Method ta parameters); BytecodeNode invocation = invokeDynamic( - new BootstrapMethod( - classDefinition.getType(), - "$bootstrap", - type(CallSite.class), - ImmutableList.of(type(Lookup.class), type(String.class), type(MethodType.class))), + getBootstrapMethod(classDefinition), ImmutableList.of(), target.getName(), type(target.getReturnType()), @@ -188,6 +187,68 @@ private static void defineProxyMethod(ClassDefinition classDefinition, Method ta method.getBody().append(invocation); } + public static MethodHandle toDirectMethodHandle(MethodHandle target, ClassLoader parentClassLoader) + { + String className = uniqueClassName(BASE_PACKAGE, "MethodHandle").getClassName(); + return toDirectMethodHandle(className, target, parentClassLoader); + } + + public static MethodHandle toDirectMethodHandle(String className, MethodHandle target, ClassLoader parentClassLoader) + { + try { + lookup().revealDirect(target); + return target; + } + catch (RuntimeException ignored) { + } + + ClassDefinition classDefinition = new ClassDefinition( + a(PUBLIC, FINAL, SYNTHETIC), + typeFromJavaClassName(className), + type(Object.class)); + + classDefinition.declareDefaultConstructor(a(PUBLIC)); + + defineDirectMethod(classDefinition, target.type()); + + defineBootstrapMethod(classDefinition); + + DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(parentClassLoader, ImmutableMap.of(0L, target)); + Class newClass = classGenerator(dynamicClassLoader).defineClass(classDefinition, Object.class); + try { + return lookup().findStatic(newClass, DIRECT_METHOD_NAME, target.type()); + } + catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + private static void defineDirectMethod(ClassDefinition classDefinition, MethodType methodType) + { + List parameters = new ArrayList<>(); + for (int i = 0; i < methodType.parameterCount(); i++) { + parameters.add(arg("arg" + i, methodType.parameterType(i))); + } + + MethodDefinition method = classDefinition.declareMethod( + a(PUBLIC, STATIC), + DIRECT_METHOD_NAME, + type(methodType.returnType()), + parameters); + + BytecodeExpression invocation = invokeDynamic( + getBootstrapMethod(classDefinition), + ImmutableList.of(), + DIRECT_METHOD_NAME, + type(methodType.returnType()), + parameters.stream() + .map(BytecodeExpression::getType) + .collect(toImmutableList()), + parameters); + + method.getBody().append(invocation.ret()); + } + private static void defineBootstrapMethod(ClassDefinition classDefinition) { Parameter callerLookup = arg("callerLookup", Lookup.class); @@ -224,6 +285,15 @@ private static void defineBootstrapMethod(ClassDefinition classDefinition) method.getBody().append(callSite.ret()); } + private static BootstrapMethod getBootstrapMethod(ClassDefinition classDefinition) + { + return new BootstrapMethod( + classDefinition.getType(), + "$bootstrap", + type(CallSite.class), + ImmutableList.of(type(Lookup.class), type(String.class), type(MethodType.class))); + } + public static Method getSingleAbstractMethod(Class type) { return stream(type.getMethods()) diff --git a/src/test/java/io/airlift/bytecode/TestFastMethodHandleProxies.java b/src/test/java/io/airlift/bytecode/TestFastMethodHandleProxies.java index 5a42bdb..b3a78e5 100644 --- a/src/test/java/io/airlift/bytecode/TestFastMethodHandleProxies.java +++ b/src/test/java/io/airlift/bytecode/TestFastMethodHandleProxies.java @@ -17,16 +17,24 @@ import org.testng.annotations.Test; import java.io.IOException; +import java.lang.invoke.CallSite; +import java.lang.invoke.LambdaMetafactory; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandleProxies; +import java.lang.invoke.MethodType; import java.lang.invoke.MutableCallSite; +import java.lang.reflect.Method; import java.lang.reflect.UndeclaredThrowableException; -import java.util.function.Consumer; +import java.util.function.BiConsumer; import java.util.function.IntSupplier; import java.util.function.LongFunction; import java.util.function.LongUnaryOperator; +import static com.google.common.base.Throwables.throwIfUnchecked; +import static io.airlift.bytecode.FastMethodHandleProxies.getSingleAbstractMethod; +import static io.airlift.bytecode.FastMethodHandleProxies.toDirectMethodHandle; import static java.lang.invoke.MethodHandles.lookup; +import static java.lang.invoke.MethodHandles.privateLookupIn; import static java.lang.invoke.MethodType.methodType; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.testng.Assert.assertEquals; @@ -40,7 +48,7 @@ public void testBasic() assertInterface( LongUnaryOperator.class, lookup().findStatic(getClass(), "increment", methodType(long.class, long.class)), - addOne -> assertEquals(addOne.applyAsLong(1), 2L)); + (addOne, wrapped) -> assertEquals(addOne.applyAsLong(1), 2L)); } private static long increment(long x) @@ -55,7 +63,7 @@ public void testGeneric() assertInterface( LongFunction.class, lookup().findStatic(getClass(), "incrementAndPrint", methodType(String.class, long.class)), - print -> assertEquals(print.apply(1), "2")); + (print, wrapped) -> assertEquals(print.apply(1), "2")); } private static String incrementAndPrint(long x) @@ -70,7 +78,7 @@ public void testObjectAndDefaultMethods() assertInterface( StringLength.class, lookup().findStatic(getClass(), "stringLength", methodType(int.class, String.class)), - length -> { + (length, wrapped) -> { assertEquals(length.length("abc"), 3); assertEquals(length.theAnswer(), 42); }); @@ -101,7 +109,7 @@ public void testUncheckedException() assertInterface( Runnable.class, lookup().findStatic(getClass(), "throwUncheckedException", methodType(void.class)), - runnable -> assertThatThrownBy(runnable::run) + (runnable, wrapped) -> assertThatThrownBy(runnable::run) .isInstanceOf(VerifyException.class)); } @@ -117,9 +125,17 @@ public void testCheckedException() assertInterface( Runnable.class, lookup().findStatic(getClass(), "throwCheckedException", methodType(void.class)), - runnable -> assertThatThrownBy(runnable::run) - .isInstanceOf(UndeclaredThrowableException.class) - .hasCauseInstanceOf(IOException.class)); + (runnable, wrapped) -> { + if (wrapped) { + assertThatThrownBy(runnable::run) + .isInstanceOf(UndeclaredThrowableException.class) + .hasCauseInstanceOf(IOException.class); + } + else { + assertThatThrownBy(runnable::run) + .isInstanceOf(IOException.class); + } + }); } private static void throwCheckedException() @@ -139,7 +155,7 @@ public void testMutableCallSite() assertInterface( IntSupplier.class, callSite.dynamicInvoker(), - supplier -> { + (supplier, wrapped) -> { callSite.setTarget(one); assertEquals(supplier.getAsInt(), 1); callSite.setTarget(two); @@ -157,9 +173,34 @@ private static int two() return 2; } - private static void assertInterface(Class interfaceType, MethodHandle target, Consumer consumer) + private static void assertInterface(Class interfaceType, MethodHandle target, BiConsumer consumer) + { + consumer.accept(MethodHandleProxies.asInterfaceInstance(interfaceType, target), true); + consumer.accept(FastMethodHandleProxies.asInterfaceInstance(interfaceType, target), true); + consumer.accept(toInterfaceInstance(interfaceType, target), false); + } + + @SuppressWarnings("unchecked") + private static T toInterfaceInstance(Class type, MethodHandle target) { - consumer.accept(MethodHandleProxies.asInterfaceInstance(interfaceType, target)); - consumer.accept(FastMethodHandleProxies.asInterfaceInstance(interfaceType, target)); + Method method = getSingleAbstractMethod(type); + target = toDirectMethodHandle(target, type.getClassLoader()); + try { + MethodHandle handle = lookup().unreflect(method); + MethodType methodType = handle.type().dropParameterTypes(0, 1); + Class targetClass = lookup().revealDirect(target).getDeclaringClass(); + CallSite callSite = LambdaMetafactory.metafactory( + privateLookupIn(targetClass, lookup()), + method.getName(), + methodType(type), + methodType, + target, + methodType); + return (T) callSite.getTarget().invoke(); + } + catch (Throwable t) { + throwIfUnchecked(t); + throw new IllegalArgumentException(t); + } } }