package act.controller.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.app.ActionContext; import act.asm.*; import act.asm.tree.AbstractInsnNode; import act.asm.tree.*; import act.controller.meta.HandlerMethodMetaInfo; import act.controller.meta.HandlerParamMetaInfo; import act.controller.meta.LocalVariableMetaInfo; import act.util.AsmTypes; import org.osgl.logging.LogManager; import org.osgl.logging.Logger; import org.osgl.mvc.result.Result; import org.osgl.util.C; import org.osgl.util.E; import org.osgl.util.S; import java.util.*; import static act.asm.tree.AbstractInsnNode.*; public class HandlerEnhancer extends MethodVisitor implements Opcodes { private static final Logger logger = LogManager.get(HandlerEnhancer.class); private static final String RESULT_CLASS = Result.class.getName(); private HandlerMethodMetaInfo info; private MethodVisitor next; private int paramIdShift = 0; private Set<Integer> skipNaming = new HashSet<Integer>(); private boolean notAction; public HandlerEnhancer(final MethodVisitor mv, HandlerMethodMetaInfo 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 AnnotationVisitor visitAnnotation(String desc, boolean visible) { if ("Lact/controller/NotAction;".equals(desc)) { notAction = true; } return super.visitAnnotation(desc, visible); } @Override public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { if ("Ljavax/inject/Named;".equals(desc)) { skipNaming.add(parameter); } return super.visitParameterAnnotation(parameter, desc, visible); } @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() { if (notAction) { super.visitEnd(); return; } MethodNode mn = (MethodNode) mv; addParamAnnotations(); transform(mn); mn.accept(next); } private void addParamAnnotations() { int sz = info.paramCount(); for (int i = 0; i < sz; ++i) { if (!skipNaming.contains(i)) { String name = info.param(i).name(); AnnotationVisitor av = mv.visitParameterAnnotation(i, "Ljavax/inject/Named;", true); av.visit("value", name); } } } private void transform(MethodNode mn) { new Transformer(mn, info).doIt(); } private static class Transformer { MethodNode mn; InsnList instructions; private HandlerMethodMetaInfo meta; List<Label> lblList = C.newSizedList(20); Transformer(MethodNode mn, HandlerMethodMetaInfo meta) { this.mn = mn; this.meta = meta; this.instructions = mergeRenderLineBreaks(mn.instructions); } void doIt() { ListIterator<AbstractInsnNode> itr = instructions.iterator(); Segment cur = null; while (itr.hasNext()) { AbstractInsnNode insn = itr.next(); if (insn.getType() == LABEL) { cur = new Segment(((LabelNode) insn).getLabel(), meta, instructions, itr, this); } else if (null != cur) { cur.handle(insn); } } } private InsnList mergeRenderLineBreaks(InsnList list) { ListIterator<AbstractInsnNode> itr = list.iterator(); while (itr.hasNext()) { AbstractInsnNode insn = itr.next(); if (isRenderLine(insn)) { mergeRenderLineBreaks(insn, list); } } return list; } private void mergeRenderLineBreaks(AbstractInsnNode renderLine, InsnList list) { AbstractInsnNode node = renderLine; while (null != node) { node = node.getPrevious(); if (node.getOpcode() == ANEWARRAY) { return; } else if (node instanceof LabelNode) { AbstractInsnNode node0 = node.getPrevious(); list.remove(node); node = node0; } } } static boolean isRenderLine(AbstractInsnNode insnNode) { if (!(insnNode instanceof MethodInsnNode)) { return false; } MethodInsnNode node = (MethodInsnNode) insnNode; Type type = Type.getMethodType(node.desc); Type retType = type.getReturnType(); return (isResult(retType) && node.desc.startsWith("([Ljava/lang/Object;)")); } static boolean isResult(Type type) { return ResultClassLookup.isResult(type.getClassName()); } private static abstract class InstructionHandler { Segment segment; HandlerMethodMetaInfo meta; InstructionHandler(Segment segment, HandlerMethodMetaInfo meta) { this.segment = segment; this.meta = meta; } protected abstract void handle(AbstractInsnNode node); protected void refreshIteratorNext() { segment.itr.previous(); segment.itr.next(); } } private static class Segment { Label startLabel; InsnList instructions; HandlerMethodMetaInfo meta; ListIterator<AbstractInsnNode> itr; Transformer trans; private Map<Integer, InstructionHandler> handlers = C.map( AbstractInsnNode.METHOD_INSN, new InvocationHandler(this, meta) ); Segment(Label start, HandlerMethodMetaInfo meta, InsnList instructions, ListIterator<AbstractInsnNode> itr, Transformer trans) { this.startLabel = start; this.meta = meta; this.instructions = instructions; this.itr = itr; this.trans = trans; trans.lblList.add(start); } String varName(int index) { Label lbl = startLabel; int pos = -1; List<Label> lblList = trans.lblList; while (null != lbl) { LocalVariableMetaInfo var = meta.localVariable(index, lbl); if (null != var) return var.name(); if (-1 == pos) { pos = lblList.indexOf(lbl); if (pos <= 0) { return null; } } lbl = lblList.get(--pos); } return null; } void handle(AbstractInsnNode node) { InstructionHandler handler = handlers.get(node.getType()); if (null != handler) { handler.handle(node); } } } private static class InvocationHandler extends InstructionHandler { InvocationHandler(Segment segment, HandlerMethodMetaInfo meta) { super(segment, meta); } @Override protected void handle(AbstractInsnNode node) { MethodInsnNode n = (MethodInsnNode) node; Type type = Type.getMethodType(n.desc); Type retType = type.getReturnType(); if (isResult(retType)) { if (n.desc.startsWith("([Ljava/lang/Object;)")) { injectRenderArgSetCode(n); } injectThrowCode(n); } } private String ctxFieldName() { return segment.meta.classInfo().ctxField(); } private int ctxIndex() { return segment.meta.appCtxLocalVariableTableIndex(); } private void injectRenderArgSetCode(AbstractInsnNode invokeNode) { if (!segment.meta.hasLocalVariableTable()) { logger.warn("local variable table info not found. ActionContext render args might not be automatically populated"); } AbstractInsnNode node = invokeNode.getPrevious(); List<LoadInsnInfo> loadInsnInfoList = C.newList(); String templatePath = null; String renderArgName = null; InsnList nodeList = new InsnList(); boolean invalidParam = false; while (null != node) { int type = node.getType(); boolean breakWhile = false; switch (type) { case LABEL: case FRAME: node = node.getNext(); breakWhile = true; if (!invalidParam && null != renderArgName) { loadInsnInfoList.add(new LoadInsnInfo(renderArgName, nodeList)); } break; case INSN: switch (node.getOpcode()) { case DUP: // need to check if previous insn is NEW which is invalid in param list AbstractInsnNode prev = node.getPrevious(); if (NEW == prev.getOpcode()) { logger.warn("Invalid render argument found in %s: new object is not accepted", segment.meta.fullName()); renderArgName = null; nodeList = new InsnList(); invalidParam = false; node = prev.getPrevious(); continue; } if (!invalidParam && null != renderArgName) { loadInsnInfoList.add(new LoadInsnInfo(renderArgName, nodeList)); } renderArgName = null; nodeList = new InsnList(); invalidParam = false; case AASTORE: case ICONST_0: case ICONST_1: case ICONST_2: case ICONST_3: case ICONST_4: case ICONST_5: break; default: logger.warn("Invalid render argument found in %s: unknow opcode: %s", segment.meta.fullName(), node.getOpcode()); invalidParam = true; } break; case TYPE_INSN: if (NEW == node.getOpcode()) { logger.warn("Invalid render argument found in %s: new object is not accepted", segment.meta.fullName()); invalidParam = true; } break; case VAR_INSN: if (null == renderArgName) { VarInsnNode vn = (VarInsnNode) node; if (0 == vn.var && !segment.meta.isStatic()) { // skip "this" } else { renderArgName = segment.varName(vn.var); } } nodeList.insert(node.clone(C.<LabelNode, LabelNode>map())); break; case METHOD_INSN: if (invalidParam) { break; } if (null == renderArgName) { MethodInsnNode mn = (MethodInsnNode) node; if (mn.desc.startsWith("()")) { // if method does not have parameter, e.g. `this.foo()` then // we take it's name as render arg name renderArgName = mn.name; } else if (!"valueOf".equals(mn.name)) { // if method is not something like `Integer.valueOf` then // we say it is an invalid render parameter logger.warn("Invalid render argument found in %s: method with param is not supported", segment.meta.fullName()); invalidParam = true; } } nodeList.insert(node.clone(C.<LabelNode, LabelNode>map())); break; case INT_INSN: if (BIPUSH == node.getOpcode()) { if (!invalidParam && null != renderArgName) { loadInsnInfoList.add(new LoadInsnInfo(renderArgName, nodeList)); } renderArgName = null; nodeList = new InsnList(); invalidParam = false; } break; case FIELD_INSN: if (invalidParam) { break; } if (null == renderArgName) { FieldInsnNode n = (FieldInsnNode) node; renderArgName = n.name; } nodeList.insert(node.clone(C.<LabelNode, LabelNode>map())); break; case AbstractInsnNode.LDC_INSN: if (invalidParam) { break; } 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(); } } if (breakWhile) { break; } node = node.getPrevious(); } InsnList list = new InsnList(); int appCtxIdx = ctxIndex(); // setTemplatePath enhancement if (null != templatePath) { if (appCtxIdx < 0) { String appCtxFieldName = ctxFieldName(); if (null == appCtxFieldName) { MethodInsnNode getAppCtx = new MethodInsnNode(INVOKESTATIC, AsmTypes.ACTION_CONTEXT_INTERNAL_NAME, ActionContext.METHOD_GET_CURRENT, GET_ACTION_CTX_DESC, false); list.add(getAppCtx); } else { VarInsnNode loadThis = new VarInsnNode(ALOAD, 0); FieldInsnNode getCtx = new FieldInsnNode(GETFIELD, segment.meta.classInfo().internalName(), appCtxFieldName, AsmTypes.ACTION_CONTEXT_DESC); list.add(loadThis); list.add(getCtx); } } else { LabelNode lbl = new LabelNode(); VarInsnNode loadCtx = new VarInsnNode(ALOAD, appCtxIdx); list.add(lbl); list.add(loadCtx); } LdcInsnNode insnNode = new LdcInsnNode(templatePath); list.add(insnNode); MethodInsnNode invokeTemplatePath = new MethodInsnNode(INVOKEVIRTUAL, AsmTypes.ACTION_CONTEXT_INTERNAL_NAME, TEMPLATE_PATH_NM, TEMPLATE_PATH_DESC, false); list.add(invokeTemplatePath); InsnNode pop = new InsnNode(POP); list.add(pop); } int len = loadInsnInfoList.size(); if (0 == len) { if (list.size() > 0) { segment.instructions.insertBefore(node, list); } return; } // SetRenderArgs enhancement if (appCtxIdx < 0) { String appCtxFieldName = ctxFieldName(); if (null == appCtxFieldName) { MethodInsnNode getActionContext = new MethodInsnNode(INVOKESTATIC, AsmTypes.ACTION_CONTEXT_INTERNAL_NAME, ActionContext.METHOD_GET_CURRENT, GET_ACTION_CTX_DESC, false); list.add(getActionContext); } else { VarInsnNode loadThis = new VarInsnNode(ALOAD, 0); FieldInsnNode getCtx = new FieldInsnNode(GETFIELD, segment.meta.classInfo().internalName(), appCtxFieldName, AsmTypes.ACTION_CONTEXT_DESC); list.add(loadThis); list.add(getCtx); } } else { LabelNode lbl = new LabelNode(); VarInsnNode loadCtx = new VarInsnNode(ALOAD, appCtxIdx); list.add(lbl); list.add(loadCtx); } S.Buffer sb = S.newBuffer(); for (int i = 0; i < len; ++i) { LoadInsnInfo info = loadInsnInfoList.get(i); info.appendTo(list, sb); } LdcInsnNode ldc = new LdcInsnNode(sb.toString()); list.add(ldc); MethodInsnNode invokeRenderArg = new MethodInsnNode(INVOKEVIRTUAL, AsmTypes.ACTION_CONTEXT_INTERNAL_NAME, RENDER_ARG_NAMES_NM, RENDER_ARG_NAMES_DESC, false); list.add(invokeRenderArg); InsnNode pop = new InsnNode(POP); list.add(pop); segment.instructions.insertBefore(node, list); } private void injectThrowCode(AbstractInsnNode invokeNode) { if (segment.meta.hasReturn()) { return; } AbstractInsnNode next = invokeNode.getNext(); if (next.getOpcode() == POP) { AbstractInsnNode newNext = new InsnNode(ATHROW); InsnList instructions = segment.instructions; instructions.insert(invokeNode, newNext); instructions.remove(next); next = newNext.getNext(); int curLine = -1; while (null != next) { boolean breakWhile = false; int type = next.getType(); switch (type) { case LABEL: next = next.getNext(); break; case AbstractInsnNode.LINE: curLine = ((LineNumberNode) next).line; next = next.getNext(); break; case AbstractInsnNode.JUMP_INSN: AbstractInsnNode tmp = next.getNext(); instructions.remove(next); next = tmp; break; case INSN: int op = next.getOpcode(); if (op == RETURN) { tmp = next.getNext(); instructions.remove(next); next = tmp; tmp = next.getPrevious(); if (tmp.getType() == AbstractInsnNode.LINE) { instructions.remove(tmp); } break; } case FRAME: breakWhile = true; break; default: AsmContext.line(curLine); E.unexpected("Invalid statement after render result statement at line %s", curLine); } if (breakWhile) { break; } } refreshIteratorNext(); } } } private static class LoadInsnInfo { private String name; private InsnList insnList; LoadInsnInfo(String name, InsnList insnList) { this.insnList = insnList; this.name = name; } void appendTo(InsnList list, S.Buffer paramNames) { LdcInsnNode ldc = new LdcInsnNode(name); list.add(ldc); list.add(insnList); MethodInsnNode invokeRenderArg = new MethodInsnNode(INVOKEVIRTUAL, AsmTypes.ACTION_CONTEXT_INTERNAL_NAME, RENDER_NM, RENDER_DESC, false); list.add(invokeRenderArg); if (paramNames.length() != 0) { paramNames.append(','); } paramNames.append(name); } @Override public String toString() { return S.fmt("Load %s", name); } } } private static final String GET_ACTION_CTX_DESC = "()" + AsmTypes.ACTION_CONTEXT_DESC; private static final String RENDER_NM = "renderArg"; private static final String RENDER_DESC = AsmTypes.methodDesc(ActionContext.class, String.class, Object.class); private static final String TEMPLATE_PATH_NM = "templatePath"; private static final String TEMPLATE_PATH_DESC = AsmTypes.methodDesc(ActionContext.class, String.class); private static final String RENDER_ARG_NAMES_NM = "__appRenderArgNames"; private static final String RENDER_ARG_NAMES_DESC = AsmTypes.methodDesc(ActionContext.class, String.class); }