Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import groovy.lang.GroovyRuntimeException;
import groovy.lang.MetaMethod;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
Expand All @@ -29,6 +28,7 @@
import java.io.IOException;
import java.io.Serial;
import java.io.Serializable;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
Expand Down Expand Up @@ -70,6 +70,19 @@ public CachedClass getDeclaringClass() {
return declaringClass;
}

/**
* Returns a {@link MethodHandle} pointing directly to the underlying target method,
* or {@code null} if not available.
* <p>
* Generated DGM adapter classes override this to provide a pre-computed handle that
* avoids the boxing overhead of {@link #invoke(Object, Object[])}.
*
* @return a method handle for direct invocation, or {@code null}
*/
public MethodHandle getTargetMethodHandle() {
return null;
}

public static class Proxy extends GeneratedMetaMethod {
private volatile MetaMethod proxy;
private final String className;
Expand Down
130 changes: 93 additions & 37 deletions src/main/java/org/codehaus/groovy/tools/DgmConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,14 @@

import static org.objectweb.asm.Opcodes.AALOAD;
import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.ACONST_NULL;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ARETURN;
import static org.objectweb.asm.Opcodes.ASTORE;
import static org.objectweb.asm.Opcodes.GETSTATIC;
import static org.objectweb.asm.Opcodes.GOTO;
import static org.objectweb.asm.Opcodes.ICONST_0;
import static org.objectweb.asm.Opcodes.ICONST_1;
Expand All @@ -55,7 +58,9 @@
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.IRETURN;
import static org.objectweb.asm.Opcodes.PUTSTATIC;
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Type.getMethodType;

/**
* Generates {@code GeneratedMetaMethod} adapter classes and metadata for the
Expand All @@ -64,6 +69,8 @@
public class DgmConverter {

private static final System.Logger LOGGER = System.getLogger(DgmConverter.class.getName());
private static final String TARGET = "TARGET";
private static final String METHOD_HANDLE_CLASS_NAME = "Ljava/lang/invoke/MethodHandle;";

/**
* Generates DGM adapter classes into the target directory.
Expand All @@ -79,36 +86,29 @@
targetDirectory = args[1];
if (!targetDirectory.endsWith("/")) targetDirectory += "/";
}
List<CachedMethod> cachedMethodsList = new ArrayList<CachedMethod>();
for (Class aClass : DefaultGroovyMethods.DGM_LIKE_CLASSES) {
List<CachedMethod> cachedMethodsList = new ArrayList<>();
for (Class<?> aClass : DefaultGroovyMethods.DGM_LIKE_CLASSES) {
Collections.addAll(cachedMethodsList, ReflectionCache.getCachedClass(aClass).getMethods());
}
final CachedMethod[] cachedMethods = cachedMethodsList.toArray(CachedMethod.EMPTY_ARRAY);

List<GeneratedMetaMethod.DgmMethodRecord> records = new ArrayList<GeneratedMetaMethod.DgmMethodRecord>();
List<GeneratedMetaMethod.DgmMethodRecord> records = new ArrayList<>();

int cur = 0;
for (CachedMethod method : cachedMethods) {
if (!method.isStatic() || !method.isPublic())
continue;
if (skipMethod(method)) continue;

if (method.getAnnotation(Deprecated.class) != null)
continue;

if (method.getParameterTypes().length == 0)
continue;

final Class returnType = method.getReturnType();
final Class<?> returnType = method.getReturnType();

final String className = "org/codehaus/groovy/runtime/dgm$" + cur++;

GeneratedMetaMethod.DgmMethodRecord record = new GeneratedMetaMethod.DgmMethodRecord();
records.add(record);
GeneratedMetaMethod.DgmMethodRecord dgmMethodRecord = new GeneratedMetaMethod.DgmMethodRecord();
records.add(dgmMethodRecord);

record.methodName = method.getName();
record.returnType = method.getReturnType();
record.parameters = method.getNativeParameterTypes();
record.className = className;
dgmMethodRecord.methodName = method.getName();
dgmMethodRecord.returnType = method.getReturnType();
dgmMethodRecord.parameters = method.getNativeParameterTypes();
dgmMethodRecord.className = className;

ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
cw.visit(CompilerConfiguration.DEFAULT.getBytecodeVersion(), ACC_PUBLIC, className, null, "org/codehaus/groovy/reflection/GeneratedMetaMethod", null);
Expand All @@ -117,12 +117,16 @@

final String methodDescriptor = BytecodeHelper.getMethodDescriptor(returnType, method.getNativeParameterTypes());

createTargetMethodHandleField(cw, method, className);

createInvokeMethod(method, cw, returnType, methodDescriptor);

createDoMethodInvokeMethod(method, cw, className, returnType, methodDescriptor);

createIsValidMethodMethod(method, cw, className);

createGetTargetMethodHandleMethod(cw, className);

cw.visitEnd();

final byte[] bytes = cw.toByteArray();
Expand All @@ -141,6 +145,16 @@
LOGGER.log(INFO, "Saved {0} dgm records to: {1}/META-INF/dgminfo", cur, targetDirectory);
}

private static boolean skipMethod(CachedMethod method) {
if (!method.isStatic() || !method.isPublic())
return true;

if (method.getAnnotation(Deprecated.class) != null)
return true;

return method.getParameterTypes().length == 0;
}

private static void createConstructor(ClassWriter cw) {
MethodVisitor mv;
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/String;Lorg/codehaus/groovy/reflection/CachedClass;Ljava/lang/Class;[Ljava/lang/Class;)V", null, null);
Expand Down Expand Up @@ -188,7 +202,10 @@
}
}

private static void createDoMethodInvokeMethod(CachedMethod method, ClassWriter cw, String className, Class returnType, String methodDescriptor) {
/**
* Generates bytecode for method invocation with argument coercion
*/
private static void createDoMethodInvokeMethod(CachedMethod method, ClassWriter cw, String className, Class<?> returnType, String methodDescriptor) {
MethodVisitor mv;
mv = cw.visitMethod(ACC_PUBLIC + ACC_FINAL, "doMethodInvoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", null, null);
mv.visitCode();
Expand All @@ -207,7 +224,7 @@

// cast argument to parameter class, inclusive unboxing
// for methods with primitive types
Class type = method.getParameterTypes()[1].getTheClass();
Class<?> type = method.getParameterTypes()[1].getTheClass();
BytecodeHelper.doCast(mv, type);
} else {
mv.visitVarInsn(ALOAD, 0);
Expand All @@ -216,9 +233,14 @@
mv.visitVarInsn(ASTORE, 2);
mv.visitVarInsn(ALOAD, 1);
BytecodeHelper.doCast(mv, method.getParameterTypes()[0].getTheClass());
loadParameters(method, 2, mv);
loadParameters(method, mv);
}
writeMethodCall(method, returnType, methodDescriptor, mv);
}

private static void writeMethodCall(CachedMethod method, Class<?> returnType, String methodDescriptor, MethodVisitor mv) {
mv.visitMethodInsn(INVOKESTATIC, BytecodeHelper.getClassInternalName(method.getDeclaringClass().getTheClass()), method.getName(), methodDescriptor, false);
// Handles primitive return types via autoboxing
if (returnType == void.class) {
mv.visitInsn(ACONST_NULL);
} else if (returnType.isPrimitive()) {
Expand All @@ -230,46 +252,80 @@
mv.visitEnd();
}

private static void createInvokeMethod(CachedMethod method, ClassWriter cw, Class returnType, String methodDescriptor) {
/**
* Generates bytecode for method invocation with return handling
*/
private static void createInvokeMethod(CachedMethod method, ClassWriter cw, Class<?> returnType, String methodDescriptor) {
MethodVisitor mv;
mv = cw.visitMethod(ACC_PUBLIC, "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 1);
BytecodeHelper.doCast(mv, method.getParameterTypes()[0].getTheClass());
loadParameters(method, 2, mv);
mv.visitMethodInsn(INVOKESTATIC, BytecodeHelper.getClassInternalName(method.getDeclaringClass().getTheClass()), method.getName(), methodDescriptor, false);
if (returnType == void.class) {
mv.visitInsn(ACONST_NULL);
} else if (returnType.isPrimitive()) {
Class<?> wrapperType = TypeUtil.autoboxType(returnType);
mv.visitMethodInsn(INVOKESTATIC, BytecodeHelper.getClassInternalName(wrapperType), "valueOf", "(" + BytecodeHelper.getTypeDescription(returnType) + ")" + BytecodeHelper.getTypeDescription(wrapperType), false);
}
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
loadParameters(method, mv);
writeMethodCall(method, returnType, methodDescriptor, mv);
}

/**
* Loads and casts the non-receiver arguments for the supplied cached
* method from an {@code Object[]} local variable.
*
* @param method the cached method whose parameters are being loaded
* @param argumentIndex the local-variable slot containing the argument array
* @param mv the visitor receiving the bytecode instructions
*/
protected static void loadParameters(CachedMethod method, int argumentIndex, MethodVisitor mv) {
protected static void loadParameters(CachedMethod method, MethodVisitor mv) {
CachedClass[] parameters = method.getParameterTypes();
int size = parameters.length - 1;
for (int i = 0; i < size; i++) {
// unpack argument from Object[]
mv.visitVarInsn(ALOAD, argumentIndex);
mv.visitVarInsn(ALOAD, 2);
BytecodeHelper.pushConstant(mv, i);
mv.visitInsn(AALOAD);

// cast argument to parameter class, inclusive unboxing
// for methods with primitive types
Class type = parameters[i + 1].getTheClass();
Class<?> type = parameters[i + 1].getTheClass();
BytecodeHelper.doCast(mv, type);
}
}

private static void createTargetMethodHandleField(ClassWriter cw, CachedMethod method, String className) {
// private static final java.lang.invoke.MethodHandle TARGET
cw.visitField(ACC_PRIVATE + ACC_STATIC + ACC_FINAL,
TARGET, METHOD_HANDLE_CLASS_NAME, null, null).visitEnd();

// static initializer
MethodVisitor mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
mv.visitCode();

// Lookup lookup = java.lang.invoke.MethodHandles.lookup()
mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "lookup",
"()Ljava/lang/invoke/MethodHandles$Lookup;", false);
// Class ownerClass = <declaring class>.class
String ownerInternal = BytecodeHelper.getClassInternalName(method.getDeclaringClass().getTheClass());
mv.visitLdcInsn(org.objectweb.asm.Type.getObjectType(ownerInternal));
// String methodName = "<method name>"
mv.visitLdcInsn(method.getName());
// MethodType methodType = MethodType.methodType(<return>, <param1>, <param2>, ...)
mv.visitLdcInsn(getMethodType(method.getDescriptor()));
// TARGET = lookup.findStatic(ownerClass, methodName, methodType)
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic",
"(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false);
mv.visitFieldInsn(PUTSTATIC, className, TARGET, METHOD_HANDLE_CLASS_NAME);

mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}

private static void createGetTargetMethodHandleMethod(ClassWriter cw, String className) {
MethodVisitor mv;
// public MethodHandle getTargetMethodHandle() { return TARGET; }

Check warning on line 322 in src/main/java/org/codehaus/groovy/tools/DgmConverter.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This block of commented-out lines of code should be removed.

See more on https://sonarcloud.io/project/issues?id=apache_groovy&issues=AZ6vDMM6rFMaaeLmRp4H&open=AZ6vDMM6rFMaaeLmRp4H&pullRequest=2598
mv = cw.visitMethod(ACC_PUBLIC, "getTargetMethodHandle",
"()Ljava/lang/invoke/MethodHandle;", null, null);
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, className, TARGET, METHOD_HANDLE_CLASS_NAME);
mv.visitInsn(ARETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
}
Loading
Loading