git-svn-id: http://anonsvn.jboss.org/repos/javassist/trunk@129 30ef5769-5b8d-40dd-aea6-55b5d6557bb3tags/rel_3_17_1_ga
@@ -262,6 +262,9 @@ see javassist.Dump. | |||
changed. | |||
<li>javassist.expr.NewArray has been implemented. It enables modifying | |||
an expression for array creation. | |||
<li><code>.class</code> notation has been supported. The modified class | |||
file needs javassist.runtime.DotClass at runtime. | |||
<li>a bug in <code>CtClass.getMethods()</code> has been fixed. | |||
</ul> | |||
<p>- version 3.0 beta in May 18th, 2004. | |||
@@ -565,7 +568,7 @@ Andreas Salathe, Dante Torres estrada, S. Pam, Nuno Santos, | |||
Denis Taye, Colin Sampaleanu, Robert Bialek, Asato Shimotaki, | |||
Howard Lewis Ship, Richard Jones, Marjan Sterjev, | |||
Bruce McDonald, Mark Brennan, Vlad Skarzhevskyy, | |||
Brett Randall, and Tsuyoshi Murakami | |||
Brett Randall, Tsuyoshi Murakami, and Nathan Meyers | |||
for their contributions. | |||
<p><br> |
@@ -199,28 +199,20 @@ public abstract class CodeGen extends Visitor implements Opcode, TokenId { | |||
} | |||
public void compileExpr(ASTree expr) throws CompileError { | |||
expr = doTypeCheck(expr); | |||
doTypeCheck(expr); | |||
expr.accept(this); | |||
} | |||
public boolean compileBooleanExpr(boolean branchIf, ASTree expr) | |||
throws CompileError | |||
{ | |||
expr = doTypeCheck(expr); | |||
doTypeCheck(expr); | |||
return booleanExpr(branchIf, expr); | |||
} | |||
/* This returns a different expression from the given one | |||
* if the given expression has been modified. | |||
*/ | |||
private ASTree doTypeCheck(ASTree expr) throws CompileError { | |||
if (typeChecker != null) { | |||
public void doTypeCheck(ASTree expr) throws CompileError { | |||
if (typeChecker != null) | |||
expr.accept(typeChecker); | |||
ASTree expr2 = typeChecker.modifiedExpr; | |||
return expr2 == null ? expr : expr2; | |||
} | |||
else | |||
return expr; | |||
} | |||
public void atASTList(ASTList n) throws CompileError { fatal(); } | |||
@@ -308,7 +300,7 @@ public abstract class CodeGen extends Visitor implements Opcode, TokenId { | |||
int op = st.getOperator(); | |||
if (op == EXPR) { | |||
ASTree expr = st.getLeft(); | |||
expr = doTypeCheck(expr); | |||
doTypeCheck(expr); | |||
if (expr instanceof AssignExpr) | |||
atAssignExpr((AssignExpr)expr, false); | |||
else if (isPlusPlusExpr(expr)) { | |||
@@ -568,7 +560,7 @@ public abstract class CodeGen extends Visitor implements Opcode, TokenId { | |||
*/ | |||
ASTree init = d.getInitializer(); | |||
if (init != null) { | |||
init = doTypeCheck(init); | |||
doTypeCheck(init); | |||
atVariableAssign(null, '=', null, d, init, false); | |||
} | |||
} | |||
@@ -798,10 +790,13 @@ public abstract class CodeGen extends Visitor implements Opcode, TokenId { | |||
int k = lookupBinOp(token); | |||
if (k >= 0) { | |||
expr.oprand1().accept(this); | |||
ASTree right = expr.oprand2(); | |||
if (right == null) | |||
return; // see TypeChecker.atBinExpr(). | |||
int type1 = exprType; | |||
int dim1 = arrayDim; | |||
String cname1 = className; | |||
ASTree right = expr.oprand2(); | |||
right.accept(this); | |||
if (dim1 != arrayDim) | |||
throw new CompileError("incompatible array types"); |
@@ -17,17 +17,11 @@ package javassist.compiler; | |||
import javassist.CtClass; | |||
import javassist.CtField; | |||
import javassist.Modifier; | |||
import javassist.ClassPool; | |||
import javassist.NotFoundException; | |||
import javassist.compiler.ast.*; | |||
import javassist.bytecode.*; | |||
/** | |||
* This class does type checking and, if needed, transformes the original | |||
* abstract syntax tree. The resulting tree is available from | |||
* this.modifiedExpr. | |||
*/ | |||
public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
static final String javaLangObject = "java.lang.Object"; | |||
static final String jvmJavaLangObject = "java/lang/Object"; | |||
@@ -40,7 +34,6 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
protected int exprType; // VOID, NULL, CLASS, BOOLEAN, INT, ... | |||
protected int arrayDim; | |||
protected String className; // JVM-internal representation | |||
protected ASTree modifiedExpr; // null if the given expr was not changed | |||
protected MemberResolver resolver; | |||
protected CtClass thisClass; | |||
@@ -105,7 +98,6 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
exprType = CLASS; | |||
arrayDim = 0; | |||
className = MemberResolver.javaToJvmName(cname); | |||
modifiedExpr = null; | |||
} | |||
} | |||
@@ -117,12 +109,8 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
atMultiNewArray(type, classname, size); | |||
else { | |||
size.head().accept(this); | |||
if (modifiedExpr != null) | |||
size.setHead(modifiedExpr); | |||
exprType = type; | |||
arrayDim = 1; | |||
modifiedExpr = null; | |||
if (type == CLASS) | |||
className = resolveClassName(classname); | |||
else | |||
@@ -142,11 +130,8 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
++count; | |||
s.accept(this); | |||
if (modifiedExpr != null) | |||
size.setHead(modifiedExpr); | |||
} | |||
modifiedExpr = null; | |||
exprType = type; | |||
arrayDim = dim; | |||
if (type == CLASS) | |||
@@ -189,80 +174,49 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
int varArray = d.getArrayDim(); | |||
String varClass = d.getClassName(); | |||
if (op != '=') { | |||
if (op != '=') | |||
atVariable(var); | |||
if (modifiedExpr != null) | |||
expr.setOprand1(modifiedExpr); | |||
} | |||
right.accept(this); | |||
if (modifiedExpr != null) | |||
expr.setOprand2(modifiedExpr); | |||
exprType = varType; | |||
arrayDim = varArray; | |||
className = varClass; | |||
modifiedExpr = null; | |||
} | |||
private void atArrayAssign(Expr expr, int op, Expr array, | |||
ASTree right) throws CompileError | |||
{ | |||
atArrayRead(array); | |||
if (modifiedExpr != null) | |||
expr.setOprand1(modifiedExpr); | |||
atArrayRead(array.oprand1(), array.oprand2()); | |||
int aType = exprType; | |||
int aDim = arrayDim; | |||
String cname = className; | |||
right.accept(this); | |||
if (modifiedExpr != null) | |||
expr.setOprand2(modifiedExpr); | |||
exprType = aType; | |||
arrayDim = aDim; | |||
className = cname; | |||
modifiedExpr = null; | |||
} | |||
protected void atFieldAssign(Expr expr, int op, ASTree left, ASTree right) | |||
throws CompileError | |||
{ | |||
CtField f = atFieldRead(left); | |||
CtField f = fieldAccess(left); | |||
atFieldRead(f); | |||
int fType = exprType; | |||
int fDim = arrayDim; | |||
String cname = className; | |||
if (modifiedExpr != null) | |||
expr.setOprand1(modifiedExpr); | |||
if (Modifier.isFinal(f.getModifiers())) | |||
throw new CompileError("assignment to a final field"); | |||
right.accept(this); | |||
if (modifiedExpr != null) | |||
expr.setOprand2(modifiedExpr); | |||
exprType = fType; | |||
arrayDim = fDim; | |||
className = cname; | |||
modifiedExpr = null; | |||
} | |||
public void atCondExpr(CondExpr expr) throws CompileError { | |||
booleanExpr(expr.condExpr()); | |||
if (modifiedExpr != null) | |||
expr.setCond(modifiedExpr); | |||
expr.thenExpr().accept(this); | |||
if (modifiedExpr != null) | |||
expr.setThen(modifiedExpr); | |||
int type1 = exprType; | |||
int dim1 = arrayDim; | |||
String cname1 = className; | |||
expr.elseExpr().accept(this); | |||
if (modifiedExpr != null) | |||
expr.setElse(modifiedExpr); | |||
if (dim1 == 0 && dim1 == arrayDim) | |||
if (CodeGen.rightIsStrong(type1, exprType)) | |||
@@ -271,89 +225,63 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
expr.setElse(new CastExpr(type1, 0, expr.elseExpr())); | |||
exprType = type1; | |||
} | |||
modifiedExpr = null; | |||
} | |||
public void atBinExpr(BinExpr expr) throws CompileError { | |||
int token = expr.getOperator(); | |||
int k = CodeGen.lookupBinOp(token); | |||
if (k < 0) { | |||
/* equation: &&, ||, ==, !=, <=, >=, <, > | |||
*/ | |||
booleanExpr(expr); | |||
} | |||
else { | |||
if (k >= 0) { | |||
/* arithmetic operators: +, -, *, /, %, |, ^, &, <<, >>, >>> | |||
*/ | |||
if (token != '+') | |||
atNonPlusExpr(expr, token); | |||
else { | |||
if (token == '+') { | |||
Expr e = atPlusExpr(expr); | |||
if (e != null) { | |||
/* String concatenation has been translated into | |||
* an expression using StringBuffer. | |||
*/ | |||
e = CallExpr.makeCall(Expr.make('.', e, | |||
new Member("toString")), null); | |||
new Member("toString")), null); | |||
expr.setLeft(e); | |||
expr.setOprand2(null); // <---- look at this! | |||
className = jvmJavaLangString; | |||
modifiedExpr = e; // expr will be replaced with e. | |||
} | |||
} | |||
else { | |||
expr.oprand1().accept(this); | |||
int type1 = exprType; | |||
expr.oprand2().accept(this); | |||
computeBinExprType(expr, token, type1); | |||
} | |||
} | |||
} | |||
private void atNonPlusExpr(BinExpr expr, int token) throws CompileError { | |||
ASTree left = expr.oprand1(); | |||
ASTree right = expr.oprand2(); | |||
left.accept(this); | |||
if (modifiedExpr != null) { | |||
left = modifiedExpr; | |||
expr.setOprand1(left); | |||
} | |||
int type1 = exprType; | |||
right.accept(this); | |||
if (modifiedExpr != null) { | |||
right = modifiedExpr; | |||
expr.setOprand2(right); | |||
else { | |||
/* equation: &&, ||, ==, !=, <=, >=, <, > | |||
*/ | |||
booleanExpr(expr); | |||
} | |||
modifiedExpr = computeConstExpr(token, left, right); | |||
computeBinExprType(expr, token, type1); | |||
} | |||
/* This method deals with string concatenation. It converts a + | |||
* expression on String such as: | |||
* "value:" + i + "." | |||
* into: | |||
* new StringBuffer().append("value:").append(i).append(".") | |||
* .toString() | |||
* | |||
* This method also inserts a cast operator for the right operand | |||
* if needed. | |||
* | |||
* EXPR must be a + expression. | |||
* | |||
* atPlusExpr() returns null if the expression is not a string | |||
* concatenation. | |||
*/ | |||
// expr must be a + expression. | |||
private Expr atPlusExpr(BinExpr expr) throws CompileError { | |||
ASTree left = expr.oprand1(); | |||
ASTree right = expr.oprand2(); | |||
if (right == null) { | |||
/* this expression has been already type-checked since it is | |||
string concatenation. | |||
see atBinExpr() above. | |||
*/ | |||
exprType = CLASS; | |||
arrayDim = 0; | |||
className = jvmJavaLangString; | |||
return null; | |||
} | |||
if (isPlusExpr(left)) { | |||
Expr newExpr = atPlusExpr((BinExpr)left); | |||
if (newExpr != null) { | |||
right.accept(this); | |||
if (modifiedExpr != null) | |||
right = modifiedExpr; | |||
exprType = CLASS; | |||
arrayDim = 0; | |||
className = "java/lang/StringBuffer"; | |||
modifiedExpr = null; | |||
return makeAppendCall(newExpr, right); | |||
} | |||
} | |||
@@ -363,37 +291,17 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
int type1 = exprType; | |||
int dim1 = arrayDim; | |||
String cname = className; | |||
if (modifiedExpr != null) { | |||
left = modifiedExpr; | |||
expr.setOprand1(left); | |||
} | |||
right.accept(this); | |||
if (modifiedExpr != null) { | |||
right = modifiedExpr; | |||
expr.setOprand2(right); | |||
} | |||
modifiedExpr = computeConstExpr('+', left, right); | |||
if ((type1 == CLASS && dim1 == 0 && jvmJavaLangString.equals(cname)) | |||
|| (exprType == CLASS && arrayDim == 0 | |||
&& jvmJavaLangString.equals(className))) | |||
{ | |||
&& jvmJavaLangString.equals(className))) { | |||
ASTList sbufClass = ASTList.make(new Symbol("java"), | |||
new Symbol("lang"), new Symbol("StringBuffer")); | |||
ASTree e = new NewExpr(sbufClass, null); | |||
exprType = CLASS; | |||
arrayDim = 0; | |||
if (modifiedExpr != null) { | |||
// this expression is constant. | |||
className = jvmJavaLangString; | |||
return null; | |||
} | |||
else { | |||
className = "java/lang/StringBuffer"; | |||
ASTList sbufClass = ASTList.make(new Symbol("java"), | |||
new Symbol("lang"), | |||
new Symbol("StringBuffer")); | |||
ASTree e = new NewExpr(sbufClass, null); | |||
return makeAppendCall(makeAppendCall(e, left), right); | |||
} | |||
className = "java/lang/StringBuffer"; | |||
return makeAppendCall(makeAppendCall(e, left), right); | |||
} | |||
else { | |||
computeBinExprType(expr, '+', type1); | |||
@@ -428,54 +336,33 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
if (CodeGen.isP_INT(exprType)) | |||
exprType = INT; // type1 may be BYTE, ... | |||
arrayDim = 0; | |||
// don't change the value of modifiedExpr. | |||
} | |||
private void booleanExpr(ASTree expr) | |||
throws CompileError | |||
{ | |||
ASTree modExpr = null; | |||
int op = CodeGen.getCompOperator(expr); | |||
if (op == EQ) { // ==, !=, ... | |||
BinExpr bexpr = (BinExpr)expr; | |||
bexpr.oprand1().accept(this); | |||
if (modifiedExpr != null) | |||
bexpr.setOprand1(modifiedExpr); | |||
int type1 = exprType; | |||
int dim1 = arrayDim; | |||
bexpr.oprand2().accept(this); | |||
if (modifiedExpr != null) | |||
bexpr.setOprand2(modifiedExpr); | |||
if (dim1 == 0 && arrayDim == 0) | |||
insertCast(bexpr, type1, exprType); | |||
} | |||
else if (op == '!') { | |||
else if (op == '!') | |||
((Expr)expr).oprand1().accept(this); | |||
if (modifiedExpr != null) | |||
((Expr)expr).setOprand1(modifiedExpr); | |||
} | |||
else if (op == ANDAND || op == OROR) { | |||
BinExpr bexpr = (BinExpr)expr; | |||
bexpr.oprand1().accept(this); | |||
if (modifiedExpr != null) | |||
bexpr.setOprand1(modifiedExpr); | |||
bexpr.oprand2().accept(this); | |||
if (modifiedExpr != null) | |||
bexpr.setOprand2(modifiedExpr); | |||
} | |||
else { // others | |||
else // others | |||
expr.accept(this); | |||
modExpr = modifiedExpr; | |||
} | |||
exprType = BOOLEAN; | |||
arrayDim = 0; | |||
modifiedExpr = modExpr; | |||
} | |||
private void insertCast(BinExpr expr, int type1, int type2) | |||
@@ -487,37 +374,18 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
exprType = type1; | |||
} | |||
private ASTree computeConstExpr(int op, ASTree left, ASTree right) { | |||
if (left instanceof StringL && right instanceof StringL && op == '+') | |||
return new StringL(((StringL)left).get() + ((StringL)right).get()); | |||
else if (left instanceof IntConst) | |||
return ((IntConst)left).compute(op, right); | |||
else if (left instanceof DoubleConst) | |||
return ((DoubleConst)left).compute(op, right); | |||
else | |||
return null; // not constant expression | |||
} | |||
public void atCastExpr(CastExpr expr) throws CompileError { | |||
String cname = resolveClassName(expr.getClassName()); | |||
expr.getOprand().accept(this); | |||
if (modifiedExpr != null) | |||
expr.setOprand(modifiedExpr); | |||
exprType = expr.getType(); | |||
arrayDim = expr.getArrayDim(); | |||
className = cname; | |||
modifiedExpr = null; | |||
} | |||
public void atInstanceOfExpr(InstanceOfExpr expr) throws CompileError { | |||
expr.getOprand().accept(this); | |||
if (modifiedExpr != null) | |||
expr.setOprand(modifiedExpr); | |||
exprType = BOOLEAN; | |||
arrayDim = 0; | |||
modifiedExpr = null; | |||
} | |||
public void atExpr(Expr expr) throws CompileError { | |||
@@ -543,7 +411,7 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
atFieldRead(expr); | |||
} | |||
else if (token == ARRAY) | |||
atArrayRead(expr); | |||
atArrayRead(oprand, expr.oprand2()); | |||
else if (token == PLUSPLUS || token == MINUSMINUS) | |||
atPlusPlus(token, oprand, expr); | |||
else if (token == '!') | |||
@@ -551,40 +419,13 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
else if (token == CALL) // method call | |||
fatal(); | |||
else { | |||
oprand.accept(this); | |||
if (modifiedExpr != null) { | |||
oprand = modifiedExpr; | |||
expr.setOprand1(oprand); | |||
} | |||
modifiedExpr = computeConstExpr(token, oprand); | |||
if (token == '-' || token == '~') { | |||
expr.oprand1().accept(this); | |||
if (token == '-' || token == '~') | |||
if (CodeGen.isP_INT(exprType)) | |||
exprType = INT; // type may be BYTE, ... | |||
} | |||
} | |||
} | |||
private ASTree computeConstExpr(int op, ASTree oprand) { | |||
if (oprand instanceof IntConst) { | |||
IntConst c = (IntConst)oprand; | |||
long v = c.get(); | |||
if (op == '-') | |||
v = -v; | |||
else if (op == '~') | |||
v = ~v; | |||
c.set(v); | |||
} | |||
else if (oprand instanceof DoubleConst) { | |||
DoubleConst c = (DoubleConst)oprand; | |||
if (op == '-') | |||
c.set(-c.get()); | |||
} | |||
return null; | |||
} | |||
public void atCallExpr(CallExpr expr) throws CompileError { | |||
String mname = null; | |||
CtClass targetClass = null; | |||
@@ -614,8 +455,6 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
ASTree target = e.oprand1(); | |||
try { | |||
target.accept(this); | |||
if (modifiedExpr != null) | |||
e.setOprand1(modifiedExpr); | |||
} | |||
catch (NoFieldException nfe) { | |||
if (nfe.getExpr() != target) | |||
@@ -643,7 +482,6 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
MemberResolver.Method minfo | |||
= atMethodCallCore(targetClass, mname, args); | |||
expr.setMethod(minfo); | |||
modifiedExpr = null; | |||
} | |||
private static void badMethod() throws CompileError { | |||
@@ -651,8 +489,6 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
} | |||
/** | |||
* modifiedExpr is not set. | |||
* | |||
* @return a pair of the class declaring the invoked method | |||
* and the MethodInfo of that method. Never null. | |||
*/ | |||
@@ -695,9 +531,6 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
while (args != null) { | |||
ASTree a = args.head(); | |||
a.accept(this); | |||
if (modifiedExpr != null) | |||
args.setHead(modifiedExpr); | |||
types[i] = exprType; | |||
dims[i] = arrayDim; | |||
cnames[i] = className; | |||
@@ -706,8 +539,6 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
} | |||
} | |||
/* modifiedExpr is not set. | |||
*/ | |||
void setReturnType(String desc) throws CompileError { | |||
int i = desc.indexOf(')'); | |||
if (i < 0) | |||
@@ -735,8 +566,11 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
} | |||
} | |||
private CtField atFieldRead(ASTree expr) throws CompileError { | |||
CtField f = fieldAccess(expr); | |||
private void atFieldRead(ASTree expr) throws CompileError { | |||
atFieldRead(fieldAccess(expr)); | |||
} | |||
private void atFieldRead(CtField f) throws CompileError { | |||
FieldInfo finfo = f.getFieldInfo2(); | |||
String type = finfo.getDescriptor(); | |||
@@ -755,9 +589,6 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
className = type.substring(i + 1, type.indexOf(';', i + 1)); | |||
else | |||
className = null; | |||
modifiedExpr = null; ?? | |||
return f; | |||
} | |||
protected CtField fieldAccess(ASTree expr) throws CompileError { | |||
@@ -780,9 +611,6 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
else if (op == '.') | |||
try { | |||
e.oprand1().accept(this); | |||
if (modifiedExpr != null) | |||
e.setOprand1(modifiedExpr); | |||
if (exprType == CLASS && arrayDim == 0) | |||
return resolver.lookupFieldByJvmName(className, | |||
(Symbol)e.oprand2()); | |||
@@ -808,38 +636,25 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
exprType = CLASS; | |||
arrayDim = 0; | |||
className =jvmJavaLangClass; | |||
modifiedExpr = null; | |||
} | |||
public void atArrayLength(Expr expr) throws CompileError { | |||
expr.oprand1().accept(this); | |||
if (modifiedExpr != null) | |||
expr.setOprand1(modifiedExpr); | |||
exprType = INT; | |||
arrayDim = 0; | |||
modifiedExpr = null; | |||
} | |||
public void atArrayRead(Expr expr) throws CompileError { | |||
ASTree array = expr.oprand1(); | |||
public void atArrayRead(ASTree array, ASTree index) | |||
throws CompileError | |||
{ | |||
array.accept(this); | |||
if (modifiedExpr != null) | |||
expr.setOprand1(modifiedExpr); | |||
int type = exprType; | |||
int dim = arrayDim; | |||
String cname = className; | |||
ASTree index = expr.oprand2(); | |||
index.accept(this); | |||
if (modifiedExpr != null) | |||
expr.setOprand2(modifiedExpr); | |||
exprType = type; | |||
arrayDim = dim - 1; | |||
className = cname; | |||
modifiedExpr = null; | |||
} | |||
private void atPlusPlus(int token, ASTree oprand, Expr expr) | |||
@@ -853,13 +668,12 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
Declarator d = ((Variable)oprand).getDeclarator(); | |||
exprType = d.getType(); | |||
arrayDim = d.getArrayDim(); | |||
modifiedExpr = null; | |||
} | |||
else { | |||
if (oprand instanceof Expr) { | |||
Expr e = (Expr)oprand; | |||
if (e.getOperator() == ARRAY) { | |||
atArrayRead(e); | |||
atArrayRead(e.oprand1(), e.oprand2()); | |||
// arrayDim should be 0. | |||
int t = exprType; | |||
if (t == INT || t == BYTE || t == CHAR || t == SHORT) | |||
@@ -875,7 +689,8 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
protected void atFieldPlusPlus(ASTree oprand) throws CompileError | |||
{ | |||
atFieldRead(oprand); | |||
CtField f = fieldAccess(oprand); | |||
atFieldRead(f); | |||
int t = exprType; | |||
if (t == INT || t == BYTE || t == CHAR || t == SHORT) | |||
exprType = INT; |
@@ -18,19 +18,18 @@ Shigeru Chiba | |||
<p><div align="right"><a href="tutorial2.html">Next page</a></div> | |||
<ul>1. <a href="#read">Reading bytecode</a> | |||
<br>2. <a href="#def">Defining a new class</a> | |||
<br>3. <a href="#pool">ClassPool</a> | |||
<br>4. <a href="#load">Class loader</a> | |||
<br>5. <a href="tutorial2.html#intro">Introspection and customization</a> | |||
<br>6. <a href="tutorial3.html#intro">Bytecode level API</a> | |||
<ul>1. <a href="#read">Reading and writing bytecode</a> | |||
<br>2. <a href="#pool">ClassPool</a> | |||
<br>3. <a href="#load">Class loader</a> | |||
<br>4. <a href="tutorial2.html#intro">Introspection and customization</a> | |||
<br>5. <a href="tutorial3.html#intro">Bytecode level API</a> | |||
</ul> | |||
<p><br> | |||
<a name="read"> | |||
<h2>1. Reading bytecode</h2> | |||
<h2>1. Reading and writing bytecode</h2> | |||
<p>Javassist is a class library for dealing with Java bytecode. | |||
Java bytecode is stored in a binary file called a class file. | |||
@@ -91,6 +90,59 @@ modified bytecode. To obtain the bytecode, call <code>toBytecode()</code>: | |||
byte[] b = cc.toBytecode(); | |||
</pre></ul> | |||
<a name="def"> | |||
<h4>Defining a new class</h4> | |||
<p>To define a new class from scratch, <code>makeClass()</code> | |||
must be called on a <code>ClassPool</code>. | |||
<ul><pre> | |||
ClassPool pool = ClassPool.getDefault(); | |||
CtClass cc = pool.makeClass("Point"); | |||
</pre></ul> | |||
<p>This program defines a class <code>Point</code> | |||
including no members. | |||
<h4>Frozen classes</h4> | |||
<p>If a <code>CtClass</code> object is converted into a class file by | |||
<code>writeFile()</code>, <code>toClass()</code>, or | |||
<code>toBytecode()</code>, Javassist freezes that <code>CtClass</code> | |||
object. Further modifications of that <code>CtClass</code> object are | |||
not permitted. | |||
<p>When Javassist freezes a <code>CtClass</code> object, it also | |||
prunes the data structure contained in that object. To reduce memory | |||
consumption, Javassist discards some part of data structure, for | |||
example, the data of method bodies. Thus, after a | |||
<code>CtClass</code> object is pruned, the bytecode of a method is not | |||
accessible although method names and signatures are accessible. | |||
<p>To disallow pruning a <code>CtClass</code>, <code>stopPruning()</code> | |||
must be called in advance: | |||
<ul><pre> | |||
CtClasss cc = ...; | |||
cc.stopPruning(true); | |||
: | |||
cc.writeFile(); // convert to a class file. | |||
// cc is not pruned. | |||
</pre></ul> | |||
<p>If a <code>CtClass</code> is not pruned, it can be defrost so that | |||
modifications of the class definition can be permitted. For example, | |||
<ul><pre> | |||
CtClasss cc = ...; | |||
cc.stopPruning(true); | |||
: | |||
cc.writeFile(); | |||
cc.defrost(); | |||
cc.setSuperclass(...); // OK since the class is not frozen. | |||
</pre></ul> | |||
<h4>Class search path</h4> | |||
<p>The default <code>ClassPool</code> returned | |||
@@ -187,21 +239,106 @@ included in the search path. | |||
<p><br> | |||
<a name="def"> | |||
<h2>2. Defining a new class</h2> | |||
<a name="pool"> | |||
<h2>2. ClassPool</h2> | |||
<p>To define a new class from scratch, <code>makeClass()</code> | |||
must be called on a <code>ClassPool</code>. | |||
<p> | |||
A <code>ClassPool</code> object is a container of <code>CtClass</code> | |||
objects. Once a <code>CtClass</code> object is created, it is | |||
recorded in a <code>ClassPool</code> for ever. This is because a | |||
compiler may need to access the <code>CtClass</code> object later when | |||
it compiles source code that refers to the class represented by that | |||
<code>CtClass</code>. | |||
<p> | |||
For example, suppose that a new method <code>getter()</code> is added | |||
to a <code>CtClass</code> object representing <code>Point</code> | |||
class. Later, the program attempts to compile source code including a | |||
method call to <code>getter()</code> in <code>Point</code> and use the | |||
compiled code as the body of a method, which will be added to another | |||
class <code>Line</code>. If the <code>CtClass</code> object representing | |||
<code>Point</code> is lost, the compiler cannot compile the method call | |||
to <code>getter()</code>. Note that the original class definition does | |||
not include <code>getter()</code>. Therefore, to correctly compile | |||
such a method call, the <code>ClassPool</code> | |||
must contain all the instances of <code>CtClass</code> all the time of | |||
program execution. | |||
<h4>Avoid out of memory</h4> | |||
<p> | |||
This specification of <code>ClassPool</code> may cause huge memory | |||
consumption if the number of <code>CtClass</code> objects becomes | |||
amazingly large (this rarely happens since Javassist tries to reduce | |||
memory consumption in various ways). To avoid this problem, you | |||
can explicitly remove an unnecessary <code>CtClass</code> object from | |||
the <code>ClassPool</code>. If you call <code>detach()</code> on a | |||
<code>CtClass</code> object, then that <code>CtClass</code> object is | |||
removed from the <code>ClassPool</code>. For example, | |||
<ul><pre> | |||
ClassPool pool = ClassPool.getDefault(); | |||
CtClass cc = pool.makeClass("Point"); | |||
CtClass cc = ... ; | |||
cc.writeFile(); | |||
cc.detach(); | |||
</pre></ul> | |||
<p>This program defines a class <code>Point</code> | |||
including no members. | |||
<p>You must not call any method on that | |||
<code>CtClass</code> object after <code>detach()</code> is called. | |||
<p> | |||
Another idea is to occasionally replace a <code>ClassPool</code> with | |||
a new one and discard the old one. If an old <code>ClassPool</code> | |||
is garbage collected, the <code>CtClass</code> objects included in | |||
that <code>ClassPool</code> are also garbage collected. | |||
To create a new instance of <code>ClassPool</code>, execute the following | |||
code snippet: | |||
<p>A new class can be also defined as a copy of an existing class. | |||
<ul><pre> | |||
ClassPool cp = new ClassPool(); | |||
cp.appendSystemPath(); // or append another path by appendClassPath() | |||
</pre></ul> | |||
<p>This creates a <code>ClassPool</code> object that behaves as the | |||
default <code>ClassPool</code> returned by | |||
<code>ClassPool.getDefault()</code> does. | |||
Note that <code>ClassPool.getDefault()</code> is a singleton factory method | |||
provided for convenience. Therefore, the default <code>ClassPool</code> | |||
is never garbage-collected. | |||
<h4>Cascaded ClassPools.</h4> | |||
<p> | |||
<code>ClassPool</code> objects can be cascaded like | |||
<code>java.lang.ClassLoader</code>. For example, | |||
<ul><pre> | |||
ClassPool parent = ClassPool.getDefault(); | |||
ClassPool child = new ClassPool(parent); | |||
child.insertClassPath("./classes"); | |||
</pre></ul> | |||
<p> | |||
If <code>child.get()</code> is called, the child <code>ClassPool</code> | |||
first delegates to the parent <code>ClassPool</code>. If the parent | |||
<code>ClassPool</code> fails to find a class file, then the child | |||
<code>ClassPool</code> attempts to find a class file | |||
under the <code>./classes</code> directory. | |||
<p> | |||
If <code>child.childFirstLookup</code> is true, the child | |||
<code>ClassPool</code> attempts to find a class file before delegating | |||
to the parent <code>ClassPool</code>. For example, | |||
<ul><pre> | |||
ClassPool parent = ClassPool.getDefault(); | |||
ClassPool child = new ClassPool(parent); | |||
child.appendSystemPath(); // the same class path as the default one. | |||
child.childFirstLookup = true; // changes the behavior of the child. | |||
</pre></ul> | |||
<h4>Changing a class name for defining a new class</h4> | |||
<p>A new class can be defined as a copy of an existing class. | |||
The program below does that: | |||
<ul><pre> | |||
@@ -258,26 +395,26 @@ mapping between classes and <code>CtClass</code> objects. Javassist | |||
never allows two distinct <code>CtClass</code> objects to represent | |||
the same class unless two independent <code>ClassPool</code> are created. | |||
This is a significant feature for consistent program | |||
transformation. To create multiple | |||
instances of <code>ClassPool</code>, write the following code: | |||
transformation. | |||
<p>To create another copy of the default instance of | |||
<code>ClassPool</code>, which is returned by | |||
<code>ClassPool.getDefault()</code>, execute the following code | |||
snippet (this code was already shown above): | |||
<ul><pre> | |||
ClassPool cp = new ClassPool(); | |||
cp.appendSystemPath(); // or append another path by appendClassPath() | |||
</pre></ul> | |||
<p>This creates a <code>ClassPool</code> object that behaves as the | |||
default <code>ClassPool</code> returned by | |||
<code>ClassPool.getDefault()</code> does. | |||
<code>ClassPool.getDefault()</code> is a singleton factory method | |||
provided for convenience. | |||
<p>If you have two <code>ClassPool</code> objects, then you can | |||
obtain, from each <code>ClassPool</code>, a distinct | |||
<code>CtClass</code> object representing the same class file. You can | |||
differently modify these <code>CtClass</code> objects to generate | |||
different versions of the class. | |||
<h4>Renaming a frozen class for defining a new class</h4> | |||
<p>Once a <code>CtClass</code> object is converted into a class file | |||
by <code>writeFile()</code> or <code>toBytecode()</code>, Javassist | |||
rejects further modifications of that <code>CtClass</code> object. | |||
@@ -314,67 +451,10 @@ can be executed after <code>writeFile()</code> or <code>toBytecode()</code> | |||
is called on the the <code>CtClass</code> object representing <code>Point</code> | |||
class. | |||
<p><br> | |||
<a name="pool"> | |||
<h2>3. ClassPool</h2> | |||
<p> | |||
A <code>ClassPool</code> object is a container of <code>CtClass</code> | |||
objects. Once a <code>CtClass</code> object is created, it is | |||
recorded in a <code>ClassPool</code> for ever. This is because a | |||
compiler may need to access the <code>CtClass</code> object later when | |||
it compiles source code that refers to the class represented by that | |||
<code>CtClass</code>. If the class definition represented by that | |||
<code>CtClass</code> object is different from that of the original class | |||
file, the compiler cannot correctly compile the source code without | |||
the <code>CtClass</code> object. | |||
<p> | |||
This specification of <code>ClassPool</code> may cause huge memory | |||
consumption if the number of <code>CtClass</code> objects becomes large. | |||
To avoid this problem, you can explicitly remove an unnecessary | |||
<code>CtClass</code> object from the <code>ClassPool</code>. If you | |||
call <code>detach()</code> on a <code>CtClass</code> object, then that | |||
<code>CtClass</code> object is removed from the <code>ClassPool</code>. | |||
For example, | |||
<ul><pre> | |||
CtClass cc = ... ; | |||
cc.writeFile(); | |||
cc.detach(); | |||
</pre></ul> | |||
<p>You must not call any method on that | |||
<code>CtClass</code> object after <code>detach()</code> is called. | |||
<p><code>ClassPool</code> objects can be cascaded like | |||
<code>java.lang.ClassLoader</code>. For example, | |||
<ul><pre> | |||
ClassPool parent = ClassPool.getDefault(); | |||
ClassPool child = new ClassPool(parent); | |||
</pre></ul> | |||
<p>If <code>child.get()</code> is called, the child <code>ClassPool</code> | |||
first delegates to the parent <code>ClassPool</code>. If the parent | |||
<code>ClassPool</code> fails to find a class file, then the child | |||
<code>ClassPool</code> attempts to find a class file. | |||
If <code>child.childFirstLookup</code> is true, the child | |||
<code>ClassPool</code> attempts to find a class file before delegating | |||
to the parent <code>ClassPool</code>. For example, | |||
<ul><pre> | |||
ClassPool parent = ClassPool.getDefault(); | |||
ClassPool child = new ClassPool(parent); | |||
child.childFirstLookup = true; // changes the behavior of the child. | |||
</pre></ul> | |||
<p><br> | |||
<a name="load"> | |||
<h2>4. Class loader</h2> | |||
<h2>3. Class loader</h2> | |||
<p>If what classes must be modified is known in advance, | |||
the easiest way for modifying the classes is as follows: | |||
@@ -396,7 +476,7 @@ by Javassist. | |||
<p><br> | |||
<h3>4.1 The <code>toClass</code> method in <code>CtClass</code></h3> | |||
<h3>3.1 The <code>toClass</code> method in <code>CtClass</code></h3> | |||
<p>The <code>CtClass</code> provides a convenience method | |||
<code>toClass()</code>, which requests the context class loader for | |||
@@ -475,7 +555,7 @@ more complex functionality, you should write your own class loader. | |||
<p><br> | |||
<h3>4.2 Class loading in Java</h3> | |||
<h3>3.2 Class loading in Java</h3> | |||
<p>In Java, multiple class loaders can coexist and | |||
each class loader creates its own name space. | |||
@@ -655,7 +735,7 @@ be helpful: | |||
<p><br> | |||
<h3>4.3 Using <code>javassist.Loader</code></h3> | |||
<h3>3.3 Using <code>javassist.Loader</code></h3> | |||
<p>Javassist provides a class loader | |||
<code>javassist.Loader</code>. This class loader uses a | |||
@@ -792,7 +872,7 @@ make sure whether all the classes using that class have been loaded by | |||
<p><br> | |||
<h3>4.4 Writing a class loader</h3> | |||
<h3>3.4 Writing a class loader</h3> | |||
<p>A simple class loader using Javassist is as follows: | |||
@@ -863,7 +943,7 @@ Hence, the | |||
<p><br> | |||
<h3>4.5 Modifying a system class</h3> | |||
<h3>3.5 Modifying a system class</h3> | |||
<p>The system classes like <code>java.lang.String</code> cannot be | |||
loaded by a class loader other than the system class loader. |
@@ -13,7 +13,7 @@ | |||
<div align="right"><a href="tutorial3.html">Next page</a></div> | |||
<p> | |||
<a href="#intro">5. Introspection and customization</a> | |||
<a href="#intro">4. Introspection and customization</a> | |||
<ul> | |||
<li><a href="#before">Inserting source text at the beginning/end of a method body</a> | |||
<br><li><a href="#alter">Altering a method body</a> | |||
@@ -25,7 +25,7 @@ | |||
<p><br> | |||
<a name="intro"> | |||
<h2>5. Introspection and customization</h2> | |||
<h2>4. Introspection and customization</h2> | |||
<p><code>CtClass</code> provides methods for introspection. The | |||
introspective ability of Javassist is compatible with that of | |||
@@ -115,7 +115,7 @@ of the <code>javassist.runtime</code> package. | |||
<p><br> | |||
<a name="before"> | |||
<h3>5.1 Inserting source text at the beginning/end of a method body</h3> | |||
<h3>4.1 Inserting source text at the beginning/end of a method body</h3> | |||
<p><code>CtMethod</code> and <code>CtConstructor</code> provide | |||
methods <code>insertBefore()</code>, <code>insertAfter()</code>, and | |||
@@ -553,7 +553,7 @@ catch (java.io.IOException e) { | |||
<p><br> | |||
<a name="alter"> | |||
<h3>5.2 Altering a method body</h3> | |||
<h3>4.2 Altering a method body</h3> | |||
<p><code>CtMethod</code> and <code>CtConstructor</code> provide | |||
<code>setBody()</code> for substituting a whole | |||
@@ -1238,7 +1238,7 @@ exception. | |||
<p><br> | |||
<a name="add"> | |||
<h3>5.3 Adding a new method or field</h3> | |||
<h3>4.3 Adding a new method or field</h3> | |||
<h4>Adding a method</h4> | |||
@@ -1362,7 +1362,7 @@ does not end with a semi colon (<code>;</code>). | |||
<p><br> | |||
<a name="runtime"> | |||
<h3>5.4 Runtime support classes</h3> | |||
<h3>4.4 Runtime support classes</h3> | |||
<p>In most cases, a class modified by Javassist does not require | |||
Javassist to run. However, some kinds of bytecode generated by the | |||
@@ -1376,7 +1376,7 @@ Javassist classes are never used at runtime of the modified classes. | |||
<p><br> | |||
<a name="limit"> | |||
<h3>5.5 Limitations</h3> | |||
<h3>4.5 Limitations</h3> | |||
<p>In the current implementation, the Java compiler included in Javassist | |||
has several limitations with respect to the language that the compiler can | |||
@@ -1389,13 +1389,6 @@ declarations. However, the <code>java.lang</code> package is an | |||
exception; for example, the compiler accepts <code>Object</code> as | |||
well as <code>java.lang.Object</code>. | |||
<p><li>The <code>.class</code> notation is not supported. Use the | |||
method <code>Class.forName()</code>. | |||
In regular | |||
Java, an expression <code>Point.class</code> means a <code>Class</code> | |||
object representing the <code>Point</code> class. This notation is | |||
not available. | |||
<p><li>Array initializers, a comma-separated list of expressions | |||
enclosed by braces <code>{</code> and <code>}</code>, are not | |||
supported. |
@@ -12,7 +12,7 @@ | |||
<div align="left"><a href="tutorial2.html">Previous page</a></div> | |||
<p> | |||
<a href="#intro">6. Bytecode level API</a> | |||
<a href="#intro">5. Bytecode level API</a> | |||
<ul> | |||
<li><a href="#classfile">Obtaining a <code>ClassFile</code> object</a> | |||
<br><li><a href="#member">Adding and removing a member</a> | |||
@@ -25,7 +25,7 @@ | |||
<p><br> | |||
<a name="intro"> | |||
<h2>6. Bytecode level API</h2> | |||
<h2>5. Bytecode level API</h2> | |||
<p> | |||
Javassist also provides lower-level API for directly editing | |||
@@ -35,7 +35,7 @@ while this level of API allows you any kind of modification | |||
of class files. | |||
<a name="classfile"> | |||
<h3>6.1 Obtaining a <code>ClassFile</code> object</h3> | |||
<h3>5.1 Obtaining a <code>ClassFile</code> object</h3> | |||
<p>A <code>javassist.bytecode.ClassFile</code> object represents | |||
a class file. To obtian this object, <code>getClassFile()</code> | |||
@@ -64,7 +64,7 @@ writes the contents of the class file to a given | |||
<p><br> | |||
<a name="member"> | |||
<h3>6.2 Adding and removing a member</h3> | |||
<h3>5.2 Adding and removing a member</h3> | |||
<p> | |||
<code>ClassFile</code> provides <code>addField()</code> and | |||
@@ -98,7 +98,7 @@ and remove one from the list. | |||
<p><br> | |||
<a name="traverse"> | |||
<h3>6.3 Traversing a method body</h3> | |||
<h3>5.3 Traversing a method body</h3> | |||
<p> | |||
To examine every bytecode instruction in a method body, | |||
@@ -143,7 +143,7 @@ Branch offsets etc. are automatically adjusted.<br> | |||
<p><br> | |||
<a name="bytecode"> | |||
<h3>6.4 Producing a bytecode sequence</h3> | |||
<h3>5.4 Producing a bytecode sequence</h3> | |||
<p> | |||
A <code>Bytecode</code> object represents a sequence of bytecode |