001/*
002 * Copyright (c) 2012-2018 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others
003 *
004 * This program and the accompanying materials are made available under the
005 * terms of the Eclipse Public License 2.0 which is available at
006 * http://www.eclipse.org/legal/epl-2.0.
007 *
008 * SPDX-License-Identifier: EPL-2.0
009 */
010
011package org.eclipse.golo.runtime.adapters;
012
013import org.objectweb.asm.ClassWriter;
014import org.objectweb.asm.Handle;
015import org.objectweb.asm.MethodVisitor;
016import org.objectweb.asm.Type;
017
018import java.lang.reflect.Constructor;
019import java.lang.reflect.InvocationTargetException;
020import java.lang.reflect.Method;
021import java.lang.reflect.Modifier;
022import java.util.Collections;
023import java.util.HashSet;
024import java.util.Set;
025import java.util.TreeSet;
026
027import static org.eclipse.golo.runtime.adapters.AdapterSupport.DEFINITION_FIELD;
028import static java.lang.reflect.Modifier.*;
029import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
030import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
031import static org.objectweb.asm.Opcodes.*;
032
033public class JavaBytecodeAdapterGenerator {
034
035  private static final Handle ADAPTER_HANDLE;
036
037  static {
038    String bootstrapOwner = "org/eclipse/golo/runtime/adapters/AdapterSupport";
039    String bootstrapMethod = "bootstrap";
040    String description = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;";
041    ADAPTER_HANDLE = new Handle(H_INVOKESTATIC, bootstrapOwner, bootstrapMethod, description, false);
042  }
043
044  private String jvmType(String klass) {
045    return klass.replace(".", "/");
046  }
047
048  private String[] interfaceTypesArray(Set<String> interfaces) {
049    String[] types = new String[interfaces.size()];
050    int i = 0;
051    for (String iface : interfaces) {
052      types[i] = jvmType(iface);
053      i++;
054    }
055    return types;
056  }
057
058  public byte[] generate(AdapterDefinition adapterDefinition) {
059    ClassWriter classWriter = new ClassWriter(COMPUTE_FRAMES | COMPUTE_MAXS);
060    TreeSet<String> interfaces = new TreeSet<>(adapterDefinition.getInterfaces());
061    interfaces.add("gololang.GoloAdapter");
062    classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC,
063        adapterDefinition.getName(), null,
064        jvmType(adapterDefinition.getParent()),
065        interfaceTypesArray(interfaces));
066    makeDefinitionField(classWriter);
067    makeConstructors(classWriter, adapterDefinition);
068    makeFrontendOverrides(classWriter, adapterDefinition);
069    classWriter.visitEnd();
070    return classWriter.toByteArray();
071  }
072
073  public Class<?> generateIntoDefinitionClassloader(AdapterDefinition adapterDefinition) {
074    try {
075      byte[] bytecode = generate(adapterDefinition);
076      ClassLoader classLoader = adapterDefinition.getClassLoader();
077      Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
078      if (!defineClass.isAccessible()) {
079        defineClass.setAccessible(true);
080      }
081      return (Class<?>) defineClass.invoke(classLoader, adapterDefinition.getName(), bytecode, 0, bytecode.length);
082    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
083      throw new RuntimeException(e);
084    }
085  }
086
087  private void makeDefinitionField(ClassWriter classWriter) {
088    classWriter.visitField(ACC_FINAL | ACC_PUBLIC, DEFINITION_FIELD,
089        "Lorg/eclipse/golo/runtime/adapters/AdapterDefinition;", null, null).visitEnd();
090  }
091
092  private void makeFrontendOverrides(ClassWriter classWriter, AdapterDefinition adapterDefinition) {
093    for (Method method : getAllVirtualMethods(adapterDefinition)) {
094      int access = isPublic(method.getModifiers()) ? ACC_PUBLIC : ACC_PROTECTED;
095      if (method.isVarArgs()) {
096        access &= ACC_VARARGS;
097      }
098      String name = method.getName();
099      String descriptor = Type.getMethodDescriptor(method);
100      Class<?>[] exceptionTypes = method.getExceptionTypes();
101      String[] exceptions = new String[exceptionTypes.length];
102      for (int i = 0; i < exceptionTypes.length; i++) {
103        exceptions[i] = Type.getInternalName(exceptionTypes[i]);
104      }
105      MethodVisitor methodVisitor = classWriter.visitMethod(access, name, descriptor, null, exceptions);
106      methodVisitor.visitCode();
107      Class<?>[] parameterTypes = method.getParameterTypes();
108      Type[] indyTypes = new Type[parameterTypes.length + 1];
109      indyTypes[0] = Type.getType(Object.class);
110      methodVisitor.visitVarInsn(ALOAD, 0);
111      int argIndex = 1;
112      for (int i = 0; i < parameterTypes.length; i++) {
113        argIndex = loadArgument(methodVisitor, parameterTypes[i], argIndex);
114        indyTypes[i + 1] = Type.getType(parameterTypes[i]);
115      }
116      methodVisitor.visitInvokeDynamicInsn(method.getName(), Type.getMethodDescriptor(Type.getReturnType(method), indyTypes), ADAPTER_HANDLE);
117      makeReturn(methodVisitor, method.getReturnType());
118      methodVisitor.visitMaxs(0, 0);
119      methodVisitor.visitEnd();
120    }
121  }
122
123  private HashSet<Method> getAllVirtualMethods(AdapterDefinition adapterDefinition) {
124    try {
125      HashSet<Method> methods = new HashSet<>();
126      Class<?> parentClass = Class.forName(adapterDefinition.getParent(), true, adapterDefinition.getClassLoader());
127      for (Method method : parentClass.getMethods()) {
128        if (!isStatic(method.getModifiers()) && !isFinal(method.getModifiers())) {
129          methods.add(method);
130        }
131      }
132      for (Method method : parentClass.getDeclaredMethods()) {
133        if (!isStatic(method.getModifiers()) && !isPrivate(method.getModifiers()) && !isFinal(method.getModifiers())) {
134          methods.add(method);
135        }
136      }
137      for (String iface : adapterDefinition.getInterfaces()) {
138        Collections.addAll(methods, Class.forName(iface, true, adapterDefinition.getClassLoader()).getMethods());
139      }
140      return methods;
141    } catch (ClassNotFoundException e) {
142      throw new RuntimeException(e);
143    }
144  }
145
146  private void makeConstructors(ClassWriter classWriter, AdapterDefinition adapterDefinition) {
147    try {
148      Class<?> parentClass = Class.forName(adapterDefinition.getParent(), true, adapterDefinition.getClassLoader());
149      for (Constructor<?> constructor : parentClass.getDeclaredConstructors()) {
150        if (Modifier.isPublic(constructor.getModifiers()) || Modifier.isProtected(constructor.getModifiers())) {
151          Class<?>[] parameterTypes = constructor.getParameterTypes();
152          Type[] adapterParameterTypes = new Type[parameterTypes.length + 1];
153          adapterParameterTypes[0] = Type.getType(AdapterDefinition.class);
154          for (int i = 1; i < adapterParameterTypes.length; i++) {
155            adapterParameterTypes[i] = Type.getType(parameterTypes[i - 1]);
156          }
157          String descriptor = Type.getMethodDescriptor(Type.VOID_TYPE, adapterParameterTypes);
158          MethodVisitor methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", descriptor, null, null);
159          methodVisitor.visitCode();
160          methodVisitor.visitVarInsn(ALOAD, 0);
161          methodVisitor.visitVarInsn(ALOAD, 1);
162          methodVisitor.visitFieldInsn(PUTFIELD, jvmType(adapterDefinition.getName()), DEFINITION_FIELD,
163              "Lorg/eclipse/golo/runtime/adapters/AdapterDefinition;");
164          methodVisitor.visitVarInsn(ALOAD, 0);
165          int argIndex = 2;
166          for (Class<?> parameterType : parameterTypes) {
167            argIndex = loadArgument(methodVisitor, parameterType, argIndex);
168          }
169          methodVisitor.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(parentClass), "<init>", Type.getConstructorDescriptor(constructor), false);
170          methodVisitor.visitInsn(RETURN);
171          methodVisitor.visitMaxs(0, 0);
172          methodVisitor.visitEnd();
173        }
174      }
175    } catch (ClassNotFoundException e) {
176      throw new RuntimeException(e);
177    }
178  }
179
180  private int loadArgument(MethodVisitor methodVisitor, Class<?> type, int index) {
181    if (type.isPrimitive()) {
182      if (type == Integer.TYPE) {
183        methodVisitor.visitVarInsn(ILOAD, index);
184        return index + 1;
185      } else if (type == Boolean.TYPE) {
186        methodVisitor.visitVarInsn(ILOAD, index);
187        return index + 1;
188      } else if (type == Byte.TYPE) {
189        methodVisitor.visitVarInsn(ILOAD, index);
190        return index + 1;
191      } else if (type == Character.TYPE) {
192        methodVisitor.visitVarInsn(ILOAD, index);
193        return index + 1;
194      } else if (type == Short.TYPE) {
195        methodVisitor.visitVarInsn(ILOAD, index);
196        return index + 1;
197      } else if (type == Double.TYPE) {
198        methodVisitor.visitVarInsn(DLOAD, index);
199        return index + 2;
200      } else if (type == Float.TYPE) {
201        methodVisitor.visitVarInsn(FLOAD, index);
202        return index + 1;
203      } else {
204        methodVisitor.visitVarInsn(LLOAD, index);
205        return index + 2;
206      }
207    } else {
208      methodVisitor.visitVarInsn(ALOAD, index);
209      return index + 1;
210    }
211  }
212
213  private void makeReturn(MethodVisitor methodVisitor, Class<?> type) {
214    if (type.isPrimitive()) {
215      if (type == Integer.TYPE) {
216        methodVisitor.visitInsn(IRETURN);
217      } else if (type == Void.TYPE) {
218        methodVisitor.visitInsn(RETURN);
219      } else if (type == Boolean.TYPE) {
220        methodVisitor.visitInsn(IRETURN);
221      } else if (type == Byte.TYPE) {
222        methodVisitor.visitInsn(IRETURN);
223      } else if (type == Character.TYPE) {
224        methodVisitor.visitInsn(IRETURN);
225      } else if (type == Short.TYPE) {
226        methodVisitor.visitInsn(IRETURN);
227      } else if (type == Double.TYPE) {
228        methodVisitor.visitInsn(DRETURN);
229      } else if (type == Float.TYPE) {
230        methodVisitor.visitInsn(FRETURN);
231      } else if (type == Long.TYPE) {
232        methodVisitor.visitInsn(LRETURN);
233      }
234    } else {
235      methodVisitor.visitInsn(ARETURN);
236    }
237  }
238}