package edu.umd.cs.findbugs;
import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.CheckForNull;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.ClassFormatException;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.classfile.StackMapTable;
import org.apache.bcel.classfile.StackMapTableEntry;
import org.apache.bcel.classfile.StackMapType;
import org.apache.bcel.generic.Type;
import edu.umd.cs.findbugs.OpcodeStack.Item;
import edu.umd.cs.findbugs.OpcodeStack.JumpInfo;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.IAnalysisCache;
import edu.umd.cs.findbugs.classfile.MethodDescriptor;
public class StackMapAnalyzer {
public static class StackMapAnalysisFactory extends edu.umd.cs.findbugs.classfile.engine.bcel.AnalysisFactory<JumpInfoFromStackMap> {
public StackMapAnalysisFactory() {
super("Jump info for opcode stack from stack map analysis", JumpInfoFromStackMap.class);
}
@Override
public JumpInfoFromStackMap analyze(IAnalysisCache analysisCache, MethodDescriptor descriptor) {
return getFromStackMap( analysisCache, descriptor);
}
}
static class JumpInfoFromStackMap extends JumpInfo {
JumpInfoFromStackMap(Map<Integer, List<Item>> jumpEntries, Map<Integer, List<Item>> jumpStackEntries, BitSet jumpEntryLocations) {
super(jumpEntries, jumpStackEntries, jumpEntryLocations);
}
}
static final boolean DEBUG = false;
enum StackFrameType {
SAME_FRAME, SAME_LOCALS_1_STACK_ITEM_FRAME, CHOP_FRAME, APPEND_FRAME, FULL_FRAME;
static StackFrameType get(int frame_type) {
if (frame_type >= Constants.SAME_FRAME && frame_type <= Constants.SAME_FRAME_MAX) {
return SAME_FRAME;
} else if (frame_type >= Constants.SAME_LOCALS_1_STACK_ITEM_FRAME
&& frame_type <= Constants.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
return SAME_LOCALS_1_STACK_ITEM_FRAME;
} else if (frame_type == Constants.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
return SAME_LOCALS_1_STACK_ITEM_FRAME;
} else if (frame_type >= Constants.CHOP_FRAME && frame_type <= Constants.CHOP_FRAME_MAX) {
return CHOP_FRAME;
} else if (frame_type == Constants.SAME_FRAME_EXTENDED) {
return SAME_FRAME;
} else if (frame_type >= Constants.APPEND_FRAME && frame_type <= Constants.APPEND_FRAME_MAX) {
return APPEND_FRAME;
} else if (frame_type == Constants.FULL_FRAME) {
return FULL_FRAME;
} else {
throw new ClassFormatException("Invalid frame type : " + frame_type);
}
}
}
static @CheckForNull StackMapTable getStackMapTable(Code code) {
for(Attribute a : code.getAttributes()) {
if (a instanceof StackMapTable) {
return (StackMapTable) a;
}
}
return null;
}
static List<Item> getInitialLocals(MethodDescriptor descriptor) {
List<Item> locals = new ArrayList<Item>();
Type[] argTypes = Type.getArgumentTypes(descriptor.getSignature());
int reg = 0;
if (!descriptor.isStatic()) {
Item it = Item.typeOnly("L" + descriptor.getSlashedClassName() + ";");
locals.add(it);
reg += it.getSize();
}
for (Type argType : argTypes) {
Item it = Item.typeOnly(argType.getSignature());
locals.add(it);
reg += it.getSize();
if (it.usesTwoSlots()) {
locals.add(null);
}
}
return locals;
}
static final @CheckForNull Field frame_type_field;
static {
Field f;
try {
f = AccessController.doPrivileged(new PrivilegedAction<Field>() {
@Override
public Field run() {
Class<StackMapTableEntry> c = StackMapTableEntry.class;
Field result;
try {
result = c.getDeclaredField("frame_type");
result.setAccessible(true);
return result;
} catch (NoSuchFieldException e) {
throw new AssertionError("frame_type field doesn't exist");
} catch (SecurityException e) {
return null;
}
}
});
} catch (Exception e) {
AnalysisContext.logError("Unable to create frame_type accessor",e );
f = null;
}
if (DEBUG) {
System.out.println("Frame type field is null:" + (f == null));
}
frame_type_field = f;
}
static int getFrameType(StackMapTableEntry e) {
if (frame_type_field == null) {
return -1;
}
try {
return (Integer) frame_type_field.get(e);
} catch (IllegalArgumentException e1) {
return -1;
} catch (IllegalAccessException e1) {
return -1;
}
}
static private @CheckForNull JumpInfoFromStackMap getFromStackMap(IAnalysisCache analysisCache, MethodDescriptor descriptor) {
if (frame_type_field == null) {
return null;
}
Method method;
try {
method = analysisCache.getMethodAnalysis(Method.class, descriptor);
} catch (CheckedAnalysisException e1) {
analysisCache.getErrorLogger().logError("Unable to get method for " + descriptor, e1);
return null;
}
Code code = method.getCode();
if (code == null) {
return null;
}
StackMapTable stackMapTable = getStackMapTable(code);
if (stackMapTable == null) {
return null;
}
Map<Integer, List<Item>> jumpEntries = new HashMap<Integer, List<Item>>();
Map<Integer, List<Item>> jumpStackEntries = new HashMap<Integer, List<Item>>();
List<Item> locals = getInitialLocals(descriptor);
List<Item> stack = new ArrayList<Item>();
BitSet jumpEntryLocations = new BitSet();
if (DEBUG) {
System.out.println(descriptor);
System.out.println(locals);
}
int pc = 0;
for(StackMapTableEntry e : stackMapTable.getStackMapTable()) {
pc += e.getByteCodeOffsetDelta();
int rawFrameType = getFrameType(e);
StackFrameType stackFrameType = StackFrameType.get(rawFrameType);
switch (stackFrameType) {
case SAME_FRAME:
stack.clear();
break;
case SAME_LOCALS_1_STACK_ITEM_FRAME:
stack.clear();
addStack(stack, e.getTypesOfStackItems());
break;
case CHOP_FRAME :
stack.clear();
int n = Constants.CHOP_FRAME_MAX+1-rawFrameType;
for(int i = 0; i < n; i++) {
Item it = locals.remove(locals.size()-1);
if (it == null) {
it = locals.remove(locals.size()-1);
assert it.usesTwoSlots();
}
}
break;
case APPEND_FRAME:
stack.clear();
addLocals(locals, e.getTypesOfLocals());
break;
case FULL_FRAME:
stack.clear();
locals.clear();
addLocals(locals, e.getTypesOfLocals());
addStack(stack, e.getTypesOfStackItems());
break;
}
if (DEBUG) {
System.out.printf("%4d %2d %2d %12s %s%n",
pc, e.getNumberOfLocals(), e.getNumberOfStackItems(), stackFrameType, e);
System.out.printf(" %s :: %s%n", stack, locals);
}
if (pc > 0) {
jumpEntries.put(pc, new ArrayList<Item>(locals));
if (!stack.isEmpty()) {
jumpStackEntries.put(pc, new ArrayList<Item>(stack));
}
jumpEntryLocations.set(pc);
}
pc++;
}
if (DEBUG) {
System.out.println("\n");
}
return new JumpInfoFromStackMap(jumpEntries, jumpStackEntries, jumpEntryLocations);
}
static private Item getItem(StackMapType t) {
switch (t.getType()) {
case Constants.ITEM_Double:
return Item.typeOnly("D");
case Constants.ITEM_Float:
return Item.typeOnly("F");
case Constants.ITEM_Integer:
return Item.typeOnly("I");
case Constants.ITEM_Long:
return Item.typeOnly("J");
case Constants.ITEM_Bogus:
case Constants.ITEM_NewObject:
return Item.typeOnly("Ljava/lang/Object;");
case Constants.ITEM_Null:
Item it = new Item();
it.setSpecialKind(Item.TYPE_ONLY);
return it;
case Constants.ITEM_InitObject:
return Item.typeOnly("Ljava/lang/Object;");
case Constants.ITEM_Object:
int index = t.getIndex();
ConstantClass c = (ConstantClass) t.getConstantPool().getConstant(index);
String name = c.getBytes(t.getConstantPool());
if (name.charAt(0) != '[') {
name = "L" + name + ";";
}
return Item.typeOnly(name);
default:
throw new IllegalArgumentException("Bad item type: " + t.getType());
}
}
static private void addLocals(List<Item> lst, StackMapType[] typesOfStackItems) {
for(StackMapType t : typesOfStackItems) {
Item item = getItem(t);
lst.add(item);
if (item.usesTwoSlots()) {
lst.add(null);
}
}
}
static private void addStack(List<Item> lst, StackMapType[] typesOfStackItems) {
for(StackMapType t : typesOfStackItems) {
Item item = getItem(t);
lst.add(item);
}
}
}