package act.mail.bytecode; /*- * #%L * ACT Framework * %% * Copyright (C) 2014 - 2017 ActFramework * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import act.asm.Label; import act.asm.MethodVisitor; import act.asm.Opcodes; import act.asm.Type; import act.asm.tree.*; import act.controller.meta.HandlerParamMetaInfo; import act.controller.meta.LocalVariableMetaInfo; import act.mail.MailerContext; import act.mail.meta.SenderMethodMetaInfo; import act.util.AsmTypes; import org.osgl.logging.L; import org.osgl.logging.Logger; import org.osgl.util.C; import org.osgl.util.E; import org.osgl.util.S; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.concurrent.Future; public class SenderEnhancer extends MethodVisitor implements Opcodes { private static final Logger logger = L.get(SenderEnhancer.class); private SenderMethodMetaInfo info; private MethodVisitor next; private int paramIdShift = 0; public SenderEnhancer(final MethodVisitor mv, SenderMethodMetaInfo meta, final int access, final String name, final String desc, final String signature, final String[] exceptions) { super(ASM5, new MethodNode(access, name, desc, signature, exceptions)); this.info = meta; this.next = mv; } @Override public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { if (!"this".equals(name)) { int paramId = index; if (!info.isStatic()) { paramId--; } paramId -= paramIdShift; if (paramId < info.paramCount()) { HandlerParamMetaInfo param = info.param(paramId); param.name(name); if (Type.getType(long.class).equals(param.type()) || Type.getType(double.class).equals(param.type())) { paramIdShift++; } } LocalVariableMetaInfo local = new LocalVariableMetaInfo(index, name, desc, start, end); info.addLocal(local); } super.visitLocalVariable(name, desc, signature, start, end, index); } @Override public void visitEnd() { MethodNode mn = (MethodNode) mv; transform(mn); mn.accept(next); super.visitEnd(); } private void transform(MethodNode mn) { new Transformer(mn, info).doIt(); } private static class Transformer { MethodNode mn; InsnList instructions; private SenderMethodMetaInfo meta; List<Label> lblList = C.newSizedList(20); Transformer(MethodNode mn, SenderMethodMetaInfo meta) { this.mn = mn; this.meta = meta; this.instructions = mn.instructions; } void doIt() { ListIterator<AbstractInsnNode> itr = instructions.iterator(); Segment cur = null; while (itr.hasNext()) { AbstractInsnNode insn = itr.next(); if (insn.getType() == AbstractInsnNode.LABEL) { cur = new Segment(((LabelNode) insn).getLabel(), meta, instructions, itr, this); } else if (null != cur) { if (cur.handle(insn)) { cur = null; } } } } private static abstract class InstructionHandler { Segment segment; SenderMethodMetaInfo meta; InstructionHandler(Segment segment, SenderMethodMetaInfo meta) { this.segment = segment; this.meta = meta; } protected abstract boolean handle(AbstractInsnNode node); protected void refreshIteratorNext() { segment.itr.previous(); segment.itr.next(); } } private static class Segment { Label startLabel; InsnList instructions; SenderMethodMetaInfo meta; ListIterator<AbstractInsnNode> itr; Transformer trans; private Map<Integer, InstructionHandler> handlers; Segment(Label start, SenderMethodMetaInfo meta, InsnList instructions, ListIterator<AbstractInsnNode> itr, Transformer trans) { E.NPE(meta); this.startLabel = start; this.meta = meta; this.instructions = instructions; this.itr = itr; this.trans = trans; this.handlers = C.map( AbstractInsnNode.METHOD_INSN, new InvocationHandler(this, meta) ); trans.lblList.add(start); } protected boolean handle(AbstractInsnNode node) { InstructionHandler handler = handlers.get(node.getType()); if (null != handler) { return handler.handle(node); } else { return false; } } } private static class InvocationHandler extends InstructionHandler { InvocationHandler(Segment segment, SenderMethodMetaInfo meta) { super(segment, meta); } @Override protected boolean handle(AbstractInsnNode node) { MethodInsnNode n = (MethodInsnNode) node; Type type = Type.getMethodType(n.desc); Type retType = type.getReturnType(); //String method = n.name; //String owner = Type.getType(n.owner).toString(); if (MailerContext.class.getName().equals(retType.getClassName())) { return injectCreatingMailerContextCode(n); } else if (Future.class.getName().equals(retType.getClassName()) && "send".equals(n.name)) { return injectRenderArgSetCode(n); } return false; } private boolean injectCreatingMailerContextCode(AbstractInsnNode insnNode) { AbstractInsnNode node = insnNode.getNext(); InsnList list = new InsnList(); if (node instanceof VarInsnNode) { VarInsnNode varNode = (VarInsnNode) node; if (varNode.getOpcode() == ASTORE) { meta.appCtxLocalVariableTableIndex(varNode.var); list.add(new TypeInsnNode(NEW, AsmTypes.MAILER_CONTEXT_INTERNAL_NAME)); list.add(new InsnNode(DUP)); list.add(new MethodInsnNode(INVOKESTATIC, AsmTypes.APP_INTERNAL_NAME, "instance", "()" + AsmTypes.APP_DESC, false)); String confId = meta.configId(); if (null == confId) { confId = "default"; } list.add(new LdcInsnNode(confId)); list.add(new MethodInsnNode(INVOKESPECIAL, AsmTypes.MAILER_CONTEXT_INTERNAL_NAME, "<init>", "(Lact/app/App;Ljava/lang/String;)V", false)); segment.instructions.insertBefore(node, list); segment.instructions.remove(insnNode); return true; } return false; } else { list.add(new TypeInsnNode(NEW, AsmTypes.MAILER_CONTEXT_INTERNAL_NAME)); list.add(new InsnNode(DUP)); list.add(new MethodInsnNode(INVOKESTATIC, AsmTypes.APP_INTERNAL_NAME, "instance", "()" + AsmTypes.APP_DESC, false)); String confId = meta.configId(); if (null == confId) { confId = "default"; } list.add(new LdcInsnNode(confId)); list.add(new MethodInsnNode(INVOKESPECIAL, AsmTypes.MAILER_CONTEXT_INTERNAL_NAME, "<init>", "(Lact/app/App;Ljava/lang/String;)V", false)); segment.instructions.insertBefore(node, list); segment.instructions.remove(insnNode); node = node.getNext(); while (node.getOpcode() != POP) { node = node.getNext(); } int maxLocal = segment.trans.mn.maxLocals; segment.trans.mn.maxLocals++; segment.instructions.insertBefore(node, new VarInsnNode(ASTORE, maxLocal)); segment.instructions.remove(node); meta.appCtxLocalVariableTableIndex(maxLocal); return true; } } private boolean injectRenderArgSetCode(AbstractInsnNode invokeNode) { if (!segment.meta.hasLocalVariableTable()) { logger.warn("local variable table info not found. AppContext render args will not be automatically populated"); } AbstractInsnNode node = invokeNode.getPrevious(); List<LoadInsnInfo> loadInsnInfoList = C.newList(); String templatePath = null; while (null != node) { int type = node.getType(); boolean breakWhile = false; switch (type) { case AbstractInsnNode.LABEL: case AbstractInsnNode.FRAME: node = node.getNext(); breakWhile = true; break; case AbstractInsnNode.VAR_INSN: VarInsnNode n = (VarInsnNode) node; if (0 == n.var && !segment.meta.isStatic()) { // skip "this" break; } LoadInsn insn = LoadInsn.of(n.getOpcode()); if (insn.isStoreInsn()) { break; } LoadInsnInfo info = new LoadInsnInfo(insn, n.var); loadInsnInfoList.add(info); break; case AbstractInsnNode.LDC_INSN: LdcInsnNode ldc = (LdcInsnNode) node; if (null != templatePath) { logger.warn("Cannot have more than one template path parameter in the render call. Template path[%s] ignored", templatePath); } else if (!(ldc.cst instanceof String)) { logger.warn("Template path must be strictly String type. Found: %s", ldc.cst); } else { templatePath = ldc.cst.toString(); } default: } if (breakWhile) { break; } node = node.getPrevious(); } InsnList list = new InsnList(); int len = loadInsnInfoList.size(); // SetRenderArgs enhancement int ctxId = meta.appCtxLocalVariableTableIndex(); if (ctxId < 0) { list.add(new TypeInsnNode(NEW, AsmTypes.MAILER_CONTEXT_INTERNAL_NAME)); list.add(new InsnNode(DUP)); list.add(new MethodInsnNode(INVOKESTATIC, AsmTypes.APP_INTERNAL_NAME, "instance", "()" + AsmTypes.APP_DESC, false)); String confId = meta.configId(); if (null == confId) { confId = "default"; } list.add(new LdcInsnNode(confId)); list.add(new MethodInsnNode(INVOKESPECIAL, AsmTypes.MAILER_CONTEXT_INTERNAL_NAME, "<init>", "(Lact/app/App;Ljava/lang/String;)V", false)); int maxLocal = segment.trans.mn.maxLocals; list.add(new VarInsnNode(ASTORE, maxLocal)); segment.trans.mn.maxLocals++; ctxId = maxLocal; } list.add(new VarInsnNode(ALOAD, ctxId)); S.Buffer sb = S.newBuffer(); for (int i = 0; i < len; ++i) { LoadInsnInfo info = loadInsnInfoList.get(i); info.appendTo(list, segment, sb); } LdcInsnNode ldc = new LdcInsnNode(sb.toString()); list.add(ldc); MethodInsnNode invokeRenderArg = new MethodInsnNode(INVOKEVIRTUAL, AsmTypes.MAILER_CONTEXT_INTERNAL_NAME, RENDER_ARG_NAMES_NM, RENDER_ARG_NAMES_DESC, false); list.add(invokeRenderArg); // setTemplatePath enhancement if (null != templatePath) { LdcInsnNode insnNode = new LdcInsnNode(templatePath); list.add(insnNode); MethodInsnNode invokeTemplatePath = new MethodInsnNode(INVOKEVIRTUAL, AsmTypes.MAILER_CONTEXT_INTERNAL_NAME, TEMPLATE_PATH_NM, TEMPLATE_PATH_DESC, false); list.add(invokeTemplatePath); } else { String className = meta.classInfo().className(); String method = meta.name(); list.add(new LdcInsnNode(className)); list.add(new LdcInsnNode(method)); list.add(new MethodInsnNode(INVOKEVIRTUAL, AsmTypes.MAILER_CONTEXT_INTERNAL_NAME, SENDER_PATH_NM, SENDER_PATH_DESC, false)); } InsnNode pop = new InsnNode(POP); list.add(pop); list.add(new VarInsnNode(ALOAD, ctxId)); String method = meta.appCtxLocalVariableTableIndex() < 0 ? "doSend" : "doSendWithoutLoadThreadLocal"; MethodInsnNode realSend = new MethodInsnNode(INVOKESTATIC, "act/mail/Mailer$Util", method, "(Lact/mail/MailerContext;)Ljava/util/concurrent/Future;", false); list.add(realSend); segment.instructions.insertBefore(node, list); while (node != invokeNode) { AbstractInsnNode n0 = node.getNext(); segment.instructions.remove(node); node = n0; } segment.instructions.remove(invokeNode); return true; } } private static final int _I = 'I'; private static final int _Z = 'Z'; private static final int _S = 'S'; private static final int _B = 'B'; private static final int _C = 'C'; private enum LoadInsn { I(ILOAD) { void appendTo(InsnList list, int varIndex, String type) { super.appendTo(list, varIndex, type); String owner, desc; switch (type.hashCode()) { case _I: owner = "java/lang/Integer"; desc = "(I)Ljava/lang/Integer;"; break; case _Z: owner = "java/lang/Boolean"; desc = "(Z)Ljava/lang/Boolean;"; break; case _S: owner = "java/lang/Short"; desc = "(S)Ljava/lang/Short"; break; case _B: owner = "java/lang/Byte"; desc = "(B)Ljava/lang/Byte;"; break; case _C: owner = "java/lang/Character"; desc = "(C)Ljava/lang/Character;"; break; default: throw E.unexpected("int var type not recognized: %s", type); } MethodInsnNode method = new MethodInsnNode(INVOKESTATIC, owner, "valueOf", desc, false); list.add(method); } }, L(LLOAD) { @Override void appendTo(InsnList list, int varIndex, String type) { super.appendTo(list, varIndex, type); MethodInsnNode method = new MethodInsnNode(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false); list.add(method); } }, F(FLOAD) { @Override void appendTo(InsnList list, int varIndex, String type) { super.appendTo(list, varIndex, type); MethodInsnNode method = new MethodInsnNode(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false); list.add(method); } }, D(DLOAD) { @Override void appendTo(InsnList list, int varIndex, String type) { super.appendTo(list, varIndex, type); MethodInsnNode method = new MethodInsnNode(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false); list.add(method); } }, A(ALOAD), Store(-1) { @Override void appendTo(InsnList list, int varIndex, String type) { throw E.unsupport(); } }; private int opcode; LoadInsn(int opcode) { this.opcode = opcode; } static LoadInsn of(int opcode) { switch (opcode) { case ILOAD: return I; case LLOAD: return L; case FLOAD: return F; case DLOAD: return D; case ALOAD: return A; default: return Store; } } boolean isStoreInsn() { return this == Store; } void appendTo(InsnList list, int varIndex, String type) { VarInsnNode load = new VarInsnNode(opcode, varIndex); list.add(load); } } private static class LoadInsnInfo { LoadInsn insn; int index; LoadInsnInfo(LoadInsn insn, int index) { this.insn = insn; this.index = index; } void appendTo(InsnList list, Segment segment, S.Buffer paramNames) { LocalVariableMetaInfo var = var(segment); if (null == var) return; LdcInsnNode ldc = new LdcInsnNode(var.name()); list.add(ldc); insn.appendTo(list, index, var.type()); MethodInsnNode invokeRenderArg = new MethodInsnNode(INVOKEVIRTUAL, AsmTypes.MAILER_CONTEXT_INTERNAL_NAME, RENDER_NM, RENDER_DESC, false); list.add(invokeRenderArg); if (paramNames.length() != 0) { paramNames.append(','); } paramNames.append(var.name()); } LocalVariableMetaInfo var(Segment segment) { Label lbl = segment.startLabel; int pos = -1; List<Label> lblList = segment.trans.lblList; while (null != lbl) { LocalVariableMetaInfo var = segment.meta.localVariable(index, lbl); if (null != var) return var; if (-1 == pos) { pos = lblList.indexOf(lbl); if (pos <= 0) { return null; } } lbl = lblList.get(--pos); } return null; } @Override public String toString() { return S.fmt("%sLoad %s", insn, index); } } } private static final String GET_MAILER_CTX_DESC = "()" + AsmTypes.MAILER_CONTEXT_DESC; private static final String RENDER_NM = "renderArg"; private static final String RENDER_DESC = AsmTypes.methodDesc(MailerContext.class, String.class, Object.class); private static final String TEMPLATE_PATH_NM = "templatePath"; private static final String SENDER_PATH_NM = "senderPath"; private static final String TEMPLATE_PATH_DESC = AsmTypes.methodDesc(MailerContext.class, String.class); private static final String SENDER_PATH_DESC = AsmTypes.methodDesc(MailerContext.class, String.class, String.class); private static final String RENDER_ARG_NAMES_NM = "__appRenderArgNames"; private static final String RENDER_ARG_NAMES_DESC = AsmTypes.methodDesc(MailerContext.class, String.class); }