org.testifyproject.expr.ExprEditor 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 org.testifyproject.testifyprojectpliance 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 org.testifyproject.expr;
import org.testifyproject.bytecode.*;
import org.testifyproject.CtClass;
import org.testifyproject.CannotCompileException;
/**
* A translator of method bodies.
*
* The users can define a subclass of this class to customize how to
* modify a method body. The overall architecture is similar to the
* strategy pattern.
*
*
If instrument()
is called in
* CtMethod
, the method body is scanned from the beginning
* to the end.
* Whenever an expression, such as a method call and a new
* expression (object creation),
* is found, edit()
is called in ExprEdit
.
* edit()
can inspect and modify the given expression.
* The modification is reflected on the original method body. If
* edit()
does nothing, the original method body is not
* changed.
*
*
The following code is an example:
*
*
* CtMethod cm = ...;
* cm.instrument(new ExprEditor() {
* public void edit(MethodCall m) throws CannotCompileException {
* if (m.getClassName().equals("Point")) {
* System.out.println(m.getMethodName() + " line: "
* + m.getLineNumber());
* }
* });
*
*
* This code inspects all method calls appearing in the method represented
* by cm
and it prints the names and the line numbers of the
* methods declared in class Point
. This code does not modify
* the body of the method represented by cm
. If the method
* body must be modified, call replace()
* in MethodCall
.
*
* @see org.testifyproject.CtClass#instrument(ExprEditor)
* @see org.testifyproject.CtMethod#instrument(ExprEditor)
* @see org.testifyproject.CtConstructor#instrument(ExprEditor)
* @see MethodCall
* @see NewExpr
* @see FieldAccess
*
* @see org.testifyproject.CodeConverter
*/
public class ExprEditor {
/**
* Default constructor. It does nothing.
*/
public ExprEditor() {}
/**
* Undocumented method. Do not use; internal-use only.
*/
public boolean doit(CtClass clazz, MethodInfo minfo)
throws CannotCompileException
{
CodeAttribute codeAttr = minfo.getCodeAttribute();
if (codeAttr == null)
return false;
CodeIterator iterator = codeAttr.iterator();
boolean edited = false;
LoopContext context = new LoopContext(codeAttr.getMaxLocals());
while (iterator.hasNext())
if (loopBody(iterator, clazz, minfo, context))
edited = true;
ExceptionTable et = codeAttr.getExceptionTable();
int n = et.size();
for (int i = 0; i < n; ++i) {
Handler h = new Handler(et, i, iterator, clazz, minfo);
edit(h);
if (h.edited()) {
edited = true;
context.updateMax(h.locals(), h.stack());
}
}
// codeAttr might be modified by other partiess
// so I check the current value of max-locals.
if (codeAttr.getMaxLocals() < context.maxLocals)
codeAttr.setMaxLocals(context.maxLocals);
codeAttr.setMaxStack(codeAttr.getMaxStack() + context.maxStack);
try {
if (edited)
minfo.rebuildStackMapIf6(clazz.getClassPool(),
clazz.getClassFile2());
}
catch (BadBytecode b) {
throw new CannotCompileException(b.getMessage(), b);
}
return edited;
}
/**
* Visits each bytecode in the given range.
*/
boolean doit(CtClass clazz, MethodInfo minfo, LoopContext context,
CodeIterator iterator, int endPos)
throws CannotCompileException
{
boolean edited = false;
while (iterator.hasNext() && iterator.lookAhead() < endPos) {
int size = iterator.getCodeLength();
if (loopBody(iterator, clazz, minfo, context)) {
edited = true;
int size2 = iterator.getCodeLength();
if (size != size2) // the body was modified.
endPos += size2 - size;
}
}
return edited;
}
final static class NewOp {
NewOp next;
int pos;
String type;
NewOp(NewOp n, int p, String t) {
next = n;
pos = p;
type = t;
}
}
final static class LoopContext {
NewOp newList;
int maxLocals;
int maxStack;
LoopContext(int locals) {
maxLocals = locals;
maxStack = 0;
newList = null;
}
void updateMax(int locals, int stack) {
if (maxLocals < locals)
maxLocals = locals;
if (maxStack < stack)
maxStack = stack;
}
}
final boolean loopBody(CodeIterator iterator, CtClass clazz,
MethodInfo minfo, LoopContext context)
throws CannotCompileException
{
try {
Expr expr = null;
int pos = iterator.next();
int c = iterator.byteAt(pos);
if (c < Opcode.GETSTATIC) // c < 178
/* skip */;
else if (c < Opcode.NEWARRAY) { // c < 188
if (c == Opcode.INVOKESTATIC
|| c == Opcode.INVOKEINTERFACE
|| c == Opcode.INVOKEVIRTUAL) {
expr = new MethodCall(pos, iterator, clazz, minfo);
edit((MethodCall)expr);
}
else if (c == Opcode.GETFIELD || c == Opcode.GETSTATIC
|| c == Opcode.PUTFIELD
|| c == Opcode.PUTSTATIC) {
expr = new FieldAccess(pos, iterator, clazz, minfo, c);
edit((FieldAccess)expr);
}
else if (c == Opcode.NEW) {
int index = iterator.u16bitAt(pos + 1);
context.newList = new NewOp(context.newList, pos,
minfo.getConstPool().getClassInfo(index));
}
else if (c == Opcode.INVOKESPECIAL) {
NewOp newList = context.newList;
if (newList != null
&& minfo.getConstPool().isConstructor(newList.type,
iterator.u16bitAt(pos + 1)) > 0) {
expr = new NewExpr(pos, iterator, clazz, minfo,
newList.type, newList.pos);
edit((NewExpr)expr);
context.newList = newList.next;
}
else {
MethodCall mcall = new MethodCall(pos, iterator, clazz, minfo);
if (mcall.getMethodName().equals(MethodInfo.nameInit)) {
ConstructorCall ccall = new ConstructorCall(pos, iterator, clazz, minfo);
expr = ccall;
edit(ccall);
}
else {
expr = mcall;
edit(mcall);
}
}
}
}
else { // c >= 188
if (c == Opcode.NEWARRAY || c == Opcode.ANEWARRAY
|| c == Opcode.MULTIANEWARRAY) {
expr = new NewArray(pos, iterator, clazz, minfo, c);
edit((NewArray)expr);
}
else if (c == Opcode.INSTANCEOF) {
expr = new Instanceof(pos, iterator, clazz, minfo);
edit((Instanceof)expr);
}
else if (c == Opcode.CHECKCAST) {
expr = new Cast(pos, iterator, clazz, minfo);
edit((Cast)expr);
}
}
if (expr != null && expr.edited()) {
context.updateMax(expr.locals(), expr.stack());
return true;
}
else
return false;
}
catch (BadBytecode e) {
throw new CannotCompileException(e);
}
}
/**
* Edits a new expression (overridable).
* The default implementation performs nothing.
*
* @param e the new expression creating an object.
*/
public void edit(NewExpr e) throws CannotCompileException {}
/**
* Edits an expression for array creation (overridable).
* The default implementation performs nothing.
*
* @param a the new expression for creating an array.
* @throws CannotCompileException
*/
public void edit(NewArray a) throws CannotCompileException {}
/**
* Edits a method call (overridable).
*
* The default implementation performs nothing.
*/
public void edit(MethodCall m) throws CannotCompileException {}
/**
* Edits a constructor call (overridable).
* The constructor call is either
* super()
or this()
* included in a constructor body.
*
* The default implementation performs nothing.
*
* @see #edit(NewExpr)
*/
public void edit(ConstructorCall c) throws CannotCompileException {}
/**
* Edits a field-access expression (overridable).
* Field access means both read and write.
* The default implementation performs nothing.
*/
public void edit(FieldAccess f) throws CannotCompileException {}
/**
* Edits an instanceof expression (overridable).
* The default implementation performs nothing.
*/
public void edit(Instanceof i) throws CannotCompileException {}
/**
* Edits an expression for explicit type casting (overridable).
* The default implementation performs nothing.
*/
public void edit(Cast c) throws CannotCompileException {}
/**
* Edits a catch clause (overridable).
* The default implementation performs nothing.
*/
public void edit(Handler h) throws CannotCompileException {}
}