diff options
Diffstat (limited to 'src/main/javassist/compiler/JvstCodeGen.java')
-rw-r--r-- | src/main/javassist/compiler/JvstCodeGen.java | 651 |
1 files changed, 651 insertions, 0 deletions
diff --git a/src/main/javassist/compiler/JvstCodeGen.java b/src/main/javassist/compiler/JvstCodeGen.java new file mode 100644 index 00000000..6a365e16 --- /dev/null +++ b/src/main/javassist/compiler/JvstCodeGen.java @@ -0,0 +1,651 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler; + +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.ast.*; + +/* Code generator methods for extensions by Javassist. + */ +public class JvstCodeGen extends MemberCodeGen { + private String paramArrayName = null; + private String paramListName = null; + private CtClass[] paramTypeList = null; + private int paramVarBase = 0; // variable index for $0 or $1. + private boolean useParam0 = false; // true if $0 is used. + private String param0Type = null; // JVM name + private static final String sigName = "$sig"; + private static final String dollarTypeName = "$type"; + private static final String clazzName = "$class"; + private CtClass dollarType = null; + private CtClass returnType = null; + private String returnCastName = null; + private String returnVarName = null; // null if $_ is not used. + private static final String wrapperCastName = "$w"; + private String proceedName = null; + private static final String cflowName = "$cflow"; + private ProceedHandler procHandler = null; // null if not used. + + public JvstCodeGen(Bytecode b, CtClass cc, ClassPool cp) { + super(b, cc, cp); + } + + /* Index of $1. + */ + private int indexOfParam1() { + return paramVarBase + (useParam0 ? 1 : 0); + } + + /* Records a ProceedHandler obejct. + * + * @param name the name of the special method call. + * it is usually $proceed. + */ + public void setProceedHandler(ProceedHandler h, String name) { + proceedName = name; + procHandler = h; + } + + /* If the type of the expression compiled last is void, + * add ACONST_NULL and change exprType, arrayDim, className. + */ + public void addNullIfVoid() { + if (exprType == VOID) { + bytecode.addOpcode(ACONST_NULL); + exprType = CLASS; + arrayDim = 0; + className = jvmJavaLangObject; + } + } + + /* To support $args, $sig, and $type. + * $args is an array of parameter list. + */ + public void atMember(Member mem) throws CompileError { + String name = mem.get(); + if (name.equals(paramArrayName)) { + compileParameterList(bytecode, paramTypeList, indexOfParam1()); + exprType = CLASS; + arrayDim = 1; + className = jvmJavaLangObject; + } + else if (name.equals(sigName)) { + bytecode.addLdc(Descriptor.ofMethod(returnType, paramTypeList)); + bytecode.addInvokestatic("javassist/runtime/Desc", "getParams", + "(Ljava/lang/String;)[Ljava/lang/Class;"); + exprType = CLASS; + arrayDim = 1; + className = "java/lang/Class"; + } + else if (name.equals(dollarTypeName)) { + if (dollarType == null) + throw new CompileError(dollarType + " is not available"); + + bytecode.addLdc(Descriptor.of(dollarType)); + callGetType("getType"); + } + else if (name.equals(clazzName)) { + if (param0Type == null) + throw new CompileError(clazzName + " is not available"); + + bytecode.addLdc(param0Type); + callGetType("getClazz"); + } + else + super.atMember(mem); + } + + private void callGetType(String method) { + bytecode.addInvokestatic("javassist/runtime/Desc", method, + "(Ljava/lang/String;)Ljava/lang/Class;"); + exprType = CLASS; + arrayDim = 0; + className = "java/lang/Class"; + } + + private void atSigOrType(String sig) throws CompileError { + } + + protected void atFieldAssign(Expr expr, int op, ASTree left, + ASTree right, boolean doDup) throws CompileError + { + if (left instanceof Member + && ((Member)left).get().equals(paramArrayName)) { + if (op != '=') + throw new CompileError("bad operator for " + paramArrayName); + + right.accept(this); + if (arrayDim != 1 || exprType != CLASS) + throw new CompileError("invalid type for " + paramArrayName); + + atAssignParamList(paramTypeList, bytecode); + if (!doDup) + bytecode.addOpcode(POP); + } + else + super.atFieldAssign(expr, op, left, right, doDup); + } + + protected void atAssignParamList(CtClass[] params, Bytecode code) + throws CompileError + { + if (params == null) + return; + + int varNo = indexOfParam1(); + int n = params.length; + for (int i = 0; i < n; ++i) { + code.addOpcode(DUP); + code.addIconst(i); + code.addOpcode(AALOAD); + compileUnwrapValue(params[i], code); + code.addStore(varNo, params[i]); + varNo += is2word(exprType, arrayDim) ? 2 : 1; + } + } + + public void atCastExpr(CastExpr expr) throws CompileError { + ASTList classname = expr.getClassName(); + if (classname != null && expr.getArrayDim() == 0) { + ASTree p = classname.head(); + if (p instanceof Symbol && classname.tail() == null) { + String typename = ((Symbol)p).get(); + if (typename.equals(returnCastName)) { + atCastToRtype(expr); + return; + } + else if (typename.equals(wrapperCastName)) { + atCastToWrapper(expr); + return; + } + } + } + + super.atCastExpr(expr); + } + + /** + * Inserts a cast operator to the return type. + * If the return type is void, this does nothing. + */ + protected void atCastToRtype(CastExpr expr) throws CompileError { + expr.getOprand().accept(this); + if (!isRefType(exprType) || arrayDim > 0) + throw new CompileError("invalid type for " + returnCastName); + + compileUnwrapValue(returnType, bytecode); + } + + protected void atCastToWrapper(CastExpr expr) throws CompileError { + expr.getOprand().accept(this); + if (isRefType(exprType) || arrayDim > 0) + return; // Object type. do nothing. + + CtClass clazz = lookupClass(exprType, arrayDim, className); + if (clazz instanceof CtPrimitiveType) { + CtPrimitiveType pt = (CtPrimitiveType)clazz; + String wrapper = pt.getWrapperName(); + bytecode.addNew(wrapper); // new <wrapper> + bytecode.addOpcode(DUP); // dup + if (pt.getDataSize() > 1) + bytecode.addOpcode(DUP2_X2); // dup2_x2 + else + bytecode.addOpcode(DUP2_X1); // dup2_x1 + + bytecode.addOpcode(POP2); // pop2 + bytecode.addInvokespecial(wrapper, "<init>", + "(" + pt.getDescriptor() + ")V"); + // invokespecial + exprType = CLASS; + arrayDim = 0; + className = jvmJavaLangObject; + } + } + + /* Delegates to a ProcHandler object if the method call is + * $proceed(). It may process $cflow(). + */ + protected void atMethodCall(Expr expr) throws CompileError { + ASTree method = expr.oprand1(); + if (method instanceof Member) { + String name = ((Member)method).get(); + if (procHandler != null && name.equals(proceedName)) { + procHandler.doit(this, bytecode, (ASTList)expr.oprand2()); + return; + } + else if (name.equals(cflowName)) { + atCflow((ASTList)expr.oprand2()); + return; + } + } + + super.atMethodCall(expr); + } + + /* To support $cflow(). + */ + protected void atCflow(ASTList cname) throws CompileError { + StringBuffer sbuf = new StringBuffer(); + if (cname == null || cname.tail() != null) + throw new CompileError("bad " + cflowName); + + makeCflowName(sbuf, cname.head()); + String name = sbuf.toString(); + Object[] names = classPool.lookupCflow(name); + if (names == null) + throw new CompileError("no such a " + cflowName + ": " + name); + + bytecode.addGetstatic((String)names[0], (String)names[1], + "Ljavassist/runtime/Cflow;"); + bytecode.addInvokevirtual("javassist.runtime.Cflow", + "value", "()I"); + exprType = INT; + arrayDim = 0; + className = null; + } + + /* Syntax: + * + * <cflow> : $cflow '(' <cflow name> ')' + * <cflow name> : <identifier> ('.' <identifier>)* + */ + private static void makeCflowName(StringBuffer sbuf, ASTree name) + throws CompileError + { + if (name instanceof Symbol) { + sbuf.append(((Symbol)name).get()); + return; + } + else if (name instanceof Expr) { + Expr expr = (Expr)name; + if (expr.getOperator() == '.') { + makeCflowName(sbuf, expr.oprand1()); + sbuf.append('.'); + makeCflowName(sbuf, expr.oprand2()); + return; + } + } + + throw new CompileError("bad " + cflowName); + } + + /* To support $$. ($$) is equivalent to ($1, ..., $n). + * It can be used only as a parameter list of method call. + */ + public boolean isParamListName(ASTList args) { + if (paramTypeList != null + && args != null && args.tail() == null) { + ASTree left = args.head(); + return (left instanceof Member + && ((Member)left).get().equals(paramListName)); + } + else + return false; + } + + /* + public int atMethodArgsLength(ASTList args) { + if (!isParamListName(args)) + return super.atMethodArgsLength(args); + + return paramTypeList.length; + } + */ + + public int atMethodArgsLength(ASTList args) { + String pname = paramListName; + int n = 0; + while (args != null) { + ASTree a = args.head(); + if (a instanceof Member && ((Member)a).get().equals(pname)) { + if (paramTypeList != null) + n += paramTypeList.length; + } + else + ++n; + + args = args.tail(); + } + + return n; + } + + public void atMethodArgs(ASTList args, int[] types, int[] dims, + String[] cnames) throws CompileError { + CtClass[] params = paramTypeList; + String pname = paramListName; + int i = 0; + while (args != null) { + ASTree a = args.head(); + if (a instanceof Member && ((Member)a).get().equals(pname)) { + if (params != null) { + int n = params.length; + int regno = indexOfParam1(); + for (int k = 0; k < n; ++k) { + CtClass p = params[k]; + regno += bytecode.addLoad(regno, p); + setType(p); + types[i] = exprType; + dims[i] = arrayDim; + cnames[i] = className; + ++i; + } + } + } + else { + a.accept(this); + types[i] = exprType; + dims[i] = arrayDim; + cnames[i] = className; + ++i; + } + + args = args.tail(); + } + } + + /* + public void atMethodArgs(ASTList args, int[] types, int[] dims, + String[] cnames) throws CompileError { + if (!isParamListName(args)) { + super.atMethodArgs(args, types, dims, cnames); + return; + } + + CtClass[] params = paramTypeList; + if (params == null) + return; + + int n = params.length; + int regno = indexOfParam1(); + for (int i = 0; i < n; ++i) { + CtClass p = params[i]; + regno += bytecode.addLoad(regno, p); + setType(p); + types[i] = exprType; + dims[i] = arrayDim; + cnames[i] = className; + } + } + */ + + /* + * Makes it valid to write "return <expr>;" for a void method. + */ + protected void atReturnStmnt(Stmnt st) throws CompileError { + ASTree result = st.getLeft(); + if (result != null && returnType == CtClass.voidType) { + result.accept(this); + if (is2word(exprType, arrayDim)) + bytecode.addOpcode(POP2); + else if (exprType != VOID) + bytecode.addOpcode(POP); + + result = null; + } + + atReturnStmnt2(result); + } + + /** + * Makes a cast to the return type ($r) available. + * It also enables $_. + * + * <p>If the return type is void, ($r) does nothing. + * The type of $_ is java.lang.Object. + * + * @param resultName null if $_ is not used. + * @return -1 or the variable index assigned to $_. + */ + public int recordReturnType(CtClass type, String castName, + String resultName, SymbolTable tbl) throws CompileError + { + returnType = type; + returnCastName = castName; + returnVarName = resultName; + if (resultName == null) + return -1; + else { + int varNo = getMaxLocals(); + int locals = varNo + recordVar(type, resultName, varNo, tbl); + setMaxLocals(locals); + return varNo; + } + } + + /** + * Makes $type available. + */ + public void recordType(CtClass t) { + dollarType = t; + } + + /** + * Makes method parameters $0, $1, ..., $args, and $$ available. + * $0 is equivalent to THIS if the method is not static. Otherwise, + * if the method is static, then $0 is not available. + */ + public void recordParams(CtClass[] params, boolean isStatic, + String prefix, String paramVarName, + String paramsName, SymbolTable tbl) + throws CompileError + { + recordParams(params, isStatic, prefix, paramVarName, + paramsName, !isStatic, 0, getThisName(), tbl); + } + + /** + * Makes method parameters $0, $1, ..., $args, and $$ available. + * $0 is available only if use0 is true. It might not be equivalent + * to THIS. + * + * @paaram use0 true if $0 is used. + * @param paramBase the register number of $0 (use0 is true) + * or $1 (otherwise). + * @param target the class of $0. If use0 is false, target + * can be null. + * @param isStatic true if the method in which the compiled bytecode + * is embedded is static. + */ + public void recordParams(CtClass[] params, boolean isStatic, + String prefix, String paramVarName, + String paramsName, boolean use0, + int paramBase, String target, + SymbolTable tbl) + throws CompileError + { + int varNo; + + paramTypeList = params; + paramArrayName = paramVarName; + paramListName = paramsName; + paramVarBase = paramBase; + useParam0 = use0; + + param0Type = jvmToJavaName(target); + + inStaticMethod = isStatic; + varNo = paramBase; + if (use0) { + String varName = prefix + "0"; + Declarator decl + = new Declarator(CLASS, javaToJvmName(target), 0, varNo++, + new Symbol(varName)); + tbl.append(varName, decl); + } + + for (int i = 0; i < params.length; ++i) + varNo += recordVar(params[i], prefix + (i + 1), varNo, tbl); + + if (getMaxLocals() < varNo) + setMaxLocals(varNo); + } + + /** + * Makes the given variable name available. + * + * @param type variable type + * @param varName variable name + */ + public int recordVariable(CtClass type, String varName, SymbolTable tbl) + throws CompileError + { + if (varName == null) + return -1; + else { + int varNo = getMaxLocals(); + int locals = varNo + recordVar(type, varName, varNo, tbl); + setMaxLocals(locals); + return varNo; + } + } + + private int recordVar(CtClass cc, String varName, int varNo, + SymbolTable tbl) throws CompileError + { + if (cc == CtClass.voidType) { + exprType = CLASS; + arrayDim = 0; + className = jvmJavaLangObject; + } + else + setType(cc); + + Declarator decl + = new Declarator(exprType, className, arrayDim, + varNo, new Symbol(varName)); + tbl.append(varName, decl); + return is2word(exprType, arrayDim) ? 2 : 1; + } + + /* compileParameterList() returns the stack size used + * by the produced code. + * + * This method correctly computes the max_stack value. + * + * @param regno the index of the local variable in which + * the first argument is received. + * (0: static method, 1: regular method.) + */ + public static int compileParameterList(Bytecode code, + CtClass[] params, int regno) { + if (params == null) { + code.addIconst(0); // iconst_0 + code.addAnewarray(javaLangObject); // anewarray Object + return 1; + } + else { + CtClass[] args = new CtClass[1]; + int n = params.length; + code.addIconst(n); // iconst_<n> + code.addAnewarray(javaLangObject); // anewarray Object + for (int i = 0; i < n; ++i) { + code.addOpcode(Bytecode.DUP); // dup + code.addIconst(i); // iconst_<i> + if (params[i].isPrimitive()) { + CtPrimitiveType pt = (CtPrimitiveType)params[i]; + String wrapper = pt.getWrapperName(); + code.addNew(wrapper); // new <wrapper> + code.addOpcode(Bytecode.DUP); // dup + int s = code.addLoad(regno, pt); // ?load <regno> + regno += s; + args[0] = pt; + code.addInvokespecial(wrapper, "<init>", + Descriptor.ofMethod(CtClass.voidType, args)); + // invokespecial + } + else { + code.addAload(regno); // aload <regno> + ++regno; + } + + code.addOpcode(Bytecode.AASTORE); // aastore + } + + return 8; + } + } + + protected void compileUnwrapValue(CtClass type, Bytecode code) + throws CompileError + { + if (type instanceof CtPrimitiveType) { + CtPrimitiveType pt = (CtPrimitiveType)type; + if (pt != CtClass.voidType) { + String wrapper = pt.getWrapperName(); + code.addCheckcast(wrapper); + code.addInvokevirtual(wrapper, pt.getGetMethodName(), + pt.getGetMethodDescriptor()); + setType(type); + } + } + else { + code.addCheckcast(type); + setType(type); + } + } + + /* Sets exprType, arrayDim, and className; + * If type is void, then this method does nothing. + */ + public void setType(CtClass type) throws CompileError { + setType(type, 0); + } + + private void setType(CtClass type, int dim) throws CompileError { + if (type.isPrimitive()) { + CtPrimitiveType pt = (CtPrimitiveType)type; + exprType = descToType(pt.getDescriptor()); + arrayDim = dim; + className = null; + } + else if (type.isArray()) + try { + setType(type.getComponentType(), dim + 1); + } + catch (NotFoundException e) { + throw new CompileError("undefined type: " + type.getName()); + } + else { + exprType = CLASS; + arrayDim = dim; + className = javaToJvmName(type.getName()); + } + } + + /* Performs implicit coercion from exprType to type. + */ + public void doNumCast(CtClass type) throws CompileError { + if (arrayDim == 0 && !isRefType(exprType)) + if (type instanceof CtPrimitiveType) { + CtPrimitiveType pt = (CtPrimitiveType)type; + atNumCastExpr(exprType, descToType(pt.getDescriptor())); + } + else + throw new CompileError("type mismatch"); + } +} |