package eu.bibl.cfide.engine.compiler.builder.cfideimpl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
import eu.bibl.banalysis.asm.ClassNode;
import eu.bibl.banalysis.asm.desc.OpcodeInfo;
import eu.bibl.cfide.context.CFIDEContext;
import eu.bibl.cfide.engine.compiler.builder.BuilderException;
import eu.bibl.cfide.engine.compiler.builder.IBuilder;
import eu.bibl.cfide.engine.compiler.parser.ParserToken;
import eu.bibl.cfide.engine.compiler.parser.cfideimpl.tokens.member.ClassMemberToken;
import eu.bibl.cfide.engine.compiler.parser.cfideimpl.tokens.member.FieldMemberToken;
import eu.bibl.cfide.engine.compiler.parser.cfideimpl.tokens.member.MemberCloseToken;
import eu.bibl.cfide.engine.compiler.parser.cfideimpl.tokens.member.MethodMemberToken;
import eu.bibl.cfide.engine.compiler.parser.cfideimpl.tokens.using.UsingASMToken;
import eu.bibl.cfide.engine.compiler.parser.cfideimpl.tokens.using.UsingVerToken;
import eu.bibl.cfide.engine.util.StringArrayReader;
public class BytecodeClassNodeBuilder implements IBuilder<ClassNode[], List<ParserToken>>, Opcodes {
public static final Map<Integer, String> VERSION_TABLE = new HashMap<Integer, String>();
protected CFIDEContext context;
public BytecodeClassNodeBuilder(CFIDEContext context) {
this.context = context;
}
@SuppressWarnings("unchecked")
@Override
public ClassNode[] build(List<ParserToken> tokens) throws BuilderException {
List<ClassNode> cns = new ArrayList<ClassNode>();
Stack<Object> stack = new Stack<Object>();
for (int i = 0; i < tokens.size(); i++) {
ParserToken token = tokens.get(i);
if (token instanceof ClassMemberToken) {
ClassMemberToken cmt = (ClassMemberToken) token;
UsingASMToken tok1 = expect(tokens, i - 2, "Using ASM Declaration");
UsingVerToken tok2 = expect(tokens, i - 1, "Using Ver Declaration");
ClassNode cn;
try {
try {
Object o = stack.peek();
if (o instanceof ClassNode) {
} else if (o != null) {
throw new BuilderException("Invalid class declaration, cannot be inside a method.");
}
} catch (Exception e) {
}
cn = new ClassNode(tok1.getASMValue());
cn.version = tok2.getClassFileVersion();
cn.access = cmt.getAccess();
cn.name = cmt.getName();
cn.superName = cmt.getSuperName();
for (String s : cmt.getInterfaces()) {
cn.interfaces.add(s);
}
stack.push(cn);
cns.add(cn);
} catch (Exception e) {
throw new BuilderException(e);
}
stack.push(cn);
} else if (token instanceof FieldMemberToken) {
try {
FieldMemberToken fmt = (FieldMemberToken) token;
FieldNode fn = null;
if (fmt.getValue() == null)
fn = new FieldNode(fmt.getAccess(), fmt.getName(), fmt.getDesc(), null, null);
else
fn = new FieldNode(fmt.getAccess(), fmt.getName(), fmt.getDesc(), null, getValue(fmt.getValue(), fmt.getValueType()));
ClassNode cn = (ClassNode) stack.peek();
cn.fields.add(fn);
} catch (Exception e) {
throw new BuilderException(e);
}
} else if (token instanceof MethodMemberToken) {
Object o = stack.peek();
if ((o == null) || !(o instanceof ClassNode)) {
throw new BuilderException("Internal error: Stack.peek() returned: " + o);
}
ClassNode cn = (ClassNode) o;
MethodMemberToken mmt = (MethodMemberToken) token;
MethodNode mn = new MethodNode(mmt.getAccess(), mmt.getName(), mmt.getDesc(), null, mmt.getExceptionThrows());
mn.instructions.add(parseCode(new StringArrayReader(mmt.getCode()), mn));
// System.out.println(mn.instructions.size() + " code");
// AdvancedInstructionPrinter.consolePrint(new AdvancedInstructionPrinter(mn).getLines());
cn.methods.add(mn);
stack.push(mn);
} else if (token instanceof MemberCloseToken) {
stack.pop();
}
}
return cns.toArray(new ClassNode[cns.size()]);
}
protected LabelHandler labelHandler = new LabelHandler();
public InsnList parseCode(StringArrayReader cr, MethodNode m) throws BuilderException {
labelHandler.reset();
InsnList list = new InsnList();
// cache labels first
for (int i = 0; i < cr.size(); i++) {
String s = cr.read();
if (s.endsWith(":")) {// its a label
labelHandler.resolveLabel(s);
}
}
// int highestVar = 0;
cr.reset();
while (cr.canReadNext()) {
String s = cr.read();
int val = isOpcode(s);
if (val != -1) {
AbstractInsnNode ain = resolveInstruction(val, cr);
if (ain != null) {
// if (ain instanceof VarInsnNode) {
// VarInsnNode vin = (VarInsnNode) ain;
// int v = vin.var;
// if (v > highestVar)
// highestVar = v;
// }
list.add(ain);
}
} else if (s.endsWith(":")) {// its a label
LabelNode ln = labelHandler.retreiveLabel(s);
list.add(ln);
} else /*
* if (s.endsWith(":") && s.toUpperCase().equals("TRYCATCH:")) {
* LabelNode start = labelHandler.retreiveLabel(expectCode(cr, "trycatchblocknode start label."));
* LabelNode end = labelHandler.retreiveLabel(expectCode(cr, "trycatchblocknode start label."));
* LabelNode handler = labelHandler.retreiveLabel(expectCode(cr, "trycatchblocknode start label."));
* String exc = expectCode(cr, "exception type.");
* TryCatchBlockNode tcbn = new TryCatchBlockNode(start, end, handler, exc);
* m.tryCatchBlocks.add(tcbn);
* } else
*/
if (s.startsWith("<") && s.endsWith(">")) {
AbstractInsnNode ain = parseCodeMetadata(m, s);
if (ain != null)
list.add(ain);
}
}
// m.maxLocals = highestVar;
return list;
}
@SuppressWarnings("unchecked")
protected AbstractInsnNode parseCodeMetadata(MethodNode m, String meta) throws BuilderException {
meta = meta.substring(1);// get rid of <
meta = meta.substring(0, meta.length() - 1);// get rid of >
String[] split = meta.split(":", 2);
if (split.length != 2)
throw new BuilderException("Invalid meta data: " + meta + ".");
String key = split[0].toUpperCase();
switch (key) {
case "LINE": {
try {
int line = Integer.parseInt(split[1]);
return new LineNumberNode(line, labelHandler.getLastLabel());
} catch (NumberFormatException e) {
throw new BuilderException("Invalid line number: " + split[1] + ".");
}
}
case "TRYCATCH": {
String[] parts = split[1].trim().split(" ");
if (parts.length != 4)
throw new BuilderException("Invalid trycatch: " + split[1].trim());
LabelNode start = labelHandler.retreiveLabel(parts[0]);
LabelNode end = labelHandler.retreiveLabel(parts[1]);
LabelNode handler = labelHandler.retreiveLabel(parts[2]);
String exc = parts[3];
TryCatchBlockNode tcbn = new TryCatchBlockNode(start, end, handler, exc);
m.tryCatchBlocks.add(tcbn);
return null;
}
case "LOCALVAR": {
// LocalVariableNode lvn = new LocalVariableNode(arg0, arg1, arg2, arg3, arg4, arg5)
return null;
}
case "VISANNO": {
}
default:
throw new BuilderException("Unknown metdata key: " + key + " <" + meta + ">.");
}
}
protected AbstractInsnNode resolveInstruction(int opcode, StringArrayReader cr) throws BuilderException {
switch (opcode) {
case INVOKEDYNAMIC:
case INVOKEINTERFACE:
case INVOKESPECIAL:
case INVOKESTATIC:
case INVOKEVIRTUAL: {
String operand = expectCode(cr, "method call operand.");
String[] s1 = operand.split("\\.");
if (s1.length != 2)
throw new BuilderException("Invalid method call operand: " + operand);
String[] s2 = s1[1].split(":");
if (s2.length != 2)
throw new BuilderException("Invalid method call operand: " + operand);
String className = s1[0];
String methodName = s2[0];
String methodDesc = s2[1];
MethodInsnNode min = new MethodInsnNode(opcode, className, methodName, methodDesc);
return min;
}
case GETFIELD:
case GETSTATIC:
case PUTFIELD:
case PUTSTATIC: {
String operand = expectCode(cr, "field operation operand.");
String[] s1 = operand.split("\\.");
if (s1.length != 2)
throw new BuilderException("Invalid field operation operand: " + operand);
String[] s2 = s1[1].split(":");
if (s2.length != 2)
throw new BuilderException("Invalid field operation operand: " + operand);
String className = s1[0];
String fieldName = s2[0];
String fieldDesc = s2[1];
FieldInsnNode fin = new FieldInsnNode(opcode, className, fieldName, fieldDesc);
return fin;
}
case ALOAD:
case ILOAD:
case DLOAD:
case FLOAD:
case LLOAD:
case ASTORE:
case ISTORE:
case DSTORE:
case FSTORE:
case LSTORE:
case RET: {
String operand = expectCode(cr, "index load/store operand.");
try {
int var = Integer.parseInt(operand);
VarInsnNode vin = new VarInsnNode(opcode, var);
return vin;
} catch (NumberFormatException e) {
throw new BuilderException("Invalid load/store index: " + operand);
}
}
case NEW:
case ANEWARRAY:
case CHECKCAST:
case INSTANCEOF: {
String operand = expectCode(cr, "class name as operand.");
TypeInsnNode tin = new TypeInsnNode(opcode, operand);
return tin;
}
case BIPUSH:
case SIPUSH:
case NEWARRAY: {
String operand = expectCode(cr, "int instruction operand.");
try {
int intOperand = Integer.parseInt(operand);
IntInsnNode iin = new IntInsnNode(opcode, intOperand);
return iin;
} catch (NumberFormatException e) {
throw new BuilderException("Invalid int instruction operand value.");
}
}
case IFEQ:
case IFNE:
case IFLT:
case IFGE:
case IFGT:
case IFLE:
case IF_ICMPEQ:
case IF_ICMPNE:
case IF_ICMPLT:
case IF_ICMPGE:
case IF_ICMPGT:
case IF_ICMPLE:
case IF_ACMPEQ:
case IF_ACMPNE:
case GOTO:
case JSR:
case IFNULL:
case IFNONNULL: {
String operand = expectCode(cr, "jump label.");
LabelNode ln = labelHandler.retreiveLabel(operand);
if (ln == null) {
throw new BuilderException("Invalid label to jump to: " + operand);
}
JumpInsnNode jin = new JumpInsnNode(opcode, ln);
return jin;
}
case LDC: {
String val = expectCode(cr, "ldc value.");
String type = expectCode(cr, "ldc value type.");
LdcInsnNode lin = new LdcInsnNode(getValue(val, type));
return lin;
}
default: {
InsnNode in = new InsnNode(opcode);
// System.out.println("unknown opcode: " + OpcodeInfo.OPCODES.get(opcode));
return in;
}
}
// throw new BuilderException("Unknown opcode: " + OpcodeInfo.OPCODES.get(opcode));
}
protected int isOpcode(String s) {
if (!OpcodeInfo.OPCODE_NAMES.containsKey(s = s.toUpperCase()))
return -1;
return OpcodeInfo.OPCODE_NAMES.get(s);
}
protected Object getValue(String value, String valueType) throws BuilderException {
switch (valueType) {
case "(java.lang.String)":
if ((value.startsWith("\"") && value.endsWith("\""))) {
value = value.substring(1);
value = value.substring(0, value.length() - 1);
return value;
}
throw new BuilderException("String default value fields must be surrounded in \"\"s : " + value);
case "(java.lang.Integer)":
try {
int i = Integer.parseInt(value);
return i;
} catch (NumberFormatException e) {
throw new BuilderException("Invalid integer value: " + value);
}
case "(java.lang.Double)":
try {
double d = Double.parseDouble(value);
return d;
} catch (NumberFormatException e) {
throw new BuilderException("Invalid double value: " + value);
}
case "(java.lang.Long)":
try {
long l = Long.parseLong(value);
return l;
} catch (NumberFormatException e) {
throw new BuilderException("Invalid double value: " + value);
}
case "(java.lang.Float)":
try {
float f = Float.parseFloat(value);
return f;
} catch (NumberFormatException e) {
throw new BuilderException("Invalid double value: " + value);
}
case "(org.objectweb.asm.Type)":
return org.objectweb.asm.Type.getType(value);
default:
throw new BuilderException("Unknown field default value type: " + valueType);
}
}
protected <T extends ParserToken> T expect(List<ParserToken> tokens, int index, String e1) throws BuilderException {
try {
if (index < 0)
throw new BuilderException("Expected token but index was " + index);
@SuppressWarnings("unchecked")
T token = (T) tokens.get(index);
return token;
} catch (RuntimeException e) {
throw new BuilderException("Expecting " + e1, e);
}
}
protected String expectCode(StringArrayReader cr, String e1) throws BuilderException {
try {
if (!cr.valid())
throw new BuilderException("Expected token but index was " + cr.index());
return cr.read();
} catch (RuntimeException e) {
throw new BuilderException("Expecting " + e1, e);
}
}
}