javassist.bytecode.analysis.Analyzer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache Show documentation
Show all versions of ehcache Show documentation
Ehcache is an open source, standards-based cache used to boost performance,
offload the database and simplify scalability. Ehcache is robust, proven and full-featured and
this has made it the most widely-used Java-based cache.
/*
* 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.bytecode.analysis;
import java.util.Iterator;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.CodeIterator;
import javassist.bytecode.ConstPool;
import javassist.bytecode.Descriptor;
import javassist.bytecode.ExceptionTable;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Opcode;
/**
* A data-flow analyzer that determines the type state of the stack and local
* variable table at every reachable instruction in a method. During analysis,
* bytecode verification is performed in a similar manner to that described
* in the JVM specification.
*
* Example:
*
*
* // Method to analyze
* public Object doSomething(int x) {
* Number n;
* if (x < 5) {
* n = new Double(0);
* } else {
* n = new Long(0);
* }
*
* return n;
* }
*
* // Which compiles to:
* // 0: iload_1
* // 1: iconst_5
* // 2: if_icmpge 17
* // 5: new #18; //class java/lang/Double
* // 8: dup
* // 9: dconst_0
* // 10: invokespecial #44; //Method java/lang/Double."":(D)V
* // 13: astore_2
* // 14: goto 26
* // 17: new #16; //class java/lang/Long
* // 20: dup
* // 21: lconst_1
* // 22: invokespecial #47; //Method java/lang/Long."":(J)V
* // 25: astore_2
* // 26: aload_2
* // 27: areturn
*
* public void analyzeIt(CtClass clazz, MethodInfo method) {
* Analyzer analyzer = new Analyzer();
* Frame[] frames = analyzer.analyze(clazz, method);
* frames[0].getLocal(0).getCtClass(); // returns clazz;
* frames[0].getLocal(1).getCtClass(); // returns java.lang.String
* frames[1].peek(); // returns Type.INTEGER
* frames[27].peek().getCtClass(); // returns java.lang.Number
* }
*
*
* @see FramePrinter
* @author Jason T. Greene
*/
public class Analyzer implements Opcode {
private final SubroutineScanner scanner = new SubroutineScanner();
private CtClass clazz;
private ExceptionInfo[] exceptions;
private Frame[] frames;
private Subroutine[] subroutines;
private static class ExceptionInfo {
private int end;
private int handler;
private int start;
private Type type;
private ExceptionInfo(int start, int end, int handler, Type type) {
this.start = start;
this.end = end;
this.handler = handler;
this.type = type;
}
}
/**
* Performs data-flow analysis on a method and returns an array, indexed by
* instruction position, containing the starting frame state of all reachable
* instructions. Non-reachable code, and illegal code offsets are represented
* as a null in the frame state array. This can be used to detect dead code.
*
* If the method does not contain code (it is either native or abstract), null
* is returned.
*
* @param clazz the declaring class of the method
* @param method the method to analyze
* @return an array, indexed by instruction position, of the starting frame state,
* or null if this method doesn't have code
* @throws BadBytecode if the bytecode does not comply with the JVM specification
*/
public Frame[] analyze(CtClass clazz, MethodInfo method) throws BadBytecode {
this.clazz = clazz;
CodeAttribute codeAttribute = method.getCodeAttribute();
// Native or Abstract
if (codeAttribute == null)
return null;
int maxLocals = codeAttribute.getMaxLocals();
int maxStack = codeAttribute.getMaxStack();
int codeLength = codeAttribute.getCodeLength();
CodeIterator iter = codeAttribute.iterator();
IntQueue queue = new IntQueue();
exceptions = buildExceptionInfo(method);
subroutines = scanner.scan(method);
Executor executor = new Executor(clazz.getClassPool(), method.getConstPool());
frames = new Frame[codeLength];
frames[iter.lookAhead()] = firstFrame(method, maxLocals, maxStack);
queue.add(iter.next());
while (!queue.isEmpty()) {
analyzeNextEntry(method, iter, queue, executor);
}
return frames;
}
/**
* Performs data-flow analysis on a method and returns an array, indexed by
* instruction position, containing the starting frame state of all reachable
* instructions. Non-reachable code, and illegal code offsets are represented
* as a null in the frame state array. This can be used to detect dead code.
*
* If the method does not contain code (it is either native or abstract), null
* is returned.
*
* @param method the method to analyze
* @return an array, indexed by instruction position, of the starting frame state,
* or null if this method doesn't have code
* @throws BadBytecode if the bytecode does not comply with the JVM specification
*/
public Frame[] analyze(CtMethod method) throws BadBytecode {
return analyze(method.getDeclaringClass(), method.getMethodInfo2());
}
private void analyzeNextEntry(MethodInfo method, CodeIterator iter,
IntQueue queue, Executor executor) throws BadBytecode {
int pos = queue.take();
iter.move(pos);
iter.next();
Frame frame = frames[pos].copy();
Subroutine subroutine = subroutines[pos];
try {
executor.execute(method, pos, iter, frame, subroutine);
} catch (RuntimeException e) {
throw new BadBytecode(e.getMessage() + "[pos = " + pos + "]", e);
}
int opcode = iter.byteAt(pos);
if (opcode == TABLESWITCH) {
mergeTableSwitch(queue, pos, iter, frame);
} else if (opcode == LOOKUPSWITCH) {
mergeLookupSwitch(queue, pos, iter, frame);
} else if (opcode == RET) {
mergeRet(queue, iter, pos, frame, subroutine);
} else if (Util.isJumpInstruction(opcode)) {
int target = Util.getJumpTarget(pos, iter);
if (Util.isJsr(opcode)) {
// Merge the state before the jsr into the next instruction
mergeJsr(queue, frames[pos], subroutines[target], pos, lookAhead(iter, pos));
} else if (! Util.isGoto(opcode)) {
merge(queue, frame, lookAhead(iter, pos));
}
merge(queue, frame, target);
} else if (opcode != ATHROW && ! Util.isReturn(opcode)) {
// Can advance to next instruction
merge(queue, frame, lookAhead(iter, pos));
}
// Merge all exceptions that are reachable from this instruction.
// The redundancy is intentional, since the state must be based
// on the current instruction frame.
mergeExceptionHandlers(queue, method, pos, frame);
}
private ExceptionInfo[] buildExceptionInfo(MethodInfo method) {
ConstPool constPool = method.getConstPool();
ClassPool classes = clazz.getClassPool();
ExceptionTable table = method.getCodeAttribute().getExceptionTable();
ExceptionInfo[] exceptions = new ExceptionInfo[table.size()];
for (int i = 0; i < table.size(); i++) {
int index = table.catchType(i);
Type type;
try {
type = index == 0 ? Type.THROWABLE : Type.get(classes.get(constPool.getClassInfo(index)));
} catch (NotFoundException e) {
throw new IllegalStateException(e.getMessage());
}
exceptions[i] = new ExceptionInfo(table.startPc(i), table.endPc(i), table.handlerPc(i), type);
}
return exceptions;
}
private Frame firstFrame(MethodInfo method, int maxLocals, int maxStack) {
int pos = 0;
Frame first = new Frame(maxLocals, maxStack);
if ((method.getAccessFlags() & AccessFlag.STATIC) == 0) {
first.setLocal(pos++, Type.get(clazz));
}
CtClass[] parameters;
try {
parameters = Descriptor.getParameterTypes(method.getDescriptor(), clazz.getClassPool());
} catch (NotFoundException e) {
throw new RuntimeException(e);
}
for (int i = 0; i < parameters.length; i++) {
Type type = zeroExtend(Type.get(parameters[i]));
first.setLocal(pos++, type);
if (type.getSize() == 2)
first.setLocal(pos++, Type.TOP);
}
return first;
}
private int getNext(CodeIterator iter, int of, int restore) throws BadBytecode {
iter.move(of);
iter.next();
int next = iter.lookAhead();
iter.move(restore);
iter.next();
return next;
}
private int lookAhead(CodeIterator iter, int pos) throws BadBytecode {
if (! iter.hasNext())
throw new BadBytecode("Execution falls off end! [pos = " + pos + "]");
return iter.lookAhead();
}
private void merge(IntQueue queue, Frame frame, int target) {
Frame old = frames[target];
boolean changed;
if (old == null) {
frames[target] = frame.copy();
changed = true;
} else {
changed = old.merge(frame);
}
if (changed) {
queue.add(target);
}
}
private void mergeExceptionHandlers(IntQueue queue, MethodInfo method, int pos, Frame frame) {
for (int i = 0; i < exceptions.length; i++) {
ExceptionInfo exception = exceptions[i];
// Start is inclusive, while end is exclusive!
if (pos >= exception.start && pos < exception.end) {
Frame newFrame = frame.copy();
newFrame.clearStack();
newFrame.push(exception.type);
merge(queue, newFrame, exception.handler);
}
}
}
private void mergeJsr(IntQueue queue, Frame frame, Subroutine sub, int pos, int next) throws BadBytecode {
if (sub == null)
throw new BadBytecode("No subroutine at jsr target! [pos = " + pos + "]");
Frame old = frames[next];
boolean changed = false;
if (old == null) {
old = frames[next] = frame.copy();
changed = true;
} else {
for (int i = 0; i < frame.localsLength(); i++) {
// Skip everything accessed by a subroutine, mergeRet must handle this
if (!sub.isAccessed(i)) {
Type oldType = old.getLocal(i);
Type newType = frame.getLocal(i);
if (oldType == null) {
old.setLocal(i, newType);
changed = true;
continue;
}
newType = oldType.merge(newType);
// Always set the type, in case a multi-type switched to a standard type.
old.setLocal(i, newType);
if (!newType.equals(oldType) || newType.popChanged())
changed = true;
}
}
}
if (! old.isJsrMerged()) {
old.setJsrMerged(true);
changed = true;
}
if (changed && old.isRetMerged())
queue.add(next);
}
private void mergeLookupSwitch(IntQueue queue, int pos, CodeIterator iter, Frame frame) throws BadBytecode {
int index = (pos & ~3) + 4;
// default
merge(queue, frame, pos + iter.s32bitAt(index));
int npairs = iter.s32bitAt(index += 4);
int end = npairs * 8 + (index += 4);
// skip "match"
for (index += 4; index < end; index += 8) {
int target = iter.s32bitAt(index) + pos;
merge(queue, frame, target);
}
}
private void mergeRet(IntQueue queue, CodeIterator iter, int pos, Frame frame, Subroutine subroutine) throws BadBytecode {
if (subroutine == null)
throw new BadBytecode("Ret on no subroutine! [pos = " + pos + "]");
Iterator callerIter = subroutine.callers().iterator();
while (callerIter.hasNext()) {
int caller = ((Integer) callerIter.next()).intValue();
int returnLoc = getNext(iter, caller, pos);
boolean changed = false;
Frame old = frames[returnLoc];
if (old == null) {
old = frames[returnLoc] = frame.copyStack();
changed = true;
} else {
changed = old.mergeStack(frame);
}
for (Iterator i = subroutine.accessed().iterator(); i.hasNext(); ) {
int index = ((Integer)i.next()).intValue();
Type oldType = old.getLocal(index);
Type newType = frame.getLocal(index);
if (oldType != newType) {
old.setLocal(index, newType);
changed = true;
}
}
if (! old.isRetMerged()) {
old.setRetMerged(true);
changed = true;
}
if (changed && old.isJsrMerged())
queue.add(returnLoc);
}
}
private void mergeTableSwitch(IntQueue queue, int pos, CodeIterator iter, Frame frame) throws BadBytecode {
// Skip 4 byte alignment padding
int index = (pos & ~3) + 4;
// default
merge(queue, frame, pos + iter.s32bitAt(index));
int low = iter.s32bitAt(index += 4);
int high = iter.s32bitAt(index += 4);
int end = (high - low + 1) * 4 + (index += 4);
// Offset table
for (; index < end; index += 4) {
int target = iter.s32bitAt(index) + pos;
merge(queue, frame, target);
}
}
private Type zeroExtend(Type type) {
if (type == Type.SHORT || type == Type.BYTE || type == Type.CHAR || type == Type.BOOLEAN)
return Type.INTEGER;
return type;
}
}