com.feilong.lib.javassist.compiler.Javac Maven / Gradle / Ivy
Show all versions of feilong Show documentation
/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. Alternatively, the contents of this file may be used under
* the terms of the GNU Lesser General Public License Version 2.1 or later,
* or the Apache License Version 2.0.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*/
package com.feilong.lib.javassist.compiler;
import com.feilong.lib.javassist.CannotCompileException;
import com.feilong.lib.javassist.CtBehavior;
import com.feilong.lib.javassist.CtClass;
import com.feilong.lib.javassist.CtConstructor;
import com.feilong.lib.javassist.CtField;
import com.feilong.lib.javassist.CtMember;
import com.feilong.lib.javassist.CtMethod;
import com.feilong.lib.javassist.CtPrimitiveType;
import com.feilong.lib.javassist.Modifier;
import com.feilong.lib.javassist.NotFoundException;
import com.feilong.lib.javassist.bytecode.BadBytecode;
import com.feilong.lib.javassist.bytecode.Bytecode;
import com.feilong.lib.javassist.bytecode.CodeAttribute;
import com.feilong.lib.javassist.bytecode.LocalVariableAttribute;
import com.feilong.lib.javassist.bytecode.Opcode;
import com.feilong.lib.javassist.compiler.ast.ASTList;
import com.feilong.lib.javassist.compiler.ast.ASTree;
import com.feilong.lib.javassist.compiler.ast.CallExpr;
import com.feilong.lib.javassist.compiler.ast.Declarator;
import com.feilong.lib.javassist.compiler.ast.Expr;
import com.feilong.lib.javassist.compiler.ast.FieldDecl;
import com.feilong.lib.javassist.compiler.ast.Member;
import com.feilong.lib.javassist.compiler.ast.MethodDecl;
import com.feilong.lib.javassist.compiler.ast.Stmnt;
import com.feilong.lib.javassist.compiler.ast.Symbol;
public class Javac{
JvstCodeGen gen;
SymbolTable stable;
private Bytecode bytecode;
public static final String param0Name = "$0";
public static final String resultVarName = "$_";
public static final String proceedName = "$proceed";
/**
* Constructs a compiler.
*
* @param thisClass
* the class that a compiled method/field
* belongs to.
*/
public Javac(CtClass thisClass){
this(new Bytecode(thisClass.getClassFile2().getConstPool(), 0, 0), thisClass);
}
/**
* Constructs a compiler.
* The produced bytecode is stored in the Bytecode
object
* specified by b
.
*
* @param thisClass
* the class that a compiled method/field
* belongs to.
*/
public Javac(Bytecode b, CtClass thisClass){
gen = new JvstCodeGen(b, thisClass, thisClass.getClassPool());
stable = new SymbolTable();
bytecode = b;
}
/**
* Returns the produced bytecode.
*/
public Bytecode getBytecode(){
return bytecode;
}
/**
* Compiles a method, constructor, or field declaration
* to a class.
* A field declaration can declare only one field.
*
*
* In a method or constructor body, $0, $1, ... and $_
* are not available.
*
* @return a CtMethod
, CtConstructor
,
* or CtField
object.
* @see #recordProceed(String,String)
*/
public CtMember compile(String src) throws CompileError{
Parser p = new Parser(new Lex(src));
ASTList mem = p.parseMember1(stable);
try{
if (mem instanceof FieldDecl){
return compileField((FieldDecl) mem);
}
CtBehavior cb = compileMethod(p, (MethodDecl) mem);
CtClass decl = cb.getDeclaringClass();
cb.getMethodInfo2().rebuildStackMapIf6(decl.getClassPool(), decl.getClassFile2());
return cb;
}catch (BadBytecode bb){
throw new CompileError(bb.getMessage());
}catch (CannotCompileException e){
throw new CompileError(e.getMessage());
}
}
public static class CtFieldWithInit extends CtField{
private ASTree init;
CtFieldWithInit(CtClass type, String name, CtClass declaring) throws CannotCompileException{
super(type, name, declaring);
init = null;
}
protected void setInit(ASTree i){
init = i;
}
@Override
protected ASTree getInitAST(){
return init;
}
}
private CtField compileField(FieldDecl fd) throws CompileError,CannotCompileException{
CtFieldWithInit f;
Declarator d = fd.getDeclarator();
f = new CtFieldWithInit(gen.resolver.lookupClass(d), d.getVariable().get(), gen.getThisClass());
f.setModifiers(MemberResolver.getModifiers(fd.getModifiers()));
if (fd.getInit() != null){
f.setInit(fd.getInit());
}
return f;
}
private CtBehavior compileMethod(Parser p,MethodDecl md) throws CompileError{
int mod = MemberResolver.getModifiers(md.getModifiers());
CtClass[] plist = gen.makeParamList(md);
CtClass[] tlist = gen.makeThrowsList(md);
recordParams(plist, Modifier.isStatic(mod));
md = p.parseMethod2(stable, md);
try{
if (md.isConstructor()){
CtConstructor cons = new CtConstructor(plist, gen.getThisClass());
cons.setModifiers(mod);
md.accept(gen);
cons.getMethodInfo().setCodeAttribute(bytecode.toCodeAttribute());
cons.setExceptionTypes(tlist);
return cons;
}
Declarator r = md.getReturn();
CtClass rtype = gen.resolver.lookupClass(r);
recordReturnType(rtype, false);
CtMethod method = new CtMethod(rtype, r.getVariable().get(), plist, gen.getThisClass());
method.setModifiers(mod);
gen.setThisMethod(method);
md.accept(gen);
if (md.getBody() != null){
method.getMethodInfo().setCodeAttribute(bytecode.toCodeAttribute());
}else{
method.setModifiers(mod | Modifier.ABSTRACT);
}
method.setExceptionTypes(tlist);
return method;
}catch (NotFoundException e){
throw new CompileError(e.toString());
}
}
/**
* Compiles a method (or constructor) body.
*
* @param src
* a single statement or a block.
* If null, this method produces a body returning zero or null.
*/
public Bytecode compileBody(CtBehavior method,String src) throws CompileError{
try{
int mod = method.getModifiers();
recordParams(method.getParameterTypes(), Modifier.isStatic(mod));
CtClass rtype;
if (method instanceof CtMethod){
gen.setThisMethod((CtMethod) method);
rtype = ((CtMethod) method).getReturnType();
}else{
rtype = CtClass.voidType;
}
recordReturnType(rtype, false);
boolean isVoid = rtype == CtClass.voidType;
if (src == null){
makeDefaultBody(bytecode, rtype);
}else{
Parser p = new Parser(new Lex(src));
SymbolTable stb = new SymbolTable(stable);
Stmnt s = p.parseStatement(stb);
if (p.hasMore()){
throw new CompileError("the method/constructor body must be surrounded by {}");
}
boolean callSuper = false;
if (method instanceof CtConstructor){
callSuper = !((CtConstructor) method).isClassInitializer();
}
gen.atMethodBody(s, callSuper, isVoid);
}
return bytecode;
}catch (NotFoundException e){
throw new CompileError(e.toString());
}
}
private static void makeDefaultBody(Bytecode b,CtClass type){
int op;
int value;
if (type instanceof CtPrimitiveType){
CtPrimitiveType pt = (CtPrimitiveType) type;
op = pt.getReturnOp();
if (op == Opcode.DRETURN){
value = Opcode.DCONST_0;
}else if (op == Opcode.FRETURN){
value = Opcode.FCONST_0;
}else if (op == Opcode.LRETURN){
value = Opcode.LCONST_0;
}else if (op == Opcode.RETURN){
value = Opcode.NOP;
}else{
value = Opcode.ICONST_0;
}
}else{
op = Opcode.ARETURN;
value = Opcode.ACONST_NULL;
}
if (value != Opcode.NOP){
b.addOpcode(value);
}
b.addOpcode(op);
}
/**
* Records local variables available at the specified program counter.
* If the LocalVariableAttribute is not available, this method does not
* record any local variable. It only returns false.
*
* @param pc
* program counter (>= 0)
* @return false if the CodeAttribute does not include a
* LocalVariableAttribute.
*/
public boolean recordLocalVariables(CodeAttribute ca,int pc) throws CompileError{
LocalVariableAttribute va = (LocalVariableAttribute) ca.getAttribute(LocalVariableAttribute.tag);
if (va == null){
return false;
}
int n = va.tableLength();
for (int i = 0; i < n; ++i){
int start = va.startPc(i);
int len = va.codeLength(i);
if (start <= pc && pc < start + len){
gen.recordVariable(va.descriptor(i), va.variableName(i), va.index(i), stable);
}
}
return true;
}
/**
* Records parameter names if the LocalVariableAttribute is available.
* It returns false unless the LocalVariableAttribute is available.
*
* @param numOfLocalVars
* the number of local variables used
* for storing the parameters.
* @return false if the CodeAttribute does not include a
* LocalVariableAttribute.
*/
public boolean recordParamNames(CodeAttribute ca,int numOfLocalVars) throws CompileError{
LocalVariableAttribute va = (LocalVariableAttribute) ca.getAttribute(LocalVariableAttribute.tag);
if (va == null){
return false;
}
int n = va.tableLength();
for (int i = 0; i < n; ++i){
int index = va.index(i);
if (index < numOfLocalVars){
gen.recordVariable(va.descriptor(i), va.variableName(i), index, stable);
}
}
return true;
}
/**
* Makes variables $0 (this), $1, $2, ..., and $args represent method
* parameters. $args represents an array of all the parameters.
* It also makes $$ available as a parameter list of method call.
*
*
* This must be called before calling compileStmnt()
and
* compileExpr()
. The correct value of
* isStatic
must be recorded before compilation.
* maxLocals
is updated to include $0,...
*/
public int recordParams(CtClass[] params,boolean isStatic) throws CompileError{
return gen.recordParams(params, isStatic, "$", "$args", "$$", stable);
}
/**
* Makes variables $0, $1, $2, ..., and $args represent method
* parameters. $args represents an array of all the parameters.
* It also makes $$ available as a parameter list of method call.
* $0 can represent a local variable other than THIS (variable 0).
* $class is also made available.
*
*
* This must be called before calling compileStmnt()
and
* compileExpr()
. The correct value of
* isStatic
must be recorded before compilation.
* maxLocals
is updated to include $0,...
*
* @param use0
* true if $0 is used.
* @param varNo
* the register number of $0 (use0 is true)
* or $1 (otherwise).
* @param target
* the type of $0 (it can be null if use0 is false).
* It is used as the name of the type represented
* by $class.
* @param isStatic
* true if the method in which the compiled bytecode
* is embedded is static.
*/
public int recordParams(String target,CtClass[] params,boolean use0,int varNo,boolean isStatic) throws CompileError{
return gen.recordParams(params, isStatic, "$", "$args", "$$", use0, varNo, target, stable);
}
/**
* Sets maxLocals
to max
.
* This method tells the compiler the local variables that have been
* allocated for the rest of the code. When the compiler needs
* new local variables, the local variables at the index max
,
* max + 1
, ... are assigned.
*
*
* This method is indirectly called by recordParams
.
*/
public void setMaxLocals(int max){
gen.setMaxLocals(max);
}
/**
* Prepares to use cast $r, $w, $_, and $type.
* $type is made to represent the specified return type.
* It also enables to write a return statement with a return value
* for void method.
*
*
* If the return type is void, ($r) does nothing.
* The type of $_ is java.lang.Object.
*
* @param type
* the return type.
* @param useResultVar
* true if $_ is used.
* @return -1 or the variable index assigned to $_.
* @see #recordType(CtClass)
*/
public int recordReturnType(CtClass type,boolean useResultVar) throws CompileError{
gen.recordType(type);
return gen.recordReturnType(type, "$r", (useResultVar ? resultVarName : null), stable);
}
/**
* Prepares to use $type. Note that recordReturnType() overwrites
* the value of $type.
*
* @param t
* the type represented by $type.
*/
public void recordType(CtClass t){
gen.recordType(t);
}
/**
* Makes the given variable available.
*
* @param type
* variable type
* @param name
* variable name
*/
public int recordVariable(CtClass type,String name) throws CompileError{
return gen.recordVariable(type, name, stable);
}
/**
* Prepares to use $proceed().
* If the return type of $proceed() is void, null is pushed on the
* stack.
*
* @param target
* an expression specifying the target object.
* if null, "this" is the target.
* @param method
* the method name.
*/
public void recordProceed(String target,String method) throws CompileError{
Parser p = new Parser(new Lex(target));
final ASTree texpr = p.parseExpression(stable);
final String m = method;
ProceedHandler h = new ProceedHandler(){
@Override
public void doit(JvstCodeGen gen,Bytecode b,ASTList args) throws CompileError{
ASTree expr = new Member(m);
if (texpr != null){
expr = Expr.make('.', texpr, expr);
}
expr = CallExpr.makeCall(expr, args);
gen.compileExpr(expr);
gen.addNullIfVoid();
}
@Override
public void setReturnType(JvstTypeChecker check,ASTList args) throws CompileError{
ASTree expr = new Member(m);
if (texpr != null){
expr = Expr.make('.', texpr, expr);
}
expr = CallExpr.makeCall(expr, args);
expr.accept(check);
check.addNullIfVoid();
}
};
gen.setProceedHandler(h, proceedName);
}
/**
* Prepares to use $proceed() representing a static method.
* If the return type of $proceed() is void, null is pushed on the
* stack.
*
* @param targetClass
* the fully-qualified dot-separated name
* of the class declaring the method.
* @param method
* the method name.
*/
public void recordStaticProceed(String targetClass,String method) throws CompileError{
final String c = targetClass;
final String m = method;
ProceedHandler h = new ProceedHandler(){
@Override
public void doit(JvstCodeGen gen,Bytecode b,ASTList args) throws CompileError{
Expr expr = Expr.make(TokenId.MEMBER, new Symbol(c), new Member(m));
expr = CallExpr.makeCall(expr, args);
gen.compileExpr(expr);
gen.addNullIfVoid();
}
@Override
public void setReturnType(JvstTypeChecker check,ASTList args) throws CompileError{
Expr expr = Expr.make(TokenId.MEMBER, new Symbol(c), new Member(m));
expr = CallExpr.makeCall(expr, args);
expr.accept(check);
check.addNullIfVoid();
}
};
gen.setProceedHandler(h, proceedName);
}
/**
* Prepares to use $proceed() representing a private/super's method.
* If the return type of $proceed() is void, null is pushed on the
* stack. This method is for methods invoked by INVOKESPECIAL.
*
* @param target
* an expression specifying the target object.
* if null, "this" is the target.
* @param classname
* the class name declaring the method.
* @param methodname
* the method name.
* @param descriptor
* the method descriptor.
*/
public void recordSpecialProceed(
String target,
final String classname,
final String methodname,
final String descriptor,
final int methodIndex) throws CompileError{
Parser p = new Parser(new Lex(target));
final ASTree texpr = p.parseExpression(stable);
ProceedHandler h = new ProceedHandler(){
@Override
public void doit(JvstCodeGen gen,Bytecode b,ASTList args) throws CompileError{
gen.compileInvokeSpecial(texpr, methodIndex, descriptor, args);
}
@Override
public void setReturnType(JvstTypeChecker c,ASTList args) throws CompileError{
c.compileInvokeSpecial(texpr, classname, methodname, descriptor, args);
}
};
gen.setProceedHandler(h, proceedName);
}
/**
* Prepares to use $proceed().
*/
public void recordProceed(ProceedHandler h){
gen.setProceedHandler(h, proceedName);
}
/**
* Compiles a statement (or a block).
* recordParams()
must be called before invoking
* this method.
*
*
* Local variables that are not declared
* in the compiled source text might not be accessible within that
* source text. Fields and method parameters ($0, $1, ..) are available.
*/
public void compileStmnt(String src) throws CompileError{
Parser p = new Parser(new Lex(src));
SymbolTable stb = new SymbolTable(stable);
while (p.hasMore()){
Stmnt s = p.parseStatement(stb);
if (s != null){
s.accept(gen);
}
}
}
/**
* Compiles an exression. recordParams()
must be
* called before invoking this method.
*
*
* Local variables are not accessible
* within the compiled source text. Fields and method parameters
* ($0, $1, ..) are available if recordParams()
* have been invoked.
*/
public void compileExpr(String src) throws CompileError{
ASTree e = parseExpr(src, stable);
compileExpr(e);
}
/**
* Parsers an expression.
*/
public static ASTree parseExpr(String src,SymbolTable st) throws CompileError{
Parser p = new Parser(new Lex(src));
return p.parseExpression(st);
}
/**
* Compiles an exression. recordParams()
must be
* called before invoking this method.
*
*
* Local variables are not accessible
* within the compiled source text. Fields and method parameters
* ($0, $1, ..) are available if recordParams()
* have been invoked.
*/
public void compileExpr(ASTree e) throws CompileError{
if (e != null){
gen.compileExpr(e);
}
}
}