001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.transformer.bytecode.statement.tag; 020 021import java.util.Iterator; 022import java.util.Map; 023 024import javax.servlet.jsp.tagext.BodyTag; 025import javax.servlet.jsp.tagext.IterationTag; 026 027import lucee.commons.lang.ClassException; 028import lucee.runtime.exp.Abort; 029import lucee.runtime.tag.MissingAttribute; 030import lucee.runtime.type.util.ArrayUtil; 031import lucee.transformer.bytecode.BytecodeContext; 032import lucee.transformer.bytecode.BytecodeException; 033import lucee.transformer.bytecode.cast.CastOther; 034import lucee.transformer.bytecode.expression.Expression; 035import lucee.transformer.bytecode.expression.type.LiteralStringArray; 036import lucee.transformer.bytecode.expression.var.Variable; 037import lucee.transformer.bytecode.literal.LitString; 038import lucee.transformer.bytecode.statement.FlowControlFinal; 039import lucee.transformer.bytecode.util.ASMConstants; 040import lucee.transformer.bytecode.util.ExpressionUtil; 041import lucee.transformer.bytecode.util.Types; 042import lucee.transformer.bytecode.visitor.ArrayVisitor; 043import lucee.transformer.bytecode.visitor.OnFinally; 044import lucee.transformer.bytecode.visitor.TryCatchFinallyVisitor; 045import lucee.transformer.bytecode.visitor.TryFinallyVisitor; 046import lucee.transformer.library.tag.TagLibTag; 047import lucee.transformer.library.tag.TagLibTagAttr; 048 049import org.objectweb.asm.Label; 050import org.objectweb.asm.Opcodes; 051import org.objectweb.asm.Type; 052import org.objectweb.asm.commons.GeneratorAdapter; 053import org.objectweb.asm.commons.Method; 054 055public final class TagHelper { 056 private static final Type MISSING_ATTRIBUTE = Type.getType(MissingAttribute.class); 057 private static final Type MISSING_ATTRIBUTE_ARRAY = Type.getType(MissingAttribute[].class); 058 private static final Type TAG=Type.getType(javax.servlet.jsp.tagext.Tag.class); 059 private static final Type TAG_UTIL=Type.getType(lucee.runtime.tag.TagUtil.class); 060 061 // TagUtil.setAttributeCollection(Tag, Struct) 062 private static final Method SET_ATTRIBUTE_COLLECTION = new Method( 063 "setAttributeCollection",Types.VOID,new Type[]{Types.PAGE_CONTEXT,TAG,MISSING_ATTRIBUTE_ARRAY,Types.STRUCT,Types.INT_VALUE}); 064 065 // Tag use(String) 066 private static final Method USE1= new Method("use",TAG,new Type[]{Types.STRING}); 067 private static final Method USE3= new Method("use",TAG,new Type[]{Types.STRING,Types.STRING,Types.INT_VALUE}); 068 069 // void setAppendix(String appendix) 070 private static final Method SET_APPENDIX = new Method("setAppendix",Type.VOID_TYPE,new Type[]{Types.STRING}); 071 072 // void setDynamicAttribute(String uri, String name, Object value) 073 private static final Method SET_DYNAMIC_ATTRIBUTE = new Method( 074 "setDynamicAttribute", 075 Type.VOID_TYPE, 076 new Type[]{Types.STRING,Types.COLLECTION_KEY,Types.OBJECT}); 077 078 private static final Method SET_META_DATA = new Method( 079 "setMetaData", 080 Type.VOID_TYPE, 081 new Type[]{Types.STRING,Types.OBJECT}); 082 083 // void hasBody(boolean hasBody) 084 private static final Method HAS_BODY = new Method( 085 "hasBody", 086 Type.VOID_TYPE, 087 new Type[]{Types.BOOLEAN_VALUE}); 088 089 // int doStartTag() 090 private static final Method DO_START_TAG = new Method( 091 "doStartTag", 092 Types.INT_VALUE, 093 new Type[]{}); 094 095 // int doEndTag() 096 private static final Method DO_END_TAG = new Method( 097 "doEndTag", 098 Types.INT_VALUE, 099 new Type[]{}); 100 101 private static final Type ABORT = Type.getType(Abort.class); 102 //private static final Type EXPRESSION_EXCEPTION = Type.getType(ExpressionException.class); 103 private static final Type BODY_TAG = Type.getType(BodyTag.class); 104 105 // ExpressionException newInstance(int) 106 private static final Method NEW_INSTANCE = new Method( 107 "newInstance", 108 ABORT, 109 new Type[]{Types.INT_VALUE}); 110 private static final Method NEW_INSTANCE_MAX2 = new Method( 111 "newInstance", 112 MISSING_ATTRIBUTE, 113 new Type[]{Types.COLLECTION_KEY,Types.STRING}); 114 115 private static final Method NEW_INSTANCE_MAX3 = new Method( 116 "newInstance", 117 MISSING_ATTRIBUTE, 118 new Type[]{Types.COLLECTION_KEY,Types.STRING,Types.STRING_ARRAY}); 119 120 121 122 123 // void initBody(BodyTag bodyTag, int state) 124 private static final Method INIT_BODY = new Method( 125 "initBody", 126 Types.VOID, 127 new Type[]{BODY_TAG,Types.INT_VALUE}); 128 129 // int doAfterBody() 130 private static final Method DO_AFTER_BODY = new Method( 131 "doAfterBody", 132 Types.INT_VALUE, 133 new Type[]{}); 134 135 // void doCatch(Throwable t) 136 private static final Method DO_CATCH = new Method( 137 "doCatch", 138 Types.VOID, 139 new Type[]{Types.THROWABLE}); 140 141 // void doFinally() 142 private static final Method DO_FINALLY = new Method( 143 "doFinally", 144 Types.VOID, 145 new Type[]{}); 146 147 // JspWriter popBody() 148 private static final Method POP_BODY = new Method( 149 "popBody", 150 Types.JSP_WRITER, 151 new Type[]{}); 152 153 // void reuse(Tag tag) 154 private static final Method RE_USE = new Method( 155 "reuse", 156 Types.VOID, 157 new Type[]{Types.TAG}); 158 159 /** 160 * writes out the tag 161 * @param tag 162 * @param bc 163 * @param doReuse 164 * @throws BytecodeException 165 */ 166 public static void writeOut(Tag tag, BytecodeContext bc, boolean doReuse, final FlowControlFinal fcf) throws BytecodeException { 167 final GeneratorAdapter adapter = bc.getAdapter(); 168 final TagLibTag tlt = tag.getTagLibTag(); 169 final Type currType=getTagType(tag); 170 171 final int currLocal=adapter.newLocal(currType); 172 Label tagBegin=new Label(); 173 Label tagEnd=new Label(); 174 ExpressionUtil.visitLine(bc, tag.getStart()); 175 // TODO adapter.visitLocalVariable("tag", "L"+currType.getInternalName()+";", null, tagBegin, tagEnd, currLocal); 176 177 adapter.visitLabel(tagBegin); 178 179 // tag=pc.use(str); 180 adapter.loadArg(0); 181 adapter.checkCast(Types.PAGE_CONTEXT_IMPL); 182 adapter.push(tlt.getTagClassName()); 183 adapter.push(tlt.getFullName()); 184 adapter.push(tlt.getAttributeType()); 185 adapter.invokeVirtual(Types.PAGE_CONTEXT_IMPL, USE3); 186 adapter.checkCast(currType); 187 adapter.storeLocal(currLocal); 188 189 TryFinallyVisitor outerTcfv=new TryFinallyVisitor(new OnFinally() { 190 public void _writeOut(BytecodeContext bc) { 191 192 adapter.loadArg(0); 193 adapter.loadLocal(currLocal); 194 adapter.invokeVirtual(Types.PAGE_CONTEXT, RE_USE); 195 } 196 },null); 197 if(doReuse)outerTcfv.visitTryBegin(bc); 198 199 // appendix 200 if(tlt.hasAppendix()) { 201 adapter.loadLocal(currLocal); 202 adapter.push(tag.getAppendix()); 203 adapter.invokeVirtual(currType, SET_APPENDIX); 204 } 205 206 // hasBody 207 boolean hasBody=tag.getBody()!=null; 208 if(tlt.isBodyFree() && tlt.hasBodyMethodExists()) { 209 adapter.loadLocal(currLocal); 210 adapter.push(hasBody); 211 adapter.invokeVirtual(currType, HAS_BODY); 212 } 213 214 // default attributes (get overwritten by attributeCollection because of that set before) 215 setAttributes(bc,tag,currLocal,currType, true); 216 217 // attributeCollection 218 Attribute attrColl=tag.getAttribute("attributecollection"); 219 if(attrColl!=null){ 220 int attrType = tag.getTagLibTag().getAttributeType(); 221 if(TagLibTag.ATTRIBUTE_TYPE_NONAME!=attrType) { 222 tag.removeAttribute("attributecollection"); 223 // TagUtil.setAttributeCollection(Tag, Struct) 224 adapter.loadArg(0); 225 adapter.loadLocal(currLocal); 226 adapter.cast(currType, TAG); 227 228 /// 229 TagLibTagAttr[] missings = tag.getMissingAttributes(); 230 if(!ArrayUtil.isEmpty(missings)) { 231 ArrayVisitor av=new ArrayVisitor(); 232 av.visitBegin(adapter,MISSING_ATTRIBUTE,missings.length); 233 int count=0; 234 TagLibTagAttr miss; 235 for(int i=0;i<missings.length;i++){ 236 miss = missings[i]; 237 av.visitBeginItem(adapter, count++); 238 Variable.registerKey(bc, LitString.toExprString(miss.getName())); 239 adapter.push(miss.getType()); 240 if(ArrayUtil.isEmpty(miss.getAlias())) 241 adapter.invokeStatic(MISSING_ATTRIBUTE, NEW_INSTANCE_MAX2); 242 else { 243 new LiteralStringArray(miss.getAlias()).writeOut(bc, Expression.MODE_REF); 244 adapter.invokeStatic(MISSING_ATTRIBUTE, NEW_INSTANCE_MAX3); 245 } 246 av.visitEndItem(bc.getAdapter()); 247 } 248 av.visitEnd(); 249 } 250 else { 251 ASMConstants.NULL(adapter); 252 } 253 /// 254 attrColl.getValue().writeOut(bc, Expression.MODE_REF); 255 256 adapter.push(attrType); 257 adapter.invokeStatic(TAG_UTIL, SET_ATTRIBUTE_COLLECTION); 258 } 259 } 260 261 262 // metadata 263 Attribute attr; 264 Map<String, Attribute> metadata = tag.getMetaData(); 265 if(metadata!=null){ 266 Iterator<Attribute> it = metadata.values().iterator(); 267 while(it.hasNext()) { 268 attr=it.next(); 269 adapter.loadLocal(currLocal); 270 adapter.push(attr.getName()); 271 attr.getValue().writeOut(bc, Expression.MODE_REF); 272 adapter.invokeVirtual(currType, SET_META_DATA); 273 } 274 } 275 276 // set attributes 277 setAttributes(bc,tag,currLocal,currType,false); 278 279 // Body 280 if(hasBody){ 281 final int state=adapter.newLocal(Types.INT_VALUE); 282 283 // int state=tag.doStartTag(); 284 adapter.loadLocal(currLocal); 285 adapter.invokeVirtual(currType, DO_START_TAG); 286 adapter.storeLocal(state); 287 288 // if (state!=Tag.SKIP_BODY) 289 Label endBody=new Label(); 290 adapter.loadLocal(state); 291 adapter.push(javax.servlet.jsp.tagext.Tag.SKIP_BODY); 292 adapter.visitJumpInsn(Opcodes.IF_ICMPEQ, endBody); 293 // pc.initBody(tag, state); 294 adapter.loadArg(0); 295 adapter.loadLocal(currLocal); 296 adapter.loadLocal(state); 297 adapter.invokeVirtual(Types.PAGE_CONTEXT, INIT_BODY); 298 299 300 OnFinally onFinally = new OnFinally() { 301 302 public void _writeOut(BytecodeContext bc) { 303 Label endIf = new Label(); 304 /*if(tlt.handleException() && fcf!=null && fcf.getAfterFinalGOTOLabel()!=null){ 305 ASMUtil.visitLabel(adapter, fcf.getFinalEntryLabel()); 306 }*/ 307 adapter.loadLocal(state); 308 adapter.push(javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE); 309 adapter.visitJumpInsn(Opcodes.IF_ICMPEQ, endIf); 310 // ... pc.popBody(); 311 adapter.loadArg(0); 312 adapter.invokeVirtual(Types.PAGE_CONTEXT, POP_BODY); 313 adapter.pop(); 314 adapter.visitLabel(endIf); 315 316 // tag.doFinally(); 317 if(tlt.handleException()) { 318 adapter.loadLocal(currLocal); 319 adapter.invokeVirtual(currType, DO_FINALLY); 320 } 321 // GOTO after execution body, used when a continue/break was called before 322 /*if(fcf!=null) { 323 Label l = fcf.getAfterFinalGOTOLabel(); 324 if(l!=null)adapter.visitJumpInsn(Opcodes.GOTO, l); 325 }*/ 326 327 } 328 }; 329 330 331 if(tlt.handleException()) { 332 TryCatchFinallyVisitor tcfv=new TryCatchFinallyVisitor(onFinally,fcf); 333 tcfv.visitTryBegin(bc); 334 doTry(bc,adapter,tag,currLocal,currType); 335 int t=tcfv.visitTryEndCatchBeging(bc); 336 // tag.doCatch(t); 337 adapter.loadLocal(currLocal); 338 adapter.loadLocal(t); 339 //adapter.visitVarInsn(Opcodes.ALOAD,t); 340 adapter.invokeVirtual(currType, DO_CATCH); 341 tcfv.visitCatchEnd(bc); 342 } 343 else { 344 TryFinallyVisitor tfv=new TryFinallyVisitor(onFinally,fcf); 345 tfv.visitTryBegin(bc); 346 doTry(bc,adapter,tag,currLocal,currType); 347 tfv.visitTryEnd(bc); 348 } 349 350 351 adapter.visitLabel(endBody); 352 353 } 354 else { 355 //tag.doStartTag(); 356 adapter.loadLocal(currLocal); 357 adapter.invokeVirtual(currType, DO_START_TAG); 358 adapter.pop(); 359 } 360 361 // if (tag.doEndTag()==Tag.SKIP_PAGE) throw new Abort(0<!-- SCOPE_PAGE -->); 362 Label endDoEndTag=new Label(); 363 adapter.loadLocal(currLocal); 364 adapter.invokeVirtual(currType, DO_END_TAG); 365 adapter.push(javax.servlet.jsp.tagext.Tag.SKIP_PAGE); 366 adapter.visitJumpInsn(Opcodes.IF_ICMPNE, endDoEndTag); 367 adapter.push(Abort.SCOPE_PAGE); 368 adapter.invokeStatic(ABORT, NEW_INSTANCE); 369 adapter.throwException(); 370 adapter.visitLabel(endDoEndTag); 371 372 373 if(doReuse) { 374 // } finally{pc.reuse(tag);} 375 outerTcfv.visitTryEnd(bc); 376 } 377 378 379 adapter.visitLabel(tagEnd); 380 ExpressionUtil.visitLine(bc, tag.getEnd()); 381 } 382 383 private static void setAttributes(BytecodeContext bc, Tag tag, int currLocal, Type currType, boolean doDefault) throws BytecodeException { 384 GeneratorAdapter adapter = bc.getAdapter(); 385 Map<String,Attribute> attributes = tag.getAttributes(); 386 387 String methodName; 388 Attribute attr; 389 Iterator<Attribute> it = attributes.values().iterator(); 390 while(it.hasNext()) { 391 attr=it.next(); 392 if(doDefault!=attr.isDefaultAttribute()) continue; 393 394 if(attr.isDynamicType()){ 395 adapter.loadLocal(currLocal); 396 adapter.visitInsn(Opcodes.ACONST_NULL); 397 //adapter.push(attr.getName()); 398 Variable.registerKey(bc, LitString.toExprString(attr.getName())); 399 attr.getValue().writeOut(bc, Expression.MODE_REF); 400 adapter.invokeVirtual(currType, SET_DYNAMIC_ATTRIBUTE); 401 } 402 else { 403 Type type = CastOther.getType(attr.getType()); 404 methodName=tag.getTagLibTag().getSetter(attr,type); 405 adapter.loadLocal(currLocal); 406 attr.getValue().writeOut(bc, Types.isPrimitiveType(type)?Expression.MODE_VALUE:Expression.MODE_REF); 407 adapter.invokeVirtual(currType, new Method(methodName,Type.VOID_TYPE,new Type[]{type})); 408 } 409 } 410 } 411 412 private static void doTry(BytecodeContext bc, GeneratorAdapter adapter, Tag tag, int currLocal, Type currType) throws BytecodeException { 413 Label beginDoWhile=new Label(); 414 adapter.visitLabel(beginDoWhile); 415 bc.setCurrentTag(currLocal); 416 tag.getBody().writeOut(bc); 417 418 // while (tag.doAfterBody()==BodyTag.EVAL_BODY_AGAIN); 419 adapter.loadLocal(currLocal); 420 adapter.invokeVirtual(currType, DO_AFTER_BODY); 421 adapter.push(IterationTag.EVAL_BODY_AGAIN); 422 adapter.visitJumpInsn(Opcodes.IF_ICMPEQ, beginDoWhile); 423 } 424 425 private static Type getTagType(Tag tag) throws BytecodeException { 426 TagLibTag tlt = tag.getTagLibTag(); 427 try { 428 return tlt.getTagType(); 429 } catch (ClassException e) { 430 throw new BytecodeException(e,tag.getStart()); 431 } 432 } 433}