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
16package javassist.compiler;
17
18import javassist.CtClass;
19import javassist.CtPrimitiveType;
20import javassist.CtMember;
21import javassist.CtField;
22import javassist.CtBehavior;
23import javassist.CtMethod;
24import javassist.CtConstructor;
25import javassist.CannotCompileException;
26import javassist.Modifier;
27import javassist.bytecode.Bytecode;
28import javassist.bytecode.CodeAttribute;
29import javassist.bytecode.LocalVariableAttribute;
30import javassist.bytecode.BadBytecode;
31import javassist.bytecode.Opcode;
32import javassist.NotFoundException;
33
34import javassist.compiler.ast.*;
35
36public 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