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