package openperipheral.interfaces.oc.asm; import com.google.common.base.Optional; import li.cil.oc.api.machine.Arguments; import li.cil.oc.api.machine.Callback; import li.cil.oc.api.machine.Context; import openperipheral.adapter.IMethodExecutor; import openperipheral.util.DocUtils; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.Method; public class CommonMethodsBuilder { public static final String TARGET_FIELD_NAME = "target"; public static final String METHODS_FIELD_NAME = "methods"; private static final Type OBJECT_TYPE = Type.getType(Object.class); private static final Type STRING_TYPE = Type.getType(String.class); public static final Type EXECUTOR_TYPE = Type.getType(IMethodExecutor.class); public static final Type EXECUTORS_TYPE = Type.getType(IMethodExecutor[].class); private static final Type OBJECTS_TYPE = Type.getType(Object[].class); private static final Type CONTEXT_TYPE = Type.getType(Context.class); private static final Type ARGUMENTS_TYPE = Type.getType(Arguments.class); private static final Type CALLBACK_TYPE = Type.getType(Callback.class); private static final Type METHOD_STORE_TYPE = Type.getType(MethodsStore.class); private static final Type BASE_TYPE = Type.getType(ICallerBase.class); private static final Type SIGNALLING_BASE_TYPE = Type.getType(ISignallingCallerBase.class); private static final Type METHOD_STORE_COLLECT_TYPE = Type.getMethodType(EXECUTORS_TYPE, Type.INT_TYPE); public static final Type WRAP_TYPE = Type.getMethodType(OBJECTS_TYPE, CONTEXT_TYPE, ARGUMENTS_TYPE); public static final Type CALLER_METHOD_TYPE = Type.getMethodType(OBJECTS_TYPE, OBJECT_TYPE, EXECUTOR_TYPE, CONTEXT_TYPE, ARGUMENTS_TYPE); public static final Type SIGNALLING_CALLER_METHOD_TYPE = Type.getMethodType(OBJECTS_TYPE, OBJECT_TYPE, EXECUTOR_TYPE, STRING_TYPE, CONTEXT_TYPE, ARGUMENTS_TYPE); private static final Type INVALID_STATE_TYPE = Type.getMethodType(OBJECTS_TYPE); private static final Type CLINIT_TYPE = Type.getMethodType(Type.VOID_TYPE); private final ClassWriter writer; private final String clsName; private final Type targetType; public CommonMethodsBuilder(ClassWriter writer, String clsName, Type targetType) { this.writer = writer; this.clsName = clsName; this.targetType = targetType; } private static void visitIntConst(MethodVisitor mv, int value) { switch (value) { case 0: case 1: case 2: case 3: case 4: case 5: mv.visitInsn(Opcodes.ICONST_0 + value); break; default: mv.visitLdcInsn(value); break; } } public void addMethodsField() { writer.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, METHODS_FIELD_NAME, EXECUTORS_TYPE.getDescriptor(), null, null); } public void addTargetField() { writer.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, TARGET_FIELD_NAME, targetType.getDescriptor(), null, null); } public void createScriptMethodWrapper(String methodName, int methodIndex, IMethodExecutor executor) { final String generatedMethodName = methodName.replaceAll("[^A-Za-z0-9_]", "_") + "$" + Integer.toString(methodIndex); final MethodVisitor wrap = writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, generatedMethodName, WRAP_TYPE.getDescriptor(), null, null); final Optional<String> returnSignal = executor.getReturnSignal(); final AnnotationVisitor av = wrap.visitAnnotation(CALLBACK_TYPE.getDescriptor(), true); av.visit("value", methodName); // functions with return signal always return immediately av.visit("direct", executor.isAsynchronous() || returnSignal.isPresent()); av.visit("doc", DocUtils.doc(executor.description())); av.visitEnd(); // TODO: getter/setter av.visitEnd(); wrap.visitCode(); wrap.visitVarInsn(Opcodes.ALOAD, 0); // this wrap.visitInsn(Opcodes.DUP); // this, this wrap.visitFieldInsn(Opcodes.GETFIELD, clsName, TARGET_FIELD_NAME, targetType.getDescriptor()); // this, target Label skip = new Label(); wrap.visitInsn(Opcodes.DUP); // this, target, target wrap.visitJumpInsn(Opcodes.IFNONNULL, skip); // this, target wrap.visitInsn(Opcodes.POP); // this wrap.visitMethodInsn(Opcodes.INVOKEINTERFACE, BASE_TYPE.getInternalName(), "invalidState", INVALID_STATE_TYPE.getDescriptor(), true); // result wrap.visitInsn(Opcodes.ARETURN); wrap.visitLabel(skip); wrap.visitFieldInsn(Opcodes.GETSTATIC, clsName, METHODS_FIELD_NAME, EXECUTORS_TYPE.getDescriptor()); // this, target, methods[] visitIntConst(wrap, methodIndex); // this, target, methods[], methodIndex wrap.visitInsn(Opcodes.AALOAD); // this, target, executor if (returnSignal.isPresent()) wrap.visitLdcInsn(returnSignal.get()); wrap.visitVarInsn(Opcodes.ALOAD, 1); // this, target, executor, (returnSignal), context wrap.visitVarInsn(Opcodes.ALOAD, 2); // this, target, executor, (returnSignal), context, args if (returnSignal.isPresent()) { final String baseCallName = executor.isAsynchronous()? "callSignallingAsync" : "callSignallingSync"; wrap.visitMethodInsn(Opcodes.INVOKEINTERFACE, SIGNALLING_BASE_TYPE.getInternalName(), baseCallName, SIGNALLING_CALLER_METHOD_TYPE.getDescriptor(), true); } else { wrap.visitMethodInsn(Opcodes.INVOKEINTERFACE, BASE_TYPE.getInternalName(), "call", CALLER_METHOD_TYPE.getDescriptor(), true); } wrap.visitInsn(Opcodes.ARETURN); wrap.visitMaxs(0, 0); wrap.visitEnd(); } public void addExposedMethodBypass(Method method, Type sourceInterface) { MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, method.getName(), method.getDescriptor(), null, null); mv.visitCode(); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitFieldInsn(Opcodes.GETFIELD, clsName, TARGET_FIELD_NAME, targetType.getDescriptor()); Type[] args = method.getArgumentTypes(); for (int i = 0; i < args.length; i++) mv.visitVarInsn(args[i].getOpcode(Opcodes.ILOAD), i + 1); mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, sourceInterface.getInternalName(), method.getName(), method.getDescriptor(), true); Type returnType = method.getReturnType(); mv.visitInsn(returnType.getOpcode(Opcodes.IRETURN)); mv.visitMaxs(0, 0); mv.visitEnd(); } public void addClassInit(int methodsDropboxId) { MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_STATIC, "<clinit>", CLINIT_TYPE.getDescriptor(), null, null); mv.visitCode(); visitIntConst(mv, methodsDropboxId); mv.visitMethodInsn(Opcodes.INVOKESTATIC, METHOD_STORE_TYPE.getInternalName(), "collect", METHOD_STORE_COLLECT_TYPE.getDescriptor(), false); mv.visitFieldInsn(Opcodes.PUTSTATIC, clsName, METHODS_FIELD_NAME, EXECUTORS_TYPE.getDescriptor()); mv.visitInsn(Opcodes.RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } }