Home | History | Annotate | Download | only in compiler
      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.CtClass;
     19 import javassist.CtPrimitiveType;
     20 import javassist.CtMember;
     21 import javassist.CtField;
     22 import javassist.CtBehavior;
     23 import javassist.CtMethod;
     24 import javassist.CtConstructor;
     25 import javassist.CannotCompileException;
     26 import javassist.Modifier;
     27 import javassist.bytecode.Bytecode;
     28 import javassist.bytecode.CodeAttribute;
     29 import javassist.bytecode.LocalVariableAttribute;
     30 import javassist.bytecode.BadBytecode;
     31 import javassist.bytecode.Opcode;
     32 import javassist.NotFoundException;
     33 
     34 import javassist.compiler.ast.*;
     35 
     36 public class Javac {
     37     JvstCodeGen gen;
     38     SymbolTable stable;
     39     private Bytecode bytecode;
     40 
     41     public static final String param0Name = "$0";
     42     public static final String resultVarName = "$_";
     43     public static final String proceedName = "$proceed";
     44 
     45     /**
     46      * Constructs a compiler.
     47      *
     48      * @param thisClass         the class that a compiled method/field
     49      *                          belongs to.
     50      */
     51     public Javac(CtClass thisClass) {
     52         this(new Bytecode(thisClass.getClassFile2().getConstPool(), 0, 0),
     53              thisClass);
     54     }
     55 
     56     /**
     57      * Constructs a compiler.
     58      * The produced bytecode is stored in the <code>Bytecode</code> object
     59      * specified by <code>b</code>.
     60      *
     61      * @param thisClass         the class that a compiled method/field
     62      *                          belongs to.
     63      */
     64     public Javac(Bytecode b, CtClass thisClass) {
     65         gen = new JvstCodeGen(b, thisClass, thisClass.getClassPool());
     66         stable = new SymbolTable();
     67         bytecode = b;
     68     }
     69 
     70     /**
     71      * Returns the produced bytecode.
     72      */
     73     public Bytecode getBytecode() { return bytecode; }
     74 
     75     /**
     76      * Compiles a method, constructor, or field declaration
     77      * to a class.
     78      * A field declaration can declare only one field.
     79      *
     80      * <p>In a method or constructor body, $0, $1, ... and $_
     81      * are not available.
     82      *
     83      * @return          a <code>CtMethod</code>, <code>CtConstructor</code>,
     84      *                  or <code>CtField</code> object.
     85      * @see #recordProceed(String,String)
     86      */
     87     public CtMember compile(String src) throws CompileError {
     88         Parser p = new Parser(new Lex(src));
     89         ASTList mem = p.parseMember1(stable);
     90         try {
     91             if (mem instanceof FieldDecl)
     92                 return compileField((FieldDecl)mem);
     93             else {
     94                 CtBehavior cb = compileMethod(p, (MethodDecl)mem);
     95                 CtClass decl = cb.getDeclaringClass();
     96                 cb.getMethodInfo2()
     97                   .rebuildStackMapIf6(decl.getClassPool(),
     98                                       decl.getClassFile2());
     99                 return cb;
    100             }
    101         }
    102         catch (BadBytecode bb) {
    103             throw new CompileError(bb.getMessage());
    104         }
    105         catch (CannotCompileException e) {
    106             throw new CompileError(e.getMessage());
    107         }
    108     }
    109 
    110     public static class CtFieldWithInit extends CtField {
    111         private ASTree init;
    112 
    113         CtFieldWithInit(CtClass type, String name, CtClass declaring)
    114             throws CannotCompileException
    115         {
    116             super(type, name, declaring);
    117             init = null;
    118         }
    119 
    120         protected void setInit(ASTree i) { init = i; }
    121 
    122         protected ASTree getInitAST() {
    123             return init;
    124         }
    125     }
    126 
    127     private CtField compileField(FieldDecl fd)
    128         throws CompileError, CannotCompileException
    129     {
    130         CtFieldWithInit f;
    131         Declarator d = fd.getDeclarator();
    132         f = new CtFieldWithInit(gen.resolver.lookupClass(d),
    133                                 d.getVariable().get(), gen.getThisClass());
    134         f.setModifiers(MemberResolver.getModifiers(fd.getModifiers()));
    135         if (fd.getInit() != null)
    136             f.setInit(fd.getInit());
    137 
    138         return f;
    139     }
    140 
    141     private CtBehavior compileMethod(Parser p, MethodDecl md)
    142         throws CompileError
    143     {
    144         int mod = MemberResolver.getModifiers(md.getModifiers());
    145         CtClass[] plist = gen.makeParamList(md);
    146         CtClass[] tlist = gen.makeThrowsList(md);
    147         recordParams(plist, Modifier.isStatic(mod));
    148         md = p.parseMethod2(stable, md);
    149         try {
    150             if (md.isConstructor()) {
    151                 CtConstructor cons = new CtConstructor(plist,
    152                                                    gen.getThisClass());
    153                 cons.setModifiers(mod);
    154                 md.accept(gen);
    155                 cons.getMethodInfo().setCodeAttribute(
    156                                         bytecode.toCodeAttribute());
    157                 cons.setExceptionTypes(tlist);
    158                 return cons;
    159             }
    160             else {
    161                 Declarator r = md.getReturn();
    162                 CtClass rtype = gen.resolver.lookupClass(r);
    163                 recordReturnType(rtype, false);
    164                 CtMethod method = new CtMethod(rtype, r.getVariable().get(),
    165                                            plist, gen.getThisClass());
    166                 method.setModifiers(mod);
    167                 gen.setThisMethod(method);
    168                 md.accept(gen);
    169                 if (md.getBody() != null)
    170                     method.getMethodInfo().setCodeAttribute(
    171                                         bytecode.toCodeAttribute());
    172                 else
    173                     method.setModifiers(mod | Modifier.ABSTRACT);
    174 
    175                 method.setExceptionTypes(tlist);
    176                 return method;
    177             }
    178         }
    179         catch (NotFoundException e) {
    180             throw new CompileError(e.toString());
    181         }
    182     }
    183 
    184     /**
    185      * Compiles a method (or constructor) body.
    186      *
    187      * @src	a single statement or a block.
    188      *          If null, this method produces a body returning zero or null.
    189      */
    190     public Bytecode compileBody(CtBehavior method, String src)
    191         throws CompileError
    192     {
    193         try {
    194             int mod = method.getModifiers();
    195             recordParams(method.getParameterTypes(), Modifier.isStatic(mod));
    196 
    197             CtClass rtype;
    198             if (method instanceof CtMethod) {
    199                 gen.setThisMethod((CtMethod)method);
    200                 rtype = ((CtMethod)method).getReturnType();
    201             }
    202             else
    203                 rtype = CtClass.voidType;
    204 
    205             recordReturnType(rtype, false);
    206             boolean isVoid = rtype == CtClass.voidType;
    207 
    208             if (src == null)
    209                 makeDefaultBody(bytecode, rtype);
    210             else {
    211                 Parser p = new Parser(new Lex(src));
    212                 SymbolTable stb = new SymbolTable(stable);
    213                 Stmnt s = p.parseStatement(stb);
    214                 if (p.hasMore())
    215                     throw new CompileError(
    216                         "the method/constructor body must be surrounded by {}");
    217 
    218                 boolean callSuper = false;
    219                 if (method instanceof CtConstructor)
    220                     callSuper = !((CtConstructor)method).isClassInitializer();
    221 
    222                 gen.atMethodBody(s, callSuper, isVoid);
    223             }
    224 
    225             return bytecode;
    226         }
    227         catch (NotFoundException e) {
    228             throw new CompileError(e.toString());
    229         }
    230     }
    231 
    232     private static void makeDefaultBody(Bytecode b, CtClass type) {
    233         int op;
    234         int value;
    235         if (type instanceof CtPrimitiveType) {
    236             CtPrimitiveType pt = (CtPrimitiveType)type;
    237             op = pt.getReturnOp();
    238             if (op == Opcode.DRETURN)
    239                 value = Opcode.DCONST_0;
    240             else if (op == Opcode.FRETURN)
    241                 value = Opcode.FCONST_0;
    242             else if (op == Opcode.LRETURN)
    243                 value = Opcode.LCONST_0;
    244             else if (op == Opcode.RETURN)
    245                 value = Opcode.NOP;
    246             else
    247                 value = Opcode.ICONST_0;
    248         }
    249         else {
    250             op = Opcode.ARETURN;
    251             value = Opcode.ACONST_NULL;
    252         }
    253 
    254         if (value != Opcode.NOP)
    255             b.addOpcode(value);
    256 
    257         b.addOpcode(op);
    258     }
    259 
    260     /**
    261      * Records local variables available at the specified program counter.
    262      * If the LocalVariableAttribute is not available, this method does not
    263      * record any local variable.  It only returns false.
    264      *
    265      * @param pc    program counter (&gt;= 0)
    266      * @return false if the CodeAttribute does not include a
    267      *              LocalVariableAttribute.
    268      */
    269     public boolean recordLocalVariables(CodeAttribute ca, int pc)
    270         throws CompileError
    271     {
    272         LocalVariableAttribute va
    273             = (LocalVariableAttribute)
    274               ca.getAttribute(LocalVariableAttribute.tag);
    275         if (va == null)
    276             return false;
    277 
    278         int n = va.tableLength();
    279         for (int i = 0; i < n; ++i) {
    280             int start = va.startPc(i);
    281             int len = va.codeLength(i);
    282             if (start <= pc && pc < start + len)
    283                 gen.recordVariable(va.descriptor(i), va.variableName(i),
    284                                    va.index(i), stable);
    285         }
    286 
    287         return true;
    288     }
    289 
    290     /**
    291      * Records parameter names if the LocalVariableAttribute is available.
    292      * It returns false unless the LocalVariableAttribute is available.
    293      *
    294      * @param numOfLocalVars    the number of local variables used
    295      *                          for storing the parameters.
    296      * @return false if the CodeAttribute does not include a
    297      *              LocalVariableAttribute.
    298      */
    299     public boolean recordParamNames(CodeAttribute ca, int numOfLocalVars)
    300         throws CompileError
    301     {
    302         LocalVariableAttribute va
    303             = (LocalVariableAttribute)
    304               ca.getAttribute(LocalVariableAttribute.tag);
    305         if (va == null)
    306             return false;
    307 
    308         int n = va.tableLength();
    309         for (int i = 0; i < n; ++i) {
    310             int index = va.index(i);
    311             if (index < numOfLocalVars)
    312                 gen.recordVariable(va.descriptor(i), va.variableName(i),
    313                                    index, stable);
    314         }
    315 
    316         return true;
    317     }
    318 
    319 
    320     /**
    321      * Makes variables $0 (this), $1, $2, ..., and $args represent method
    322      * parameters.  $args represents an array of all the parameters.
    323      * It also makes $$ available as a parameter list of method call.
    324      *
    325      * <p>This must be called before calling <code>compileStmnt()</code> and
    326      * <code>compileExpr()</code>.  The correct value of
    327      * <code>isStatic</code> must be recorded before compilation.
    328      * <code>maxLocals</code> is updated to include $0,...
    329      */
    330     public int recordParams(CtClass[] params, boolean isStatic)
    331         throws CompileError
    332     {
    333         return gen.recordParams(params, isStatic, "$", "$args", "$$", stable);
    334     }
    335 
    336     /**
    337      * Makes variables $0, $1, $2, ..., and $args represent method
    338      * parameters.  $args represents an array of all the parameters.
    339      * It also makes $$ available as a parameter list of method call.
    340      * $0 can represent a local variable other than THIS (variable 0).
    341      * $class is also made available.
    342      *
    343      * <p>This must be called before calling <code>compileStmnt()</code> and
    344      * <code>compileExpr()</code>.  The correct value of
    345      * <code>isStatic</code> must be recorded before compilation.
    346      * <code>maxLocals</code> is updated to include $0,...
    347      *
    348      * @paaram use0     true if $0 is used.
    349      * @param varNo     the register number of $0 (use0 is true)
    350      *                          or $1 (otherwise).
    351      * @param target    the type of $0 (it can be null if use0 is false).
    352      *                  It is used as the name of the type represented
    353      *                  by $class.
    354      * @param isStatic  true if the method in which the compiled bytecode
    355      *                  is embedded is static.
    356      */
    357     public int recordParams(String target, CtClass[] params,
    358                              boolean use0, int varNo, boolean isStatic)
    359         throws CompileError
    360     {
    361         return gen.recordParams(params, isStatic, "$", "$args", "$$",
    362                                 use0, varNo, target, stable);
    363     }
    364 
    365     /**
    366      * Sets <code>maxLocals</code> to <code>max</code>.
    367      * This method tells the compiler the local variables that have been
    368      * allocated for the rest of the code.  When the compiler needs
    369      * new local variables, the local variables at the index <code>max</code>,
    370      * <code>max + 1</code>, ... are assigned.
    371      *
    372      * <p>This method is indirectly called by <code>recordParams</code>.
    373      */
    374     public void setMaxLocals(int max) {
    375         gen.setMaxLocals(max);
    376     }
    377 
    378     /**
    379      * Prepares to use cast $r, $w, $_, and $type.
    380      * $type is made to represent the specified return type.
    381      * It also enables to write a return statement with a return value
    382      * for void method.
    383      *
    384      * <p>If the return type is void, ($r) does nothing.
    385      * The type of $_ is java.lang.Object.
    386      *
    387      * @param type              the return type.
    388      * @param useResultVar      true if $_ is used.
    389      * @return          -1 or the variable index assigned to $_.
    390      * @see #recordType(CtClass)
    391      */
    392     public int recordReturnType(CtClass type, boolean useResultVar)
    393         throws CompileError
    394     {
    395         gen.recordType(type);
    396         return gen.recordReturnType(type, "$r",
    397                         (useResultVar ? resultVarName : null), stable);
    398     }
    399 
    400     /**
    401      * Prepares to use $type.  Note that recordReturnType() overwrites
    402      * the value of $type.
    403      *
    404      * @param t     the type represented by $type.
    405      */
    406     public void recordType(CtClass t) {
    407         gen.recordType(t);
    408     }
    409 
    410     /**
    411      * Makes the given variable available.
    412      *
    413      * @param type      variable type
    414      * @param name      variable name
    415      */
    416     public int recordVariable(CtClass type, String name)
    417         throws CompileError
    418     {
    419         return gen.recordVariable(type, name, stable);
    420     }
    421 
    422     /**
    423      * Prepares to use $proceed().
    424      * If the return type of $proceed() is void, null is pushed on the
    425      * stack.
    426      *
    427      * @param target    an expression specifying the target object.
    428      *                          if null, "this" is the target.
    429      * @param method    the method name.
    430      */
    431     public void recordProceed(String target, String method)
    432         throws CompileError
    433     {
    434         Parser p = new Parser(new Lex(target));
    435         final ASTree texpr = p.parseExpression(stable);
    436         final String m = method;
    437 
    438         ProceedHandler h = new ProceedHandler() {
    439                 public void doit(JvstCodeGen gen, Bytecode b, ASTList args)
    440                     throws CompileError
    441                 {
    442                     ASTree expr = new Member(m);
    443                     if (texpr != null)
    444                         expr = Expr.make('.', texpr, expr);
    445 
    446                     expr = CallExpr.makeCall(expr, args);
    447                     gen.compileExpr(expr);
    448                     gen.addNullIfVoid();
    449                 }
    450 
    451                 public void setReturnType(JvstTypeChecker check, ASTList args)
    452                     throws CompileError
    453                 {
    454                     ASTree expr = new Member(m);
    455                     if (texpr != null)
    456                         expr = Expr.make('.', texpr, expr);
    457 
    458                     expr = CallExpr.makeCall(expr, args);
    459                     expr.accept(check);
    460                     check.addNullIfVoid();
    461                 }
    462             };
    463 
    464         gen.setProceedHandler(h, proceedName);
    465     }
    466 
    467     /**
    468      * Prepares to use $proceed() representing a static method.
    469      * If the return type of $proceed() is void, null is pushed on the
    470      * stack.
    471      *
    472      * @param targetClass    the fully-qualified dot-separated name
    473      *				of the class declaring the method.
    474      * @param method         the method name.
    475      */
    476     public void recordStaticProceed(String targetClass, String method)
    477         throws CompileError
    478     {
    479         final String c = targetClass;
    480         final String m = method;
    481 
    482         ProceedHandler h = new ProceedHandler() {
    483                 public void doit(JvstCodeGen gen, Bytecode b, ASTList args)
    484                     throws CompileError
    485                 {
    486                     Expr expr = Expr.make(TokenId.MEMBER,
    487                                           new Symbol(c), new Member(m));
    488                     expr = CallExpr.makeCall(expr, args);
    489                     gen.compileExpr(expr);
    490                     gen.addNullIfVoid();
    491                 }
    492 
    493                 public void setReturnType(JvstTypeChecker check, ASTList args)
    494                     throws CompileError
    495                 {
    496                     Expr expr = Expr.make(TokenId.MEMBER,
    497                                           new Symbol(c), new Member(m));
    498                     expr = CallExpr.makeCall(expr, args);
    499                     expr.accept(check);
    500                     check.addNullIfVoid();
    501                 }
    502             };
    503 
    504         gen.setProceedHandler(h, proceedName);
    505     }
    506 
    507     /**
    508      * Prepares to use $proceed() representing a private/super's method.
    509      * If the return type of $proceed() is void, null is pushed on the
    510      * stack.  This method is for methods invoked by INVOKESPECIAL.
    511      *
    512      * @param target    an expression specifying the target object.
    513      *                          if null, "this" is the target.
    514      * @param classname	    the class name declaring the method.
    515      * @param methodname    the method name.
    516      * @param descriptor    the method descriptor.
    517      */
    518     public void recordSpecialProceed(String target, String classname,
    519                                      String methodname, String descriptor)
    520         throws CompileError
    521     {
    522         Parser p = new Parser(new Lex(target));
    523         final ASTree texpr = p.parseExpression(stable);
    524         final String cname = classname;
    525         final String method = methodname;
    526         final String desc = descriptor;
    527 
    528         ProceedHandler h = new ProceedHandler() {
    529                 public void doit(JvstCodeGen gen, Bytecode b, ASTList args)
    530                     throws CompileError
    531                 {
    532                     gen.compileInvokeSpecial(texpr, cname, method, desc, args);
    533                 }
    534 
    535                 public void setReturnType(JvstTypeChecker c, ASTList args)
    536                     throws CompileError
    537                 {
    538                     c.compileInvokeSpecial(texpr, cname, method, desc, args);
    539                 }
    540 
    541             };
    542 
    543         gen.setProceedHandler(h, proceedName);
    544     }
    545 
    546     /**
    547      * Prepares to use $proceed().
    548      */
    549     public void recordProceed(ProceedHandler h) {
    550         gen.setProceedHandler(h, proceedName);
    551     }
    552 
    553     /**
    554      * Compiles a statement (or a block).
    555      * <code>recordParams()</code> must be called before invoking
    556      * this method.
    557      *
    558      * <p>Local variables that are not declared
    559      * in the compiled source text might not be accessible within that
    560      * source text.  Fields and method parameters ($0, $1, ..) are available.
    561      */
    562     public void compileStmnt(String src) throws CompileError {
    563         Parser p = new Parser(new Lex(src));
    564         SymbolTable stb = new SymbolTable(stable);
    565         while (p.hasMore()) {
    566             Stmnt s = p.parseStatement(stb);
    567             if (s != null)
    568                 s.accept(gen);
    569         }
    570     }
    571 
    572     /**
    573      * Compiles an exression.  <code>recordParams()</code> must be
    574      * called before invoking this method.
    575      *
    576      * <p>Local variables are not accessible
    577      * within the compiled source text.  Fields and method parameters
    578      * ($0, $1, ..) are available if <code>recordParams()</code>
    579      * have been invoked.
    580      */
    581     public void compileExpr(String src) throws CompileError {
    582         ASTree e = parseExpr(src, stable);
    583         compileExpr(e);
    584     }
    585 
    586     /**
    587      * Parsers an expression.
    588      */
    589     public static ASTree parseExpr(String src, SymbolTable st)
    590         throws CompileError
    591     {
    592         Parser p = new Parser(new Lex(src));
    593         return p.parseExpression(st);
    594     }
    595 
    596     /**
    597      * Compiles an exression.  <code>recordParams()</code> must be
    598      * called before invoking this method.
    599      *
    600      * <p>Local variables are not accessible
    601      * within the compiled source text.  Fields and method parameters
    602      * ($0, $1, ..) are available if <code>recordParams()</code>
    603      * have been invoked.
    604      */
    605     public void compileExpr(ASTree e) throws CompileError {
    606         if (e != null)
    607             gen.compileExpr(e);
    608     }
    609 }
    610