javassist.CtBehavior Maven / Gradle / Ivy
/*
* 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.AccessFlag;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.AttributeInfo;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.Bytecode;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.CodeIterator;
import javassist.bytecode.ConstPool;
import javassist.bytecode.Descriptor;
import javassist.bytecode.ExceptionsAttribute;
import javassist.bytecode.LineNumberAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.LocalVariableTypeAttribute;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Opcode;
import javassist.bytecode.ParameterAnnotationsAttribute;
import javassist.bytecode.SignatureAttribute;
import javassist.bytecode.StackMap;
import javassist.bytecode.StackMapTable;
import javassist.compiler.CompileError;
import javassist.compiler.Javac;
import javassist.expr.ExprEditor;
/**
* CtBehavior
represents a method, a constructor,
* or a static constructor (class initializer).
* It is the abstract super class of
* CtMethod
and CtConstructor
.
*
* To directly read or modify bytecode, obtain MethodInfo
* 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);
}
}
@Override
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 javassist.CtBehavior.stBody(String)
.
*
* @since 3.5
*/
public abstract String getLongName();
/**
* Returns the MethodInfo
representing this method/constructor in the
* class file.
*
*
If you modify the bytecode through the returned
* MethodInfo
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 MethodInfo
representing the method/constructor in the
* class file (read only).
* Normal applications do not need calling this method. Use
* getMethodInfo()
.
*
*
The MethodInfo
object obtained by this method
* is read only. Changes to this object might not be reflected
* on a class file generated by toBytecode()
,
* toClass()
, etc in CtClass
.
*
*
This method is available even if the CtClass
* containing this method is frozen. However, if the class is
* frozen, the MethodInfo
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
* javassist.Modifier
.
* @see Modifier
*/
@Override
public int getModifiers() {
return AccessFlag.toModifier(methodInfo.getAccessFlags());
}
/**
* Sets the encoded modifiers of the method/constructor.
*
*
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
*/
@Override
public void setModifiers(int mod) {
declaringClass.checkModify();
methodInfo.setAccessFlags(AccessFlag.of(mod));
}
/**
* Returns true if the class has the specified annotation type.
*
* @param typeName the name of annotation type.
* @return true
if the annotation is found,
* otherwise false
.
* @since 3.21
*/
@Override
public boolean hasAnnotation(String typeName) {
MethodInfo mi = getMethodInfo2();
AnnotationsAttribute ainfo = (AnnotationsAttribute)
mi.getAttribute(AnnotationsAttribute.invisibleTag);
AnnotationsAttribute ainfo2 = (AnnotationsAttribute)
mi.getAttribute(AnnotationsAttribute.visibleTag);
return CtClassType.hasAnnotationType(typeName,
getDeclaringClass().getClassPool(),
ainfo, ainfo2);
}
/**
* Returns the annotation if the class has the specified annotation class.
* For example, if an annotation @Author
is associated
* with this method/constructor, an Author
object is returned.
* The member values can be obtained by calling methods on
* the Author
object.
*
* @param clz the annotation class.
* @return the annotation if found, otherwise null
.
* @since 3.11
*/
@Override
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
*/
@Override
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
*/
@Override
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, getSignature()
returns the
* same string (the return type of constructors is void
).
*
*
Note that the returned string is not the type signature
* contained in the SignatureAttirbute
. It is
* a descriptor.
*
* @see javassist.bytecode.Descriptor
* @see #getGenericSignature()
*/
@Override
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
*/
@Override
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
*/
@Override
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 null
, 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 null
, the substituted
* body does nothing except returning zero or null.
* @param delegateObj the source text specifying the object
* that is called on by $proceed()
.
* @param delegateMethod the name of the method
* that is called by $proceed()
.
*/
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.
*
*
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
*/
@Override
public byte[] getAttribute(String name)
{
AttributeInfo ai = methodInfo.getAttribute(name);
if (ai == null)
return null;
return ai.get();
}
/**
* Adds an attribute. The attribute is saved in the class file.
*
*
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
*/
@Override
public void setAttribute(String name, byte[] data)
{
declaringClass.checkModify();
methodInfo.addAttribute(new AttributeInfo(methodInfo.getConstPool(),
name, data));
}
/**
* Declares to use $cflow
for this method/constructor.
* 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();", 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 insertBefore()
, insertAfter()
, etc.
*
*
If the second parameter asFinally
to
* insertAfter()
is true, the declared local variable
* is not visible from the code inserted by insertAfter()
.
*
* @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.
*
*
While executing this method, only replace()
* in Expr
is available for bytecode modification.
* Other methods such as insertBefore()
may collapse
* the bytecode because the ExprEditor
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.
*
*
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 insertBeforeBody()
in CtConstructor
.
*
* @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.recordReturnType(getReturnType0(), false);
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 instruction.
* 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, false);
}
/**
* Inserts bytecode at the end of the body.
* The bytecode is inserted just before every return instruction.
*
* @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
{
insertAfter(src, asFinally, false);
}
/**
* Inserts bytecode at the end of the body.
* The bytecode is inserted just before every return instruction.
*
* @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.
* @param redundant if true, redundant bytecode will be generated.
* the redundancy is necessary when some compilers (Kotlin?)
* generate the original bytecode.
* The other insertAfter
methods calls this method
* with false
for this parameter.
* A tip is to pass this.getDeclaringClass().isKotlin()
* to this parameter.
*
* @see CtClass#isKotlin()
* @see #getDeclaringClass()
* @since 3.26
*/
public void insertAfter(String src, boolean asFinally, boolean redundant)
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 (redundant) {
iterator.setMark2(handlerPos);
Bytecode bcode;
Javac jvc;
int retVarNo;
if (noReturn) {
noReturn = false;
bcode = b;
jvc = jv;
retVarNo = varNo;
}
else {
bcode = new Bytecode(pool, 0, retAddr + 1);
bcode.setStackDepth(ca.getMaxStack() + 1);
jvc = new Javac(bcode, cc);
int nvars2 = jvc.recordParams(getParameterTypes(),
Modifier.isStatic(getModifiers()));
jvc.recordParamNames(ca, nvars2);
retVarNo = jvc.recordReturnType(rtype, true);
jvc.recordLocalVariables(ca, 0);
}
int adviceLen2 = insertAfterAdvice(bcode, jvc, src, pool, rtype, retVarNo);
int offset = iterator.append(bcode.get());
iterator.append(bcode.getExceptionTable(), offset);
int advicePos2 = iterator.getCodeLength() - adviceLen2;
insertGoto(iterator, advicePos2, pos);
handlerPos = iterator.getMark2();
}
else {
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,
* $e
.
*/
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:
*
*
insertAt(lineNum, true, src)
*
*
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.
*
*
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
* modify
set to false
.
*
* @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);
}
}
}