blob: 1414d05d7ec4bf0854d0ba4e42ec39993d4aa2cb [file] [log] [blame]
/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 1999-2007 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;
/**
* <code>CtBehavior</code> represents a method, a constructor,
* or a static constructor (class initializer).
* It is the abstract super class of
* <code>CtMethod</code> and <code>CtConstructor</code>.
*/
public abstract class CtBehavior extends CtMember {
protected MethodInfo methodInfo;
protected CtBehavior(CtClass clazz, MethodInfo minfo) {
super(clazz);
methodInfo = minfo;
}
/**
* @param isCons true if this is a constructor.
*/
void copy(CtBehavior src, boolean isCons, ClassMap map)
throws CannotCompileException
{
CtClass declaring = declaringClass;
MethodInfo srcInfo = src.methodInfo;
CtClass srcClass = src.getDeclaringClass();
ConstPool cp = declaring.getClassFile2().getConstPool();
map = new ClassMap(map);
map.put(srcClass.getName(), declaring.getName());
try {
boolean patch = false;
CtClass srcSuper = srcClass.getSuperclass();
CtClass destSuper = declaring.getSuperclass();
String destSuperName = null;
if (srcSuper != null && destSuper != null) {
String srcSuperName = srcSuper.getName();
destSuperName = destSuper.getName();
if (!srcSuperName.equals(destSuperName))
if (srcSuperName.equals(CtClass.javaLangObject))
patch = true;
else
map.putIfNone(srcSuperName, destSuperName);
}
// a stack map table is copied from srcInfo.
methodInfo = new MethodInfo(cp, srcInfo.getName(), srcInfo, map);
if (isCons && patch)
methodInfo.setSuperclass(destSuperName);
}
catch (NotFoundException e) {
throw new CannotCompileException(e);
}
catch (BadBytecode e) {
throw new CannotCompileException(e);
}
}
protected void extendToString(StringBuffer buffer) {
buffer.append(' ');
buffer.append(getName());
buffer.append(' ');
buffer.append(methodInfo.getDescriptor());
}
/**
* Returns the method or constructor name followed by parameter types
* such as <code>javassist.CtBehavior.stBody(String)</code>.
*
* @since 3.5
*/
public abstract String getLongName();
/**
* Returns the MethodInfo representing this method/constructor in the
* class file.
*/
public MethodInfo getMethodInfo() {
declaringClass.checkModify();
return methodInfo;
}
/**
* Returns the MethodInfo representing the method/constructor in the
* class file (read only).
* Normal applications do not need calling this method. Use
* <code>getMethodInfo()</code>.
*
* <p>The <code>MethodInfo</code> object obtained by this method
* is read only. Changes to this object might not be reflected
* on a class file generated by <code>toBytecode()</code>,
* <code>toClass()</code>, etc in <code>CtClass</code>.
*
* <p>This method is available even if the <code>CtClass</code>
* containing this method is frozen. However, if the class is
* frozen, the <code>MethodInfo</code> might be also pruned.
*
* @see #getMethodInfo()
* @see CtClass#isFrozen()
* @see CtClass#prune()
*/
public MethodInfo getMethodInfo2() { return methodInfo; }
/**
* Obtains the modifiers of the method/constructor.
*
* @return modifiers encoded with
* <code>javassist.Modifier</code>.
* @see Modifier
*/
public int getModifiers() {
return AccessFlag.toModifier(methodInfo.getAccessFlags());
}
/**
* Sets the encoded modifiers of the method/constructor.
*
* <p>Changing the modifiers may cause a problem.
* For example, if a non-static method is changed to static,
* the method will be rejected by the bytecode verifier.
*
* @see Modifier
*/
public void setModifiers(int mod) {
declaringClass.checkModify();
methodInfo.setAccessFlags(AccessFlag.of(mod));
}
/**
* Returns true if the class has the specified annotation class.
*
* @param clz the annotation class.
* @return <code>true</code> if the annotation is found,
* otherwise <code>false</code>.
* @since 3.11
*/
public boolean hasAnnotation(Class clz) {
MethodInfo mi = getMethodInfo2();
AnnotationsAttribute ainfo = (AnnotationsAttribute)
mi.getAttribute(AnnotationsAttribute.invisibleTag);
AnnotationsAttribute ainfo2 = (AnnotationsAttribute)
mi.getAttribute(AnnotationsAttribute.visibleTag);
return CtClassType.hasAnnotationType(clz,
getDeclaringClass().getClassPool(),
ainfo, ainfo2);
}
/**
* Returns the annotation if the class has the specified annotation class.
* For example, if an annotation <code>@Author</code> is associated
* with this method/constructor, an <code>Author</code> object is returned.
* The member values can be obtained by calling methods on
* the <code>Author</code> object.
*
* @param clz the annotation class.
* @return the annotation if found, otherwise <code>null</code>.
* @since 3.11
*/
public Object getAnnotation(Class clz) throws ClassNotFoundException {
MethodInfo mi = getMethodInfo2();
AnnotationsAttribute ainfo = (AnnotationsAttribute)
mi.getAttribute(AnnotationsAttribute.invisibleTag);
AnnotationsAttribute ainfo2 = (AnnotationsAttribute)
mi.getAttribute(AnnotationsAttribute.visibleTag);
return CtClassType.getAnnotationType(clz,
getDeclaringClass().getClassPool(),
ainfo, ainfo2);
}
/**
* Returns the annotations associated with this method or constructor.
*
* @return an array of annotation-type objects.
* @see #getAvailableAnnotations()
* @since 3.1
*/
public Object[] getAnnotations() throws ClassNotFoundException {
return getAnnotations(false);
}
/**
* Returns the annotations associated with this method or constructor.
* If any annotations are not on the classpath, they are not included
* in the returned array.
*
* @return an array of annotation-type objects.
* @see #getAnnotations()
* @since 3.3
*/
public Object[] getAvailableAnnotations(){
try{
return getAnnotations(true);
}
catch (ClassNotFoundException e){
throw new RuntimeException("Unexpected exception", e);
}
}
private Object[] getAnnotations(boolean ignoreNotFound)
throws ClassNotFoundException
{
MethodInfo mi = getMethodInfo2();
AnnotationsAttribute ainfo = (AnnotationsAttribute)
mi.getAttribute(AnnotationsAttribute.invisibleTag);
AnnotationsAttribute ainfo2 = (AnnotationsAttribute)
mi.getAttribute(AnnotationsAttribute.visibleTag);
return CtClassType.toAnnotationType(ignoreNotFound,
getDeclaringClass().getClassPool(),
ainfo, ainfo2);
}
/**
* Returns the parameter annotations associated with this method or constructor.
*
* @return an array of annotation-type objects. The length of the returned array is
* equal to the number of the formal parameters. If each parameter has no
* annotation, the elements of the returned array are empty arrays.
*
* @see #getAvailableParameterAnnotations()
* @see #getAnnotations()
* @since 3.1
*/
public Object[][] getParameterAnnotations() throws ClassNotFoundException {
return getParameterAnnotations(false);
}
/**
* Returns the parameter annotations associated with this method or constructor.
* If any annotations are not on the classpath, they are not included in the
* returned array.
*
* @return an array of annotation-type objects. The length of the returned array is
* equal to the number of the formal parameters. If each parameter has no
* annotation, the elements of the returned array are empty arrays.
*
* @see #getParameterAnnotations()
* @see #getAvailableAnnotations()
* @since 3.3
*/
public Object[][] getAvailableParameterAnnotations(){
try {
return getParameterAnnotations(true);
}
catch(ClassNotFoundException e) {
throw new RuntimeException("Unexpected exception", e);
}
}
Object[][] getParameterAnnotations(boolean ignoreNotFound)
throws ClassNotFoundException
{
MethodInfo mi = getMethodInfo2();
ParameterAnnotationsAttribute ainfo = (ParameterAnnotationsAttribute)
mi.getAttribute(ParameterAnnotationsAttribute.invisibleTag);
ParameterAnnotationsAttribute ainfo2 = (ParameterAnnotationsAttribute)
mi.getAttribute(ParameterAnnotationsAttribute.visibleTag);
return CtClassType.toAnnotationType(ignoreNotFound,
getDeclaringClass().getClassPool(),
ainfo, ainfo2, mi);
}
/**
* Obtains parameter types of this method/constructor.
*/
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 method signature (the parameter types
* and the return type).
* The method signature is represented by a character string
* called method descriptor, which is defined in the JVM specification.
* If two methods/constructors have
* the same parameter types
* and the return type, <code>getSignature()</code> returns the
* same string (the return type of constructors is <code>void</code>).
*
* <p>Note that the returned string is not the type signature
* contained in the <code>SignatureAttirbute</code>. It is
* a descriptor. To obtain a type signature, call the following
* methods:
*
* <ul><pre>getMethodInfo().getAttribute(SignatureAttribute.tag)
* </pre></ul>
*
* @see javassist.bytecode.Descriptor
* @see javassist.bytecode.SignatureAttribute
*/
public String getSignature() {
return methodInfo.getDescriptor();
}
/**
* Obtains exceptions that this method/constructor may throw.
*
* @return a zero-length array if there is no throws clause.
*/
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 method/constructor may throw.
*/
public void setExceptionTypes(CtClass[] types) throws NotFoundException {
declaringClass.checkModify();
if (types == null || types.length == 0) {
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 method/constructor body.
*
* @param src the source code representing the body.
* It must be a single statement or block.
* If it is <code>null</code>, the substituted
* body does nothing except returning zero or null.
*/
public void setBody(String src) throws CannotCompileException {
setBody(src, null, null);
}
/**
* Sets a method/constructor body.
*
* @param src the source code representing the body.
* It must be a single statement or block.
* If it is <code>null</code>, the substituted
* body does nothing except returning zero or null.
* @param delegateObj the source text specifying the object
* that is called on by <code>$proceed()</code>.
* @param delegateMethod the name of the method
* that is called by <code>$proceed()</code>.
*/
public void setBody(String src,
String delegateObj, String delegateMethod)
throws CannotCompileException
{
CtClass cc = declaringClass;
cc.checkModify();
try {
Javac jv = new Javac(cc);
if (delegateMethod != null)
jv.recordProceed(delegateObj, delegateMethod);
Bytecode b = jv.compileBody(this, src);
methodInfo.setCodeAttribute(b.toCodeAttribute());
methodInfo.setAccessFlags(methodInfo.getAccessFlags()
& ~AccessFlag.ABSTRACT);
methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2());
declaringClass.rebuildClassFile();
}
catch (CompileError e) {
throw new CannotCompileException(e);
} catch (BadBytecode e) {
throw new CannotCompileException(e);
}
}
static void setBody0(CtClass srcClass, MethodInfo srcInfo,
CtClass destClass, MethodInfo destInfo,
ClassMap map)
throws CannotCompileException
{
destClass.checkModify();
map = new ClassMap(map);
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);
// a stack map table is copied to destInfo.
}
}
catch (CodeAttribute.RuntimeCopyException e) {
/* the exception may be thrown by copy() in CodeAttribute.
*/
throw new CannotCompileException(e);
}
destInfo.setAccessFlags(destInfo.getAccessFlags()
& ~AccessFlag.ABSTRACT);
destClass.rebuildClassFile();
}
/**
* Obtains an attribute with the given name.
* If that attribute is not found in the class file, this
* method returns null.
*
* <p>Note that an attribute is a data block specified by
* the class file format. It is not an annotation.
* See {@link javassist.bytecode.AttributeInfo}.
*
* @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.
*
* <p>Note that an attribute is a data block specified by
* the class file format. It is not an annotation.
* See {@link javassist.bytecode.AttributeInfo}.
*
* @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 <code>$cflow</code> for this method/constructor.
* If <code>$cflow</code> is used, the class files modified
* with Javassist requires a support class
* <code>javassist.runtime.Cflow</code> at runtime
* (other Javassist classes are not required at runtime).
*
* <p>Every <code>$cflow</code> variable is given a unique name.
* For example, if the given name is <code>"Point.paint"</code>,
* then the variable is indicated by <code>$cflow(Point.paint)</code>.
*
* @param name <code>$cflow</code> name. It can include
* alphabets, numbers, <code>_</code>,
* <code>$</code>, and <code>.</code> (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();", false);
String src = fname + ".exit();";
insertAfter(src, true);
}
catch (NotFoundException e) {
throw new CannotCompileException(e);
}
}
/**
* Declares a new local variable. The scope of this variable is the
* whole method body. The initial value of that variable is not set.
* The declared variable can be accessed in the code snippet inserted
* by <code>insertBefore()</code>, <code>insertAfter()</code>, etc.
*
* <p>If the second parameter <code>asFinally</code> to
* <code>insertAfter()</code> is true, the declared local variable
* is not visible from the code inserted by <code>insertAfter()</code>.
*
* @param name the name of the variable
* @param type the type of the variable
* @see #insertBefore(String)
* @see #insertAfter(String)
*/
public void addLocalVariable(String name, CtClass type)
throws CannotCompileException
{
declaringClass.checkModify();
ConstPool cp = methodInfo.getConstPool();
CodeAttribute ca = methodInfo.getCodeAttribute();
if (ca == null)
throw new CannotCompileException("no method body");
LocalVariableAttribute va = (LocalVariableAttribute)ca.getAttribute(
LocalVariableAttribute.tag);
if (va == null) {
va = new LocalVariableAttribute(cp);
ca.getAttributes().add(va);
}
int maxLocals = ca.getMaxLocals();
String desc = Descriptor.of(type);
va.addEntry(0, ca.getCodeLength(),
cp.addUtf8Info(name), cp.addUtf8Info(desc), maxLocals);
ca.setMaxLocals(maxLocals + Descriptor.dataSize(desc));
}
/**
* Inserts a new parameter, which becomes the first parameter.
*/
public void insertParameter(CtClass type)
throws CannotCompileException
{
declaringClass.checkModify();
String desc = methodInfo.getDescriptor();
String desc2 = Descriptor.insertParameter(type, desc);
try {
addParameter2(Modifier.isStatic(getModifiers()) ? 0 : 1, type, desc);
}
catch (BadBytecode e) {
throw new CannotCompileException(e);
}
methodInfo.setDescriptor(desc2);
}
/**
* Appends a new parameter, which becomes the last parameter.
*/
public void addParameter(CtClass type)
throws CannotCompileException
{
declaringClass.checkModify();
String desc = methodInfo.getDescriptor();
String desc2 = Descriptor.appendParameter(type, desc);
int offset = Modifier.isStatic(getModifiers()) ? 0 : 1;
try {
addParameter2(offset + Descriptor.paramSize(desc), type, desc);
}
catch (BadBytecode e) {
throw new CannotCompileException(e);
}
methodInfo.setDescriptor(desc2);
}
private void addParameter2(int where, CtClass type, String desc)
throws BadBytecode
{
CodeAttribute ca = methodInfo.getCodeAttribute();
if (ca != null) {
int size = 1;
char typeDesc = 'L';
int classInfo = 0;
if (type.isPrimitive()) {
CtPrimitiveType cpt = (CtPrimitiveType)type;
size = cpt.getDataSize();
typeDesc = cpt.getDescriptor();
}
else
classInfo = methodInfo.getConstPool().addClassInfo(type);
ca.insertLocalVar(where, size);
LocalVariableAttribute va
= (LocalVariableAttribute)
ca.getAttribute(LocalVariableAttribute.tag);
if (va != null)
va.shiftIndex(where, size);
StackMapTable smt = (StackMapTable)ca.getAttribute(StackMapTable.tag);
if (smt != null)
smt.insertLocal(where, StackMapTable.typeTagOf(typeDesc), classInfo);
StackMap sm = (StackMap)ca.getAttribute(StackMap.tag);
if (sm != null)
sm.insertLocal(where, StackMapTable.typeTagOf(typeDesc), classInfo);
}
}
/**
* Modifies the method/constructor 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 method/constructor body.
*
* @param editor specifies how to modify.
*/
public void instrument(ExprEditor editor)
throws CannotCompileException
{
// if the class is not frozen,
// does not turn the modified flag on.
if (declaringClass.isFrozen())
declaringClass.checkModify();
if (editor.doit(declaringClass, methodInfo))
declaringClass.checkModify();
}
/**
* Inserts bytecode at the beginning of the body.
*
* <p>If this object represents a constructor,
* the bytecode is inserted before
* a constructor in the super class or this class is called.
* Therefore, the inserted bytecode is subject to constraints described
* in Section 4.8.2 of The Java Virtual Machine Specification (2nd ed).
* For example, it cannot access instance fields or methods although
* it may assign a value to an instance field directly declared in this
* class. Accessing static fields and methods is allowed.
* Use <code>insertBeforeBody()</code> in <code>CtConstructor</code>.
*
* @param src the source code representing the inserted bytecode.
* It must be a single statement or block.
* @see CtConstructor#insertBeforeBody(String)
*/
public void insertBefore(String src) throws CannotCompileException {
insertBefore(src, true);
}
private void insertBefore(String src, boolean rebuild)
throws CannotCompileException
{
CtClass cc = declaringClass;
cc.checkModify();
CodeAttribute ca = methodInfo.getCodeAttribute();
if (ca == null)
throw new CannotCompileException("no method body");
CodeIterator iterator = ca.iterator();
Javac jv = new Javac(cc);
try {
int nvars = jv.recordParams(getParameterTypes(),
Modifier.isStatic(getModifiers()));
jv.recordParamNames(ca, nvars);
jv.recordLocalVariables(ca, 0);
jv.recordType(getReturnType0());
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);
if (rebuild)
methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2());
}
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.
* If this parameter is true, the inserted code cannot
* access local variables.
*/
public void insertAfter(String src, boolean asFinally)
throws CannotCompileException
{
CtClass cc = declaringClass;
cc.checkModify();
ConstPool pool = methodInfo.getConstPool();
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(pool, 0, retAddr + 1);
b.setStackDepth(ca.getMaxStack() + 1);
Javac jv = new Javac(b, cc);
try {
int nvars = jv.recordParams(getParameterTypes(),
Modifier.isStatic(getModifiers()));
jv.recordParamNames(ca, nvars);
CtClass rtype = getReturnType0();
int varNo = jv.recordReturnType(rtype, true);
jv.recordLocalVariables(ca, 0);
// finally clause for exceptions
int handlerLen = insertAfterHandler(asFinally, b, rtype, varNo,
jv, src);
// finally clause for normal termination
insertAfterAdvice(b, jv, src, pool, rtype, varNo);
ca.setMaxStack(b.getMaxStack());
ca.setMaxLocals(b.getMaxLocals());
int gapPos = iterator.append(b.get());
iterator.append(b.getExceptionTable(), gapPos);
if (asFinally)
ca.getExceptionTable().add(getStartPosOfBody(ca), 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) {
insertGoto(iterator, subr, pos);
subr = iterator.getCodeLength() - gapLen;
}
}
methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2());
}
catch (NotFoundException e) {
throw new CannotCompileException(e);
}
catch (CompileError e) {
throw new CannotCompileException(e);
}
catch (BadBytecode e) {
throw new CannotCompileException(e);
}
}
private void insertAfterAdvice(Bytecode code, Javac jv, String src,
ConstPool cp, CtClass rtype, int varNo)
throws CompileError
{
if (rtype == CtClass.voidType) {
code.addOpcode(Opcode.ACONST_NULL);
code.addAstore(varNo);
jv.compileStmnt(src);
code.addOpcode(Opcode.RETURN);
if (code.getMaxLocals() < 1)
code.setMaxLocals(1);
}
else {
code.addStore(varNo, rtype);
jv.compileStmnt(src);
code.addLoad(varNo, rtype);
if (rtype.isPrimitive())
code.addOpcode(((CtPrimitiveType)rtype).getReturnOp());
else
code.addOpcode(Opcode.ARETURN);
}
}
/*
* assert subr > pos
*/
private void insertGoto(CodeIterator iterator, int subr, int pos)
throws BadBytecode
{
iterator.setMark(subr);
// the gap length might be a multiple of 4.
iterator.writeByte(Opcode.NOP, pos);
boolean wide = subr + 2 - pos > Short.MAX_VALUE;
pos = iterator.insertGapAt(pos, wide ? 4 : 2, false).position;
int offset = iterator.getMark() - pos;
if (wide) {
iterator.writeByte(Opcode.GOTO_W, pos);
iterator.write32bit(offset, pos + 1);
}
else if (offset <= Short.MAX_VALUE) {
iterator.writeByte(Opcode.GOTO, pos);
iterator.write16bit(offset, pos + 1);
}
else {
pos = iterator.insertGapAt(pos, 2, false).position;
iterator.writeByte(Opcode.GOTO_W, pos);
iterator.write32bit(iterator.getMark() - pos, pos + 1);
}
}
/* insert a finally clause
*/
private int insertAfterHandler(boolean asFinally, Bytecode b,
CtClass rtype, int returnVarNo,
Javac javac, String src)
throws CompileError
{
if (!asFinally)
return 0;
int var = b.getMaxLocals();
b.incMaxLocals(1);
int pc = b.currentPc();
b.addAstore(var); // store an exception
if (rtype.isPrimitive()) {
char c = ((CtPrimitiveType)rtype).getDescriptor();
if (c == 'D') {
b.addDconst(0.0);
b.addDstore(returnVarNo);
}
else if (c == 'F') {
b.addFconst(0);
b.addFstore(returnVarNo);
}
else if (c == 'J') {
b.addLconst(0);
b.addLstore(returnVarNo);
}
else if (c == 'V') {
b.addOpcode(Opcode.ACONST_NULL);
b.addAstore(returnVarNo);
}
else { // int, boolean, char, short, ...
b.addIconst(0);
b.addIstore(returnVarNo);
}
}
else {
b.addOpcode(Opcode.ACONST_NULL);
b.addAstore(returnVarNo);
}
javac.compileStmnt(src);
b.addAload(var);
b.addOpcode(Opcode.ATHROW);
return b.currentPc() - 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,
* <code>$e</code>.
*/
public void addCatch(String src, CtClass exceptionType,
String exceptionName)
throws CannotCompileException
{
CtClass cc = declaringClass;
cc.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, cc);
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(getStartPosOfBody(ca), len, len,
cp.addClassInfo(exceptionType));
iterator.append(b.getExceptionTable(), pos);
methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2());
}
catch (NotFoundException e) {
throw new CannotCompileException(e);
}
catch (CompileError e) {
throw new CannotCompileException(e);
} catch (BadBytecode e) {
throw new CannotCompileException(e);
}
}
/* CtConstructor overrides this method.
*/
int getStartPosOfBody(CodeAttribute ca) throws CannotCompileException {
return 0;
}
/**
* Inserts bytecode at the specified line in the body.
* It is equivalent to:
*
* <br><code>insertAt(lineNum, true, src)</code>
*
* <br>See this method as well.
*
* @param lineNum the line number. The bytecode is inserted at the
* beginning of the code at the line specified by this
* line number.
* @param src the source code representing the inserted bytecode.
* It must be a single statement or block.
* @return the line number at which the bytecode has been inserted.
*
* @see CtBehavior#insertAt(int,boolean,String)
*/
public int insertAt(int lineNum, String src)
throws CannotCompileException
{
return insertAt(lineNum, true, src);
}
/**
* Inserts bytecode at the specified line in the body.
*
* <p>If there is not
* a statement at the specified line, the bytecode might be inserted
* at the line including the first statement after that line specified.
* For example, if there is only a closing brace at that line, the
* bytecode would be inserted at another line below.
* To know exactly where the bytecode will be inserted, call with
* <code>modify</code> set to <code>false</code>.
*
* @param lineNum the line number. The bytecode is inserted at the
* beginning of the code at the line specified by this
* line number.
* @param modify if false, this method does not insert the bytecode.
* It instead only returns the line number at which
* the bytecode would be inserted.
* @param src the source code representing the inserted bytecode.
* It must be a single statement or block.
* If modify is false, the value of src can be null.
* @return the line number at which the bytecode has been inserted.
*/
public int insertAt(int lineNum, boolean modify, String src)
throws CannotCompileException
{
CodeAttribute ca = methodInfo.getCodeAttribute();
if (ca == null)
throw new CannotCompileException("no method body");
LineNumberAttribute ainfo
= (LineNumberAttribute)ca.getAttribute(LineNumberAttribute.tag);
if (ainfo == null)
throw new CannotCompileException("no line number info");
LineNumberAttribute.Pc pc = ainfo.toNearPc(lineNum);
lineNum = pc.line;
int index = pc.index;
if (!modify)
return lineNum;
CtClass cc = declaringClass;
cc.checkModify();
CodeIterator iterator = ca.iterator();
Javac jv = new Javac(cc);
try {
jv.recordLocalVariables(ca, index);
jv.recordParams(getParameterTypes(),
Modifier.isStatic(getModifiers()));
jv.setMaxLocals(ca.getMaxLocals());
jv.compileStmnt(src);
Bytecode b = jv.getBytecode();
int locals = b.getMaxLocals();
int stack = b.getMaxStack();
ca.setMaxLocals(locals);
/* We assume that there is no values in the operand stack
* at the position where the bytecode is inserted.
*/
if (stack > ca.getMaxStack())
ca.setMaxStack(stack);
index = iterator.insertAt(index, b.get());
iterator.insert(b.getExceptionTable(), index);
methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2());
return lineNum;
}
catch (NotFoundException e) {
throw new CannotCompileException(e);
}
catch (CompileError e) {
throw new CannotCompileException(e);
}
catch (BadBytecode e) {
throw new CannotCompileException(e);
}
}
}