/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. Alternatively, the contents of this file may be used under
* the terms of the GNU Lesser General Public License Version 2.1 or later,
* or the Apache License Version 2.0.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*/
package 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>.
*
* <p>To directly read or modify bytecode, obtain <code>MethodInfo</code>
* objects.
*
* @see #getMethodInfo()
*/
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 <code>MethodInfo</code> representing this method/constructor in the
* class file.
*
* <p>If you modify the bytecode through the returned
* <code>MethodInfo</code> object, you might have to explicitly
* rebuild a stack map table. Javassist does not automatically
* rebuild it for avoiding unnecessary rebuilding.
*
* @see javassist.bytecode.MethodInfo#rebuildStackMap(ClassPool)
*/
public
MethodInfo getMethodInfo() {
declaringClass.
checkModify();
return
methodInfo;
}
/**
* Returns the <code>MethodInfo</code> 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.
*
* @see javassist.bytecode.Descriptor
* @see #getGenericSignature()
*/
public
String getSignature() {
return
methodInfo.
getDescriptor();
}
/**
* Returns the generic signature of the method.
* It represents parameter types including type variables.
*
* @see SignatureAttribute#toMethodSignature(String)
* @since 3.17
*/
public
String getGenericSignature() {
SignatureAttribute sa
= (
SignatureAttribute)
methodInfo.
getAttribute(
SignatureAttribute.
tag);
return
sa == null ? null :
sa.
getSignature();
}
/**
* Set the generic signature of the method.
* It represents parameter types including type variables.
* See {@link javassist.CtClass#setGenericSignature(String)}
* for a code sample.
*
* @param sig a new generic signature.
* @see javassist.bytecode.SignatureAttribute.MethodSignature#encode()
* @since 3.17
*/
public void
setGenericSignature(
String sig) {
declaringClass.
checkModify();
methodInfo.
addAttribute(new
SignatureAttribute(
methodInfo.
getConstPool(),
sig));
}
/**
* 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);
LocalVariableTypeAttribute lvta
= (
LocalVariableTypeAttribute)
ca.
getAttribute(
LocalVariableTypeAttribute.
tag);
if (
lvta != null)
lvta.
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.
*
* <p>While executing this method, only <code>replace()</code>
* in <code>Expr</code> is available for bytecode modification.
* Other methods such as <code>insertBefore()</code> may collapse
* the bytecode because the <code>ExprEditor</code> loses
* its current position.
*
* @param editor specifies how to modify.
* @see javassist.expr.Expr#replace(String)
* @see #insertBefore(String)
*/
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);
int
handlerPos =
iterator.
getCodeLength();
if (
asFinally)
ca.
getExceptionTable().
add(
getStartPosOfBody(
ca),
handlerPos,
handlerPos, 0);
int
adviceLen = 0;
int
advicePos = 0;
boolean
noReturn = true;
while (
iterator.
hasNext()) {
int
pos =
iterator.
next();
if (
pos >=
handlerPos)
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 (
noReturn) {
// finally clause for normal termination
adviceLen =
insertAfterAdvice(
b,
jv,
src,
pool,
rtype,
varNo);
handlerPos =
iterator.
append(
b.
get());
iterator.
append(
b.
getExceptionTable(),
handlerPos);
advicePos =
iterator.
getCodeLength() -
adviceLen;
handlerLen =
advicePos -
handlerPos;
noReturn = false;
}
insertGoto(
iterator,
advicePos,
pos);
advicePos =
iterator.
getCodeLength() -
adviceLen;
handlerPos =
advicePos -
handlerLen;
}
}
if (
noReturn) {
handlerPos =
iterator.
append(
b.
get());
iterator.
append(
b.
getExceptionTable(),
handlerPos);
}
ca.
setMaxStack(
b.
getMaxStack());
ca.
setMaxLocals(
b.
getMaxLocals());
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 int
insertAfterAdvice(
Bytecode code,
Javac jv,
String src,
ConstPool cp,
CtClass rtype, int
varNo)
throws
CompileError
{
int
pc =
code.
currentPc();
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);
}
return
code.
currentPc() -
pc;
}
/*
* 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;
int
len =
wide ? 4 : 2;
CodeIterator.
Gap gap =
iterator.
insertGapAt(
pos,
len, false);
pos =
gap.
position +
gap.
length -
len;
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 {
if (
gap.
length < 4) {
CodeIterator.
Gap gap2 =
iterator.
insertGapAt(
gap.
position, 2, false);
pos =
gap2.
position +
gap2.
length +
gap.
length - 4;
}
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);
}
}
}