1 /* 2 * Javassist, a Java-bytecode translator toolkit. 3 * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. 4 * 5 * The contents of this file are subject to the Mozilla Public License Version 6 * 1.1 (the "License"); you may not use this file except in compliance with 7 * the License. Alternatively, the contents of this file may be used under 8 * the terms of the GNU Lesser General Public License Version 2.1 or later. 9 * 10 * Software distributed under the License is distributed on an "AS IS" basis, 11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 12 * for the specific language governing rights and limitations under the 13 * License. 14 */ 15 16 package javassist.compiler; 17 18 import javassist.*; 19 import javassist.bytecode.*; 20 import javassist.compiler.ast.*; 21 22 /* Code generator accepting extended Java syntax for Javassist. 23 */ 24 25 public class JvstCodeGen extends MemberCodeGen { 26 String paramArrayName = null; 27 String paramListName = null; 28 CtClass[] paramTypeList = null; 29 private int paramVarBase = 0; // variable index for $0 or $1. 30 private boolean useParam0 = false; // true if $0 is used. 31 private String param0Type = null; // JVM name 32 public static final String sigName = "$sig"; 33 public static final String dollarTypeName = "$type"; 34 public static final String clazzName = "$class"; 35 private CtClass dollarType = null; 36 CtClass returnType = null; 37 String returnCastName = null; 38 private String returnVarName = null; // null if $_ is not used. 39 public static final String wrapperCastName = "$w"; 40 String proceedName = null; 41 public static final String cflowName = "$cflow"; 42 ProceedHandler procHandler = null; // null if not used. 43 JvstCodeGen(Bytecode b, CtClass cc, ClassPool cp)44 public JvstCodeGen(Bytecode b, CtClass cc, ClassPool cp) { 45 super(b, cc, cp); 46 setTypeChecker(new JvstTypeChecker(cc, cp, this)); 47 } 48 49 /* Index of $1. 50 */ indexOfParam1()51 private int indexOfParam1() { 52 return paramVarBase + (useParam0 ? 1 : 0); 53 } 54 55 /* Records a ProceedHandler obejct. 56 * 57 * @param name the name of the special method call. 58 * it is usually $proceed. 59 */ setProceedHandler(ProceedHandler h, String name)60 public void setProceedHandler(ProceedHandler h, String name) { 61 proceedName = name; 62 procHandler = h; 63 } 64 65 /* If the type of the expression compiled last is void, 66 * add ACONST_NULL and change exprType, arrayDim, className. 67 */ addNullIfVoid()68 public void addNullIfVoid() { 69 if (exprType == VOID) { 70 bytecode.addOpcode(ACONST_NULL); 71 exprType = CLASS; 72 arrayDim = 0; 73 className = jvmJavaLangObject; 74 } 75 } 76 77 /* To support $args, $sig, and $type. 78 * $args is an array of parameter list. 79 */ atMember(Member mem)80 public void atMember(Member mem) throws CompileError { 81 String name = mem.get(); 82 if (name.equals(paramArrayName)) { 83 compileParameterList(bytecode, paramTypeList, indexOfParam1()); 84 exprType = CLASS; 85 arrayDim = 1; 86 className = jvmJavaLangObject; 87 } 88 else if (name.equals(sigName)) { 89 bytecode.addLdc(Descriptor.ofMethod(returnType, paramTypeList)); 90 bytecode.addInvokestatic("javassist/runtime/Desc", "getParams", 91 "(Ljava/lang/String;)[Ljava/lang/Class;"); 92 exprType = CLASS; 93 arrayDim = 1; 94 className = "java/lang/Class"; 95 } 96 else if (name.equals(dollarTypeName)) { 97 if (dollarType == null) 98 throw new CompileError(dollarTypeName + " is not available"); 99 100 bytecode.addLdc(Descriptor.of(dollarType)); 101 callGetType("getType"); 102 } 103 else if (name.equals(clazzName)) { 104 if (param0Type == null) 105 throw new CompileError(clazzName + " is not available"); 106 107 bytecode.addLdc(param0Type); 108 callGetType("getClazz"); 109 } 110 else 111 super.atMember(mem); 112 } 113 callGetType(String method)114 private void callGetType(String method) { 115 bytecode.addInvokestatic("javassist/runtime/Desc", method, 116 "(Ljava/lang/String;)Ljava/lang/Class;"); 117 exprType = CLASS; 118 arrayDim = 0; 119 className = "java/lang/Class"; 120 } 121 atFieldAssign(Expr expr, int op, ASTree left, ASTree right, boolean doDup)122 protected void atFieldAssign(Expr expr, int op, ASTree left, 123 ASTree right, boolean doDup) throws CompileError 124 { 125 if (left instanceof Member 126 && ((Member)left).get().equals(paramArrayName)) { 127 if (op != '=') 128 throw new CompileError("bad operator for " + paramArrayName); 129 130 right.accept(this); 131 if (arrayDim != 1 || exprType != CLASS) 132 throw new CompileError("invalid type for " + paramArrayName); 133 134 atAssignParamList(paramTypeList, bytecode); 135 if (!doDup) 136 bytecode.addOpcode(POP); 137 } 138 else 139 super.atFieldAssign(expr, op, left, right, doDup); 140 } 141 atAssignParamList(CtClass[] params, Bytecode code)142 protected void atAssignParamList(CtClass[] params, Bytecode code) 143 throws CompileError 144 { 145 if (params == null) 146 return; 147 148 int varNo = indexOfParam1(); 149 int n = params.length; 150 for (int i = 0; i < n; ++i) { 151 code.addOpcode(DUP); 152 code.addIconst(i); 153 code.addOpcode(AALOAD); 154 compileUnwrapValue(params[i], code); 155 code.addStore(varNo, params[i]); 156 varNo += is2word(exprType, arrayDim) ? 2 : 1; 157 } 158 } 159 atCastExpr(CastExpr expr)160 public void atCastExpr(CastExpr expr) throws CompileError { 161 ASTList classname = expr.getClassName(); 162 if (classname != null && expr.getArrayDim() == 0) { 163 ASTree p = classname.head(); 164 if (p instanceof Symbol && classname.tail() == null) { 165 String typename = ((Symbol)p).get(); 166 if (typename.equals(returnCastName)) { 167 atCastToRtype(expr); 168 return; 169 } 170 else if (typename.equals(wrapperCastName)) { 171 atCastToWrapper(expr); 172 return; 173 } 174 } 175 } 176 177 super.atCastExpr(expr); 178 } 179 180 /** 181 * Inserts a cast operator to the return type. 182 * If the return type is void, this does nothing. 183 */ atCastToRtype(CastExpr expr)184 protected void atCastToRtype(CastExpr expr) throws CompileError { 185 expr.getOprand().accept(this); 186 if (exprType == VOID || isRefType(exprType) || arrayDim > 0) 187 compileUnwrapValue(returnType, bytecode); 188 else if (returnType instanceof CtPrimitiveType) { 189 CtPrimitiveType pt = (CtPrimitiveType)returnType; 190 int destType = MemberResolver.descToType(pt.getDescriptor()); 191 atNumCastExpr(exprType, destType); 192 exprType = destType; 193 arrayDim = 0; 194 className = null; 195 } 196 else 197 throw new CompileError("invalid cast"); 198 } 199 atCastToWrapper(CastExpr expr)200 protected void atCastToWrapper(CastExpr expr) throws CompileError { 201 expr.getOprand().accept(this); 202 if (isRefType(exprType) || arrayDim > 0) 203 return; // Object type. do nothing. 204 205 CtClass clazz = resolver.lookupClass(exprType, arrayDim, className); 206 if (clazz instanceof CtPrimitiveType) { 207 CtPrimitiveType pt = (CtPrimitiveType)clazz; 208 String wrapper = pt.getWrapperName(); 209 bytecode.addNew(wrapper); // new <wrapper> 210 bytecode.addOpcode(DUP); // dup 211 if (pt.getDataSize() > 1) 212 bytecode.addOpcode(DUP2_X2); // dup2_x2 213 else 214 bytecode.addOpcode(DUP2_X1); // dup2_x1 215 216 bytecode.addOpcode(POP2); // pop2 217 bytecode.addInvokespecial(wrapper, "<init>", 218 "(" + pt.getDescriptor() + ")V"); 219 // invokespecial 220 exprType = CLASS; 221 arrayDim = 0; 222 className = jvmJavaLangObject; 223 } 224 } 225 226 /* Delegates to a ProcHandler object if the method call is 227 * $proceed(). It may process $cflow(). 228 */ atCallExpr(CallExpr expr)229 public void atCallExpr(CallExpr expr) throws CompileError { 230 ASTree method = expr.oprand1(); 231 if (method instanceof Member) { 232 String name = ((Member)method).get(); 233 if (procHandler != null && name.equals(proceedName)) { 234 procHandler.doit(this, bytecode, (ASTList)expr.oprand2()); 235 return; 236 } 237 else if (name.equals(cflowName)) { 238 atCflow((ASTList)expr.oprand2()); 239 return; 240 } 241 } 242 243 super.atCallExpr(expr); 244 } 245 246 /* To support $cflow(). 247 */ atCflow(ASTList cname)248 protected void atCflow(ASTList cname) throws CompileError { 249 StringBuffer sbuf = new StringBuffer(); 250 if (cname == null || cname.tail() != null) 251 throw new CompileError("bad " + cflowName); 252 253 makeCflowName(sbuf, cname.head()); 254 String name = sbuf.toString(); 255 Object[] names = resolver.getClassPool().lookupCflow(name); 256 if (names == null) 257 throw new CompileError("no such " + cflowName + ": " + name); 258 259 bytecode.addGetstatic((String)names[0], (String)names[1], 260 "Ljavassist/runtime/Cflow;"); 261 bytecode.addInvokevirtual("javassist.runtime.Cflow", 262 "value", "()I"); 263 exprType = INT; 264 arrayDim = 0; 265 className = null; 266 } 267 268 /* Syntax: 269 * 270 * <cflow> : $cflow '(' <cflow name> ')' 271 * <cflow name> : <identifier> ('.' <identifier>)* 272 */ makeCflowName(StringBuffer sbuf, ASTree name)273 private static void makeCflowName(StringBuffer sbuf, ASTree name) 274 throws CompileError 275 { 276 if (name instanceof Symbol) { 277 sbuf.append(((Symbol)name).get()); 278 return; 279 } 280 else if (name instanceof Expr) { 281 Expr expr = (Expr)name; 282 if (expr.getOperator() == '.') { 283 makeCflowName(sbuf, expr.oprand1()); 284 sbuf.append('.'); 285 makeCflowName(sbuf, expr.oprand2()); 286 return; 287 } 288 } 289 290 throw new CompileError("bad " + cflowName); 291 } 292 293 /* To support $$. ($$) is equivalent to ($1, ..., $n). 294 * It can be used only as a parameter list of method call. 295 */ isParamListName(ASTList args)296 public boolean isParamListName(ASTList args) { 297 if (paramTypeList != null 298 && args != null && args.tail() == null) { 299 ASTree left = args.head(); 300 return (left instanceof Member 301 && ((Member)left).get().equals(paramListName)); 302 } 303 else 304 return false; 305 } 306 307 /* 308 public int getMethodArgsLength(ASTList args) { 309 if (!isParamListName(args)) 310 return super.getMethodArgsLength(args); 311 312 return paramTypeList.length; 313 } 314 */ 315 getMethodArgsLength(ASTList args)316 public int getMethodArgsLength(ASTList args) { 317 String pname = paramListName; 318 int n = 0; 319 while (args != null) { 320 ASTree a = args.head(); 321 if (a instanceof Member && ((Member)a).get().equals(pname)) { 322 if (paramTypeList != null) 323 n += paramTypeList.length; 324 } 325 else 326 ++n; 327 328 args = args.tail(); 329 } 330 331 return n; 332 } 333 atMethodArgs(ASTList args, int[] types, int[] dims, String[] cnames)334 public void atMethodArgs(ASTList args, int[] types, int[] dims, 335 String[] cnames) throws CompileError { 336 CtClass[] params = paramTypeList; 337 String pname = paramListName; 338 int i = 0; 339 while (args != null) { 340 ASTree a = args.head(); 341 if (a instanceof Member && ((Member)a).get().equals(pname)) { 342 if (params != null) { 343 int n = params.length; 344 int regno = indexOfParam1(); 345 for (int k = 0; k < n; ++k) { 346 CtClass p = params[k]; 347 regno += bytecode.addLoad(regno, p); 348 setType(p); 349 types[i] = exprType; 350 dims[i] = arrayDim; 351 cnames[i] = className; 352 ++i; 353 } 354 } 355 } 356 else { 357 a.accept(this); 358 types[i] = exprType; 359 dims[i] = arrayDim; 360 cnames[i] = className; 361 ++i; 362 } 363 364 args = args.tail(); 365 } 366 } 367 368 /* 369 public void atMethodArgs(ASTList args, int[] types, int[] dims, 370 String[] cnames) throws CompileError { 371 if (!isParamListName(args)) { 372 super.atMethodArgs(args, types, dims, cnames); 373 return; 374 } 375 376 CtClass[] params = paramTypeList; 377 if (params == null) 378 return; 379 380 int n = params.length; 381 int regno = indexOfParam1(); 382 for (int i = 0; i < n; ++i) { 383 CtClass p = params[i]; 384 regno += bytecode.addLoad(regno, p); 385 setType(p); 386 types[i] = exprType; 387 dims[i] = arrayDim; 388 cnames[i] = className; 389 } 390 } 391 */ 392 393 /* called by Javac#recordSpecialProceed(). 394 */ compileInvokeSpecial(ASTree target, String classname, String methodname, String descriptor, ASTList args)395 void compileInvokeSpecial(ASTree target, String classname, 396 String methodname, String descriptor, 397 ASTList args) 398 throws CompileError 399 { 400 target.accept(this); 401 int nargs = getMethodArgsLength(args); 402 atMethodArgs(args, new int[nargs], new int[nargs], 403 new String[nargs]); 404 bytecode.addInvokespecial(classname, methodname, descriptor); 405 setReturnType(descriptor, false, false); 406 addNullIfVoid(); 407 } 408 409 /* 410 * Makes it valid to write "return <expr>;" for a void method. 411 */ atReturnStmnt(Stmnt st)412 protected void atReturnStmnt(Stmnt st) throws CompileError { 413 ASTree result = st.getLeft(); 414 if (result != null && returnType == CtClass.voidType) { 415 compileExpr(result); 416 if (is2word(exprType, arrayDim)) 417 bytecode.addOpcode(POP2); 418 else if (exprType != VOID) 419 bytecode.addOpcode(POP); 420 421 result = null; 422 } 423 424 atReturnStmnt2(result); 425 } 426 427 /** 428 * Makes a cast to the return type ($r) available. 429 * It also enables $_. 430 * 431 * <p>If the return type is void, ($r) does nothing. 432 * The type of $_ is java.lang.Object. 433 * 434 * @param resultName null if $_ is not used. 435 * @return -1 or the variable index assigned to $_. 436 */ recordReturnType(CtClass type, String castName, String resultName, SymbolTable tbl)437 public int recordReturnType(CtClass type, String castName, 438 String resultName, SymbolTable tbl) throws CompileError 439 { 440 returnType = type; 441 returnCastName = castName; 442 returnVarName = resultName; 443 if (resultName == null) 444 return -1; 445 else { 446 int varNo = getMaxLocals(); 447 int locals = varNo + recordVar(type, resultName, varNo, tbl); 448 setMaxLocals(locals); 449 return varNo; 450 } 451 } 452 453 /** 454 * Makes $type available. 455 */ recordType(CtClass t)456 public void recordType(CtClass t) { 457 dollarType = t; 458 } 459 460 /** 461 * Makes method parameters $0, $1, ..., $args, $$, and $class available. 462 * $0 is equivalent to THIS if the method is not static. Otherwise, 463 * if the method is static, then $0 is not available. 464 */ recordParams(CtClass[] params, boolean isStatic, String prefix, String paramVarName, String paramsName, SymbolTable tbl)465 public int recordParams(CtClass[] params, boolean isStatic, 466 String prefix, String paramVarName, 467 String paramsName, SymbolTable tbl) 468 throws CompileError 469 { 470 return recordParams(params, isStatic, prefix, paramVarName, 471 paramsName, !isStatic, 0, getThisName(), tbl); 472 } 473 474 /** 475 * Makes method parameters $0, $1, ..., $args, $$, and $class available. 476 * $0 is available only if use0 is true. It might not be equivalent 477 * to THIS. 478 * 479 * @param params the parameter types (the types of $1, $2, ..) 480 * @param prefix it must be "$" (the first letter of $0, $1, ...) 481 * @param paramVarName it must be "$args" 482 * @param paramsName it must be "$$" 483 * @param use0 true if $0 is used. 484 * @param paramBase the register number of $0 (use0 is true) 485 * or $1 (otherwise). 486 * @param target the class of $0. If use0 is false, target 487 * can be null. The value of "target" is also used 488 * as the name of the type represented by $class. 489 * @param isStatic true if the method in which the compiled bytecode 490 * is embedded is static. 491 */ recordParams(CtClass[] params, boolean isStatic, String prefix, String paramVarName, String paramsName, boolean use0, int paramBase, String target, SymbolTable tbl)492 public int recordParams(CtClass[] params, boolean isStatic, 493 String prefix, String paramVarName, 494 String paramsName, boolean use0, 495 int paramBase, String target, 496 SymbolTable tbl) 497 throws CompileError 498 { 499 int varNo; 500 501 paramTypeList = params; 502 paramArrayName = paramVarName; 503 paramListName = paramsName; 504 paramVarBase = paramBase; 505 useParam0 = use0; 506 507 if (target != null) 508 param0Type = MemberResolver.jvmToJavaName(target); 509 510 inStaticMethod = isStatic; 511 varNo = paramBase; 512 if (use0) { 513 String varName = prefix + "0"; 514 Declarator decl 515 = new Declarator(CLASS, MemberResolver.javaToJvmName(target), 516 0, varNo++, new Symbol(varName)); 517 tbl.append(varName, decl); 518 } 519 520 for (int i = 0; i < params.length; ++i) 521 varNo += recordVar(params[i], prefix + (i + 1), varNo, tbl); 522 523 if (getMaxLocals() < varNo) 524 setMaxLocals(varNo); 525 526 return varNo; 527 } 528 529 /** 530 * Makes the given variable name available. 531 * 532 * @param type variable type 533 * @param varName variable name 534 */ recordVariable(CtClass type, String varName, SymbolTable tbl)535 public int recordVariable(CtClass type, String varName, SymbolTable tbl) 536 throws CompileError 537 { 538 if (varName == null) 539 return -1; 540 else { 541 int varNo = getMaxLocals(); 542 int locals = varNo + recordVar(type, varName, varNo, tbl); 543 setMaxLocals(locals); 544 return varNo; 545 } 546 } 547 recordVar(CtClass cc, String varName, int varNo, SymbolTable tbl)548 private int recordVar(CtClass cc, String varName, int varNo, 549 SymbolTable tbl) throws CompileError 550 { 551 if (cc == CtClass.voidType) { 552 exprType = CLASS; 553 arrayDim = 0; 554 className = jvmJavaLangObject; 555 } 556 else 557 setType(cc); 558 559 Declarator decl 560 = new Declarator(exprType, className, arrayDim, 561 varNo, new Symbol(varName)); 562 tbl.append(varName, decl); 563 return is2word(exprType, arrayDim) ? 2 : 1; 564 } 565 566 /** 567 * Makes the given variable name available. 568 * 569 * @param typeDesc the type descriptor of the variable 570 * @param varName variable name 571 * @param varNo an index into the local variable array 572 */ recordVariable(String typeDesc, String varName, int varNo, SymbolTable tbl)573 public void recordVariable(String typeDesc, String varName, int varNo, 574 SymbolTable tbl) throws CompileError 575 { 576 char c; 577 int dim = 0; 578 while ((c = typeDesc.charAt(dim)) == '[') 579 ++dim; 580 581 int type = MemberResolver.descToType(c); 582 String cname = null; 583 if (type == CLASS) { 584 if (dim == 0) 585 cname = typeDesc.substring(1, typeDesc.length() - 1); 586 else 587 cname = typeDesc.substring(dim + 1, typeDesc.length() - 1); 588 } 589 590 Declarator decl 591 = new Declarator(type, cname, dim, varNo, new Symbol(varName)); 592 tbl.append(varName, decl); 593 } 594 595 /* compileParameterList() returns the stack size used 596 * by the produced code. 597 * 598 * This method correctly computes the max_stack value. 599 * 600 * @param regno the index of the local variable in which 601 * the first argument is received. 602 * (0: static method, 1: regular method.) 603 */ compileParameterList(Bytecode code, CtClass[] params, int regno)604 public static int compileParameterList(Bytecode code, 605 CtClass[] params, int regno) { 606 if (params == null) { 607 code.addIconst(0); // iconst_0 608 code.addAnewarray(javaLangObject); // anewarray Object 609 return 1; 610 } 611 else { 612 CtClass[] args = new CtClass[1]; 613 int n = params.length; 614 code.addIconst(n); // iconst_<n> 615 code.addAnewarray(javaLangObject); // anewarray Object 616 for (int i = 0; i < n; ++i) { 617 code.addOpcode(Bytecode.DUP); // dup 618 code.addIconst(i); // iconst_<i> 619 if (params[i].isPrimitive()) { 620 CtPrimitiveType pt = (CtPrimitiveType)params[i]; 621 String wrapper = pt.getWrapperName(); 622 code.addNew(wrapper); // new <wrapper> 623 code.addOpcode(Bytecode.DUP); // dup 624 int s = code.addLoad(regno, pt); // ?load <regno> 625 regno += s; 626 args[0] = pt; 627 code.addInvokespecial(wrapper, "<init>", 628 Descriptor.ofMethod(CtClass.voidType, args)); 629 // invokespecial 630 } 631 else { 632 code.addAload(regno); // aload <regno> 633 ++regno; 634 } 635 636 code.addOpcode(Bytecode.AASTORE); // aastore 637 } 638 639 return 8; 640 } 641 } 642 compileUnwrapValue(CtClass type, Bytecode code)643 protected void compileUnwrapValue(CtClass type, Bytecode code) 644 throws CompileError 645 { 646 if (type == CtClass.voidType) { 647 addNullIfVoid(); 648 return; 649 } 650 651 if (exprType == VOID) 652 throw new CompileError("invalid type for " + returnCastName); 653 654 if (type instanceof CtPrimitiveType) { 655 CtPrimitiveType pt = (CtPrimitiveType)type; 656 // pt is not voidType. 657 String wrapper = pt.getWrapperName(); 658 code.addCheckcast(wrapper); 659 code.addInvokevirtual(wrapper, pt.getGetMethodName(), 660 pt.getGetMethodDescriptor()); 661 setType(type); 662 } 663 else { 664 code.addCheckcast(type); 665 setType(type); 666 } 667 } 668 669 /* Sets exprType, arrayDim, and className; 670 * If type is void, then this method does nothing. 671 */ setType(CtClass type)672 public void setType(CtClass type) throws CompileError { 673 setType(type, 0); 674 } 675 setType(CtClass type, int dim)676 private void setType(CtClass type, int dim) throws CompileError { 677 if (type.isPrimitive()) { 678 CtPrimitiveType pt = (CtPrimitiveType)type; 679 exprType = MemberResolver.descToType(pt.getDescriptor()); 680 arrayDim = dim; 681 className = null; 682 } 683 else if (type.isArray()) 684 try { 685 setType(type.getComponentType(), dim + 1); 686 } 687 catch (NotFoundException e) { 688 throw new CompileError("undefined type: " + type.getName()); 689 } 690 else { 691 exprType = CLASS; 692 arrayDim = dim; 693 className = MemberResolver.javaToJvmName(type.getName()); 694 } 695 } 696 697 /* Performs implicit coercion from exprType to type. 698 */ doNumCast(CtClass type)699 public void doNumCast(CtClass type) throws CompileError { 700 if (arrayDim == 0 && !isRefType(exprType)) 701 if (type instanceof CtPrimitiveType) { 702 CtPrimitiveType pt = (CtPrimitiveType)type; 703 atNumCastExpr(exprType, 704 MemberResolver.descToType(pt.getDescriptor())); 705 } 706 else 707 throw new CompileError("type mismatch"); 708 } 709 } 710