scouter.javassist.compiler.Javac Maven / Gradle / Ivy
/*
* 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 scouter.javassist.compiler;
import scouter.javassist.CannotCompileException;
import scouter.javassist.CtBehavior;
import scouter.javassist.CtClass;
import scouter.javassist.CtConstructor;
import scouter.javassist.CtField;
import scouter.javassist.CtMember;
import scouter.javassist.CtMethod;
import scouter.javassist.CtPrimitiveType;
import scouter.javassist.Modifier;
import scouter.javassist.NotFoundException;
import scouter.javassist.bytecode.BadBytecode;
import scouter.javassist.bytecode.Bytecode;
import scouter.javassist.bytecode.CodeAttribute;
import scouter.javassist.bytecode.LocalVariableAttribute;
import scouter.javassist.bytecode.Opcode;
import scouter.javassist.compiler.ast.ASTList;
import scouter.javassist.compiler.ast.ASTree;
import scouter.javassist.compiler.ast.CallExpr;
import scouter.javassist.compiler.ast.Declarator;
import scouter.javassist.compiler.ast.Expr;
import scouter.javassist.compiler.ast.FieldDecl;
import scouter.javassist.compiler.ast.Member;
import scouter.javassist.compiler.ast.MethodDecl;
import scouter.javassist.compiler.ast.Stmnt;
import scouter.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);
}
}