/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 1999-2003 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.
*
* 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 javassist;
import javassist.bytecode.*;
import javassist.compiler.Javac;
import javassist.compiler.CompileError;
import javassist.expr.ExprEditor;
/**
* CtBehavior
is the abstract super class of
* CtMethod
and CtConstructor
.
*/
public abstract class CtBehavior extends CtMember {
protected MethodInfo methodInfo;
protected CtBehavior(CtClass clazz, MethodInfo minfo) {
super(clazz);
methodInfo = minfo;
}
/**
* Returns the MethodInfo representing this member in the
* class file.
*/
public MethodInfo getMethodInfo() {
declaringClass.checkModify();
return methodInfo;
}
/**
* Undocumented method. Do not use; internal-use only.
*/
public MethodInfo getMethodInfo2() { return methodInfo; }
/**
* Obtains the modifiers of the member.
*
* @return modifiers encoded with
* javassist.Modifier
.
* @see Modifier
*/
public int getModifiers() {
return AccessFlag.toModifier(methodInfo.getAccessFlags());
}
/**
* Sets the encoded modifiers of the member.
*
* @see Modifier
*/
public void setModifiers(int mod) {
declaringClass.checkModify();
methodInfo.setAccessFlags(AccessFlag.of(mod));
}
/**
* Obtains the name of this member.
*
* @see CtConstructor#getName()
*/
public abstract String getName();
/**
* Obtains parameter types of this member.
*/
public CtClass[] getParameterTypes() throws NotFoundException {
return Descriptor.getParameterTypes(methodInfo.getDescriptor(),
declaringClass.getClassPool());
}
/**
* Obtains the type of the returned value.
*/
CtClass getReturnType0() throws NotFoundException {
return Descriptor.getReturnType(methodInfo.getDescriptor(),
declaringClass.getClassPool());
}
/**
* Returns the character string representing the parameter types
* and the return type. If two members have the same parameter types
* and the return type, getSignature()
returns the
* same string.
*/
public String getSignature() {
return methodInfo.getDescriptor();
}
/**
* Obtains exceptions that this member may throw.
*/
public CtClass[] getExceptionTypes() throws NotFoundException {
String[] exceptions;
ExceptionsAttribute ea = methodInfo.getExceptionsAttribute();
if (ea == null)
exceptions = null;
else
exceptions = ea.getExceptions();
return declaringClass.getClassPool().get(exceptions);
}
/**
* Sets exceptions that this member may throw.
*/
public void setExceptionTypes(CtClass[] types) throws NotFoundException {
declaringClass.checkModify();
if (types == null) {
methodInfo.removeExceptionsAttribute();
return;
}
String[] names = new String[types.length];
for (int i = 0; i < types.length; ++i)
names[i] = types[i].getName();
ExceptionsAttribute ea = methodInfo.getExceptionsAttribute();
if (ea == null) {
ea = new ExceptionsAttribute(methodInfo.getConstPool());
methodInfo.setExceptionsAttribute(ea);
}
ea.setExceptions(names);
}
/**
* Returns true if the body is empty.
*/
public abstract boolean isEmpty();
/**
* Sets a member body.
*
* @param src the source code representing the member body.
* It must be a single statement or block.
* If it is null
, the substituted member
* body does nothing except returning zero or null.
*/
public void setBody(String src) throws CannotCompileException {
declaringClass.checkModify();
try {
Javac jv = new Javac(declaringClass);
Bytecode b = jv.compileBody(this, src);
methodInfo.setCodeAttribute(b.toCodeAttribute());
methodInfo.setAccessFlags(methodInfo.getAccessFlags()
& ~AccessFlag.ABSTRACT);
}
catch (CompileError e) {
throw new CannotCompileException(e);
}
}
static void setBody0(CtClass srcClass, MethodInfo srcInfo,
CtClass destClass, MethodInfo destInfo,
ClassMap map)
throws CannotCompileException
{
destClass.checkModify();
if (map == null)
map = new ClassMap();
map.put(srcClass.getName(), destClass.getName());
try {
CodeAttribute cattr = srcInfo.getCodeAttribute();
if (cattr != null) {
ConstPool cp = destInfo.getConstPool();
CodeAttribute ca = (CodeAttribute)cattr.copy(cp, map);
destInfo.setCodeAttribute(ca);
}
}
catch (CodeAttribute.RuntimeCopyException e) {
/* the exception may be thrown by copy() in CodeAttribute.
*/
throw new CannotCompileException(e);
}
destInfo.setAccessFlags(destInfo.getAccessFlags()
& ~AccessFlag.ABSTRACT);
}
/**
* Obtains an attribute with the given name.
* If that attribute is not found in the class file, this
* method returns null.
*
* @param name attribute name
*/
public byte[] getAttribute(String name) {
AttributeInfo ai = methodInfo.getAttribute(name);
if (ai == null)
return null;
else
return ai.get();
}
/**
* Adds an attribute. The attribute is saved in the class file.
*
* @param name attribute name
* @param data attribute value
*/
public void setAttribute(String name, byte[] data) {
declaringClass.checkModify();
methodInfo.addAttribute(new AttributeInfo(methodInfo.getConstPool(),
name, data));
}
/**
* Declares to use $cflow
for this member;
* If $cflow
is used, the class files modified
* with Javassist requires a support class
* javassist.runtime.Cflow
at runtime
* (other Javassist classes are not required at runtime).
*
*
Every $cflow
variable is given a unique name.
* For example, if the given name is "Point.paint"
,
* then the variable is indicated by $cflow(Point.paint)
.
*
* @param name $cflow
name. It can include
* alphabets, numbers, _
,
* $
, and .
(dot).
*
* @see javassist.runtime.Cflow
*/
public void useCflow(String name) throws CannotCompileException {
CtClass cc = declaringClass;
cc.checkModify();
ClassPool pool = cc.getClassPool();
String fname;
int i = 0;
while (true) {
fname = "_cflow$" + i++;
try {
cc.getDeclaredField(fname);
}
catch(NotFoundException e) {
break;
}
}
pool.recordCflow(name, declaringClass.getName(), fname);
try {
CtClass type = pool.get("javassist.runtime.Cflow");
CtField field = new CtField(type, fname, cc);
field.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
cc.addField(field, CtField.Initializer.byNew(type));
insertBefore(fname + ".enter();");
String src = fname + ".exit();";
insertAfter(src, true);
}
catch (NotFoundException e) {
throw new CannotCompileException(e);
}
}
/**
* Modifies the member body.
*
* @param converter specifies how to modify.
*/
public void instrument(CodeConverter converter)
throws CannotCompileException
{
declaringClass.checkModify();
ConstPool cp = methodInfo.getConstPool();
converter.doit(getDeclaringClass(), methodInfo, cp);
}
/**
* Modifies the member body.
*
* @param editor specifies how to modify.
*/
public void instrument(ExprEditor editor)
throws CannotCompileException
{
// if the class is not frozen,
// does not trun the modified flag on.
if (declaringClass.isFrozen())
declaringClass.checkModify();
if (editor.doit(declaringClass, methodInfo))
declaringClass.checkModify();
}
/**
* Inserts bytecode at the beginning of the body.
*
* @param src the source code representing the inserted bytecode.
* It must be a single statement or block.
*/
public void insertBefore(String src) throws CannotCompileException {
declaringClass.checkModify();
CodeAttribute ca = methodInfo.getCodeAttribute();
if (ca == null)
throw new CannotCompileException("no method body");
CodeIterator iterator = ca.iterator();
Javac jv = new Javac(declaringClass);
try {
jv.recordParams(getParameterTypes(),
Modifier.isStatic(getModifiers()));
jv.compileStmnt(src);
Bytecode b = jv.getBytecode();
int stack = b.getMaxStack();
int locals = b.getMaxLocals();
if (stack > ca.getMaxStack())
ca.setMaxStack(stack);
if (locals > ca.getMaxLocals())
ca.setMaxLocals(locals);
int pos = iterator.insertEx(b.get());
iterator.insert(b.getExceptionTable(), pos);
}
catch (NotFoundException e) {
throw new CannotCompileException(e);
}
catch (CompileError e) {
throw new CannotCompileException(e);
}
catch (BadBytecode e) {
throw new CannotCompileException(e);
}
}
/**
* Inserts bytecode at the end of the body.
* The bytecode is inserted just before every return insturction.
* It is not executed when an exception is thrown.
*
* @param src the source code representing the inserted bytecode.
* It must be a single statement or block.
*/
public void insertAfter(String src)
throws CannotCompileException
{
insertAfter(src, false);
}
/**
* Inserts bytecode at the end of the body.
* The bytecode is inserted just before every return insturction.
*
* @param src the source code representing the inserted bytecode.
* It must be a single statement or block.
* @param asFinally true if the inserted bytecode is executed
* not only when the control normally returns
* but also when an exception is thrown.
*/
public void insertAfter(String src, boolean asFinally)
throws CannotCompileException
{
declaringClass.checkModify();
CodeAttribute ca = methodInfo.getCodeAttribute();
if (ca == null)
throw new CannotCompileException("no method body");
CodeIterator iterator = ca.iterator();
int retAddr = ca.getMaxLocals();
Bytecode b = new Bytecode(methodInfo.getConstPool(), 0, retAddr + 1);
b.setStackDepth(ca.getMaxStack() + 1);
Javac jv = new Javac(b, declaringClass);
try {
jv.recordParams(getParameterTypes(),
Modifier.isStatic(getModifiers()));
CtClass rtype = getReturnType0();
int varNo = jv.recordReturnType(rtype, true);
boolean isVoid = rtype == CtClass.voidType;
int handlerLen = insertAfterHandler(asFinally, b, rtype);
b.addAstore(retAddr);
if (isVoid) {
b.addOpcode(Opcode.ACONST_NULL);
b.addAstore(varNo);
jv.compileStmnt(src);
}
else {
b.addStore(varNo, rtype);
jv.compileStmnt(src);
b.addLoad(varNo, rtype);
}
b.addRet(retAddr);
ca.setMaxStack(b.getMaxStack());
ca.setMaxLocals(b.getMaxLocals());
int gapPos = iterator.append(b.get());
iterator.append(b.getExceptionTable(), gapPos);
if (asFinally)
ca.getExceptionTable().add(0, gapPos, gapPos, 0);
int gapLen = iterator.getCodeLength() - gapPos - handlerLen;
int subr = iterator.getCodeLength() - gapLen;
while (iterator.hasNext()) {
int pos = iterator.next();
if (pos >= subr)
break;
int c = iterator.byteAt(pos);
if (c == Opcode.ARETURN || c == Opcode.IRETURN
|| c == Opcode.FRETURN || c == Opcode.LRETURN
|| c == Opcode.DRETURN || c == Opcode.RETURN) {
if (subr - pos > Short.MAX_VALUE - 5) {
int s = iterator.insertGap(pos, 5);
iterator.writeByte(Opcode.JSR_W, pos);
iterator.write32bit(subr - pos + s, pos + 1);
}
else {
int s = iterator.insertGap(pos, 3);
iterator.writeByte(Opcode.JSR, pos);
iterator.write16bit(subr - pos + s, pos + 1);
}
subr = iterator.getCodeLength() - gapLen;
}
}
}
catch (NotFoundException e) {
throw new CannotCompileException(e);
}
catch (CompileError e) {
throw new CannotCompileException(e);
}
catch (BadBytecode e) {
throw new CannotCompileException(e);
}
}
private int insertAfterHandler(boolean asFinally, Bytecode b,
CtClass rtype)
{
if (!asFinally)
return 0;
int var = b.getMaxLocals();
b.incMaxLocals(1);
int pc = b.currentPc();
b.addAstore(var);
if (rtype.isPrimitive()) {
char c = ((CtPrimitiveType)rtype).getDescriptor();
if (c == 'D')
b.addDconst(0.0);
else if (c == 'F')
b.addFconst(0);
else if (c == 'J')
b.addLconst(0);
else if (c != 'V') // int, boolean, char, short, ...
b.addIconst(0);
}
else
b.addOpcode(Opcode.ACONST_NULL);
b.addOpcode(Opcode.JSR);
int pc2 = b.currentPc();
b.addIndex(0); // correct later
b.addAload(var);
b.addOpcode(Opcode.ATHROW);
int pc3 = b.currentPc();
b.write16bit(pc2, pc3 - pc2 + 1);
return pc3 - pc;
}
/* -- OLD version --
public void insertAfter(String src) throws CannotCompileException {
declaringClass.checkModify();
CodeAttribute ca = methodInfo.getCodeAttribute();
CodeIterator iterator = ca.iterator();
Bytecode b = new Bytecode(methodInfo.getConstPool(),
ca.getMaxStack(), ca.getMaxLocals());
b.setStackDepth(ca.getMaxStack());
Javac jv = new Javac(b, declaringClass);
try {
jv.recordParams(getParameterTypes(),
Modifier.isStatic(getModifiers()));
CtClass rtype = getReturnType0();
int varNo = jv.recordReturnType(rtype, true);
boolean isVoid = rtype == CtClass.voidType;
if (isVoid) {
b.addOpcode(Opcode.ACONST_NULL);
b.addAstore(varNo);
jv.compileStmnt(src);
}
else {
b.addStore(varNo, rtype);
jv.compileStmnt(src);
b.addLoad(varNo, rtype);
}
byte[] code = b.get();
ca.setMaxStack(b.getMaxStack());
ca.setMaxLocals(b.getMaxLocals());
while (iterator.hasNext()) {
int pos = iterator.next();
int c = iterator.byteAt(pos);
if (c == Opcode.ARETURN || c == Opcode.IRETURN
|| c == Opcode.FRETURN || c == Opcode.LRETURN
|| c == Opcode.DRETURN || c == Opcode.RETURN)
iterator.insert(pos, code);
}
}
catch (NotFoundException e) {
throw new CannotCompileException(e);
}
catch (CompileError e) {
throw new CannotCompileException(e);
}
catch (BadBytecode e) {
throw new CannotCompileException(e);
}
}
*/
/**
* Adds a catch clause that handles an exception thrown in the
* body. The catch clause must end with a return or throw statement.
*
* @param src the source code representing the catch clause.
* It must be a single statement or block.
* @param exceptionType the type of the exception handled by the
* catch clause.
*/
public void addCatch(String src, CtClass exceptionType)
throws CannotCompileException
{
addCatch(src, exceptionType, "$e");
}
/**
* Adds a catch clause that handles an exception thrown in the
* body. The catch clause must end with a return or throw statement.
*
* @param src the source code representing the catch clause.
* It must be a single statement or block.
* @param exceptionType the type of the exception handled by the
* catch clause.
* @param exceptionName the name of the variable containing the
* caught exception, for example,
* $e
.
*/
public void addCatch(String src, CtClass exceptionType,
String exceptionName)
throws CannotCompileException
{
declaringClass.checkModify();
ConstPool cp = methodInfo.getConstPool();
CodeAttribute ca = methodInfo.getCodeAttribute();
CodeIterator iterator = ca.iterator();
Bytecode b = new Bytecode(cp, ca.getMaxStack(), ca.getMaxLocals());
b.setStackDepth(1);
Javac jv = new Javac(b, declaringClass);
try {
jv.recordParams(getParameterTypes(),
Modifier.isStatic(getModifiers()));
int var = jv.recordVariable(exceptionType, exceptionName);
b.addAstore(var);
jv.compileStmnt(src);
int stack = b.getMaxStack();
int locals = b.getMaxLocals();
if (stack > ca.getMaxStack())
ca.setMaxStack(stack);
if (locals > ca.getMaxLocals())
ca.setMaxLocals(locals);
int len = iterator.getCodeLength();
int pos = iterator.append(b.get());
ca.getExceptionTable().add(0, len, len,
cp.addClassInfo(exceptionType));
iterator.append(b.getExceptionTable(), pos);
}
catch (NotFoundException e) {
throw new CannotCompileException(e);
}
catch (CompileError e) {
throw new CannotCompileException(e);
}
}
}