package com.wildex999.patcher; import java.io.DataInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StreamTokenizer; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import com.google.common.primitives.Ints; import scala.actors.threadpool.Arrays; import scala.collection.mutable.HashTable; //Parses the output of TraceClassVisitor to populate a ClassWriter public class ASMClassParser { public class Token { public String str; public int line; } protected List<Token> tokens; protected int currentToken; public int classVersion = 0; protected ClassWriter cl; public ClassWriter parseClass(String classData) throws Exception { cl = new ClassWriter(0); //Generate a list of tokens tokens = new ArrayList<Token>(); int tokenStart = 0; int curPos = 0; boolean inQuote = false; boolean escaped = false; boolean inToken = false; int currentLine = 0; while(curPos < classData.length()) { char curChar = classData.charAt(curPos); curPos++; //If inside quotes, ignore whitespace(TODO: Don't ignore newline? Should be no newline inside quotes tho, as Java doesn't allow it?) if(inQuote) { if(curChar == '\\') escaped = !escaped; else if(curChar == '"' && !escaped) inQuote = false; continue; } //Check for whitespace if(Character.isWhitespace(curChar)) { if(inToken) { Token newToken = new Token(); newToken.str = classData.substring(tokenStart, curPos-1); newToken.line = currentLine; tokens.add(newToken); //System.out.println("["+tokens.get(tokens.size()-1)+"]"); inToken = false; } //We want to tokenize newline as it might be useful if(curChar == '\n') { Token newToken = new Token(); newToken.str = "\n"; newToken.line = currentLine; tokens.add(newToken); currentLine++; //System.out.println("["+tokens.get(tokens.size()-1)+"]"); } continue; } //Not whitespace if(inToken == false) { inToken = true; tokenStart = curPos-1; } //Enter quotes if(curChar == '"') inQuote = true; } //Write any token that reached until the end if(inToken) { Token newToken = new Token(); newToken.str = classData.substring(tokenStart); newToken.line = currentLine; tokens.add(newToken); } currentToken = -1; //first nextToken will get the first token System.out.println("Tokens: " + tokens.size()); //Parse Class header parseClassVersion(); skipLine(); //Bypass access flags comment line parseClassHeader(); String value; String signature = null; while(true) { value = nextToken(); if(value == null) throw new Exception(getCurrentTokenLine() + ": Reached end of class data without complete parse, Patch might be corrupt!"); //Skip empty lines if(value.equals("\n")) continue; //Skip // if(value.startsWith("//")) { //Check for pre-method generics signature value = nextToken(); if(value.equals("signature")) signature = nextToken(); else if(value.equals("compiled")) { currentToken++; //Skip the 'from:' cl.visitSource(nextToken(), null); } else currentToken--; skipLine(); continue; } //Handle Class Annotation if(value.startsWith("@")) { int valuesOffset = value.indexOf("("); String valuesToken = ""; if(valuesOffset != -1) { valuesToken = value.substring(valuesOffset); valuesToken += getLine(); //There is whitespace inside the (), so we have to combine the tokens } String desc = value.substring(1, valuesOffset); AnnotationVisitor av = cl.visitAnnotation(desc, true); parseAnnotation(av, valuesToken); av.visitEnd(); } if(value.equals("}")) break; currentToken--; //Parsers use nextToken, so go back from our peek //The first thing before either a Method or a Field is the access flags int access = parseAccessFlags(); //Check if it's a method, field etc. value = nextToken(); if(value == null) continue; //Let it reach the == null throw if(value.equals("INNERCLASS")) parseInnerClass(access); else if(value.contains("(")) //All methods have parentheses. while Fields do not parseMethod(access, signature); else parseField(access, signature); signature = null; } //Done writing class cl.visitEnd(); return cl; } protected String nextToken() { if(currentToken >= tokens.size()-1) return null; return tokens.get(++currentToken).str; } protected String getCurrentToken() { if(currentToken < 0 || currentToken >= tokens.size()) return null; return tokens.get(currentToken).str; } protected String previousToken() { if(currentToken <= 0 || currentToken >= tokens.size()+1) return null; return tokens.get(--currentToken).str; } protected int getCurrentTokenLine() { if(currentToken < 0 || currentToken >= tokens.size()) return -1; return tokens.get(currentToken).line; } //Skip all tokes on this line(Go to next newline token) protected void skipLine() { String value; do { value = nextToken(); } while(value != null && !value.equals("\n")); } //Get and combine all the remaining tokens until it finds a "\n" which it will include at the end protected String getLine() { StringBuilder builder = new StringBuilder(); String value; do { value = nextToken(); builder.append(value); } while(value != null && !value.equals("\n")); return builder.toString(); } //Parse the class version, kept in a comment at the top //Will fail if it doesn't find the version, or if the version is unsuported protected void parseClassVersion() throws Exception { String noMatchError = "Found no class version at beginning of class!"; String value; if(!nextToken().equals("//")) throw new Exception(noMatchError); if(!nextToken().equals("class")) throw new Exception(noMatchError); if(!nextToken().equals("version")) throw new Exception(noMatchError); value = nextToken(); int version = (int)Float.parseFloat(value); switch (version) { case 50: classVersion = Opcodes.V1_6; System.out.println("V1_6"); break; case 51: classVersion = Opcodes.V1_7; break; case 52: classVersion = Opcodes.V1_8; break; default: throw new Exception("Unsuported class version: " + version); } //Move to next line and Verify nextToken(); value = nextToken(); if(!value.equals("\n")) throw new Exception("Error while parsing class version, expected \n, got: " + value); } //Parse the class header(name, generics, super, implements etc.) protected void parseClassHeader() throws Exception { int access = parseAccessFlags(); if(access == 0) throw new Exception("Error: Got zero access modifier while parsing class header!"); if(!nextToken().equals("class")) throw new Exception("Error: Did not find class identifier while parsing class header!"); String className = nextToken(); String superClassName = null; List<String> impl = new ArrayList<String>(); String value; //Extending, implementing or done? boolean implement = false; while(true) { value = nextToken(); if(implement) { if(value.equals("{")) //Done reading implements break; impl.add(value); continue; } if(value.equals("implements")) implement = true; else if(value.equals("extends")) superClassName = nextToken(); else if(value.equals("{")) //Done reading header info break; } //Write header /*if(superClassName != null) { access += Opcodes.ACC_SUPER; System.out.println("super access: " + superClassName); } else*/ access += Opcodes.ACC_SUPER; //Seems this is always set after Java 1.1? if(superClassName == null) superClassName = "java/lang/Object"; System.out.println("Writing class header:" + "\nAccess: " + Integer.toHexString(access).toUpperCase() + "\nName: " + className + "\nSuper class: " + superClassName + "\nImplements: " + impl); String[] implNames = null; if(impl.size() != 0) implNames = impl.toArray(new String[impl.size()]); cl.visit(classVersion, access, className, null, superClassName, implNames); } //Scan and build up access flags(public, private, static, final etc.) for class, method, field and parameter protected int parseAccessFlags() throws Exception { String current; int access = 0; //They share value(Mutually exclusive, one is for method, one is for field) boolean gotTransient = false; boolean gotVarargs = false; while(true) { current = nextToken(); if(current == null) return access; //A lot of these actually are not a keyword, but something the compiler adds, just keep it here for now if(current.equals("public")) access += Opcodes.ACC_PUBLIC; else if(current.equals("private")) access += Opcodes.ACC_PRIVATE; else if(current.equals("protected")) access += Opcodes.ACC_PROTECTED; else if(current.equals("static")) access += Opcodes.ACC_STATIC; else if(current.equals("final")) access += Opcodes.ACC_FINAL; /*else if(current.equals("super")) access += Opcodes.ACC_SUPER;*/ //This one is added on newer compilers whenever a class has a super class else if(current.equals("synchronized")) access += Opcodes.ACC_SYNCHRONIZED; else if(current.equals("volatile")) access += Opcodes.ACC_VOLATILE; else if(current.equals("bridge")) access += Opcodes.ACC_BRIDGE; else if(current.equals("varargs")) { if(!gotTransient) { access += Opcodes.ACC_VARARGS; gotVarargs = true; } } else if(current.equals("transient")) { if(!gotVarargs) { access += Opcodes.ACC_TRANSIENT; gotTransient = true; } } else if(current.equals("native")) access += Opcodes.ACC_NATIVE; else if(current.equals("interface")) access += Opcodes.ACC_INTERFACE; else if(current.equals("abstract")) access += Opcodes.ACC_ABSTRACT; else if(current.equals("strict")) access += Opcodes.ACC_STRICT; else if(current.equals("synthetic")) access += Opcodes.ACC_SYNTHETIC; else if(current.equals("annotation")) access += Opcodes.ACC_ANNOTATION; else if(current.equals("enum")) access += Opcodes.ACC_ENUM; else if(current.equals("mandated")) access += Opcodes.ACC_MANDATED; else { //Move back from our peek currentToken--; //System.out.println("Access: " + Integer.toHexString(access).toUpperCase()); return access; } } } //Parse a inner class declaration protected void parseInnerClass(int access) throws Exception { String name = nextToken(); String outerName = nextToken(); String innerName = nextToken(); if(outerName.equals("null")) outerName = null; if(innerName.equals("null")) innerName = null; cl.visitInnerClass(name, outerName, innerName, access); } //Parse a Field. Expects the currentToken to be the first protected void parseField(int access, String signature) throws Exception { String value = getCurrentToken(); String desc = value; String name = nextToken(); Object fieldValue = null; //Check for preset value if(nextToken().equals("=")) fieldValue = parseValue(nextToken()).value; else currentToken--; //Go back from our peek FieldVisitor field = cl.visitField(access, name, desc, signature, fieldValue); //System.out.println("Field: " + Integer.toHexString(access).toUpperCase() + " | " + name + " | " + desc + " | " + fieldValue); if(!nextToken().equals("\n")) throw new Exception(getCurrentTokenLine() + ": Error: Expected newline while parsing field: " + name + "! Got: " + getCurrentToken()); //Read any annotations for the field while(true) { value = nextToken(); if(value.startsWith("@")) { int valuesOffset = value.indexOf("("); String valuesToken = ""; if(valuesOffset != -1) { valuesToken = value.substring(valuesOffset); valuesToken += getLine(); //There is whitespace inside the (), so we have to combine the tokens } desc = value.substring(1, valuesOffset); AnnotationVisitor av = field.visitAnnotation(desc, true); parseAnnotation(av, valuesToken); av.visitEnd(); } else //End of field if we reach anything else { currentToken--; break; } } field.visitEnd(); } //Parse the annotation values //Expects the annotationvisitior to already be created with desc, and for token to be the values, including the () protected void parseAnnotation(AnnotationVisitor anno, String token) throws Exception { if(token == null || token.length() == 0 || token.length() == 2) return; //Nothing to parse //System.out.println("Parse annotation: " + token); //TODO: Handle escaped quotes properly if(token.contains("\\\"")) throw new Exception(getCurrentTokenLine() + ": Parser currently does not handle escaped quotes in annotations! Bug me about this -_-(Unless you are on an old version"); int offset = 1; //Start after the ( int index; while(true) { String valueName; String value; //Get value name index = token.indexOf("=", offset); valueName = token.substring(offset, index); //Get value offset = index+1; char tokenChar = token.charAt(offset); if(tokenChar == '"') //String value { index = token.indexOf('"', offset+1); value = token.substring(offset+1, index); anno.visit(valueName, value); //System.out.println("AnnotationStr: " + valueName + "=" + value); offset = index+1; } else if(tokenChar == '{') //Array value throw new Exception(getCurrentTokenLine() + ": Parser currently does not handle arrays in annotations!"); else if(tokenChar == 'L') //Enum or Object Type { //Start with getting the Type name index = token.indexOf(";", offset); value = token.substring(offset, index+1); offset = index+1; //If we have a '.' after that, it's an Enum if(token.charAt(offset) == '.') { //Find length int index1 = token.indexOf(",", offset); int index2 = token.indexOf(")", offset); if(index1 < index2 && index1 != -1) index = index1; else index = index2; String entryName = token.substring(offset+1, index); anno.visitEnum(valueName, value, entryName); //System.out.println("AnnotationEnum: " + valueName + "=" + value + "." + entryName); offset = index; } else { anno.visit(valueName, org.objectweb.asm.Type.getType(value)); //System.out.println("AnnotationObj: " + valueName + "=" + value); } } else { //Check for Boolean and Number values index = token.indexOf(",", offset); if(index == -1) value = token.substring(offset, token.length()-1); else value = token.substring(offset, index); ValueType parsedValue = parseValue(value); anno.visit(valueName, parsedValue.value); //System.out.println("AnnotationBoolNr: " + valueName + "=" + parsedValue.value); offset = index; } tokenChar = token.charAt(offset); if(tokenChar == ',') //Continue to next value { offset ++; continue; } else if(tokenChar == ')') //Done break; throw new Exception(getCurrentTokenLine() + ": Error while parsing Annotation: Expected ',' or ')', got: " + tokenChar); } //TODO: If we get "// invisible" before end of line, the annotation is invisible? } //Get the value parsed from a string protected ValueType parseValue(String token) throws Exception { ValueType val = new ValueType(); if(token.startsWith("\"")) { val.type = ValueType.Type.TString; val.value = token.substring(1, token.length()-1); return val; } if(token.equals("true")) { val.type = ValueType.Type.TBoolean; val.value = Boolean.TRUE; return val; } if(token.equals("false")) { val.type = ValueType.Type.TBoolean; val.value = Boolean.FALSE; return val; } if(token.startsWith("L")) //Type { String objType = token.substring(0, token.indexOf(".")); //We don't want the '.class' at the end val.type = ValueType.Type.TType; val.value = org.objectweb.asm.Type.getType(objType); return val; } //Number value int index = token.indexOf("F"); if(index != -1) { val.type = ValueType.Type.TFloat; val.value = new Float(token.substring(0, index)); return val; } index = token.indexOf("D"); if(index != -1) { val.type = ValueType.Type.TDouble; val.value = new Double(token.substring(0, index)); return val; } index = token.indexOf("L"); if(index != -1) { val.type = ValueType.Type.TLong; val.value = new Long(token.substring(0, index)); return val; } if(token.startsWith("(char)")) { val.type = ValueType.Type.TChar; val.value = new Character(token.charAt(6)); return val; } if(token.startsWith("(short)")) { val.type = ValueType.Type.TShort; val.value = new Short(token.substring(7)); return val; } if(token.startsWith("(byte)")) { val.type = ValueType.Type.TByte; val.value = new Byte(token.substring(6)); return val; } try { val.type = ValueType.Type.TInteger; val.value = new Integer(token); return val; } catch (Exception e) {} throw new Exception(getCurrentTokenLine() + ": Could not parse value: " + token); } //Parse a method. Expect the currentToken to be the first protected void parseMethod(int access, String signature) throws Exception { String value = getCurrentToken(); String methodName; String desc; String[] exceptionsArray = null; Map<String, Label> labels = new HashMap<String, Label>(); int index = value.indexOf("("); methodName = value.substring(0, index); desc = value.substring(index); //System.out.println("Parsing method: " + methodName + " Desc: " + desc); value = nextToken(); if(value.equals("throws")) { List<String> exceptions = new ArrayList<String>(); while(true) { value = nextToken(); if(value.equals("\n")) break; exceptions.add(value); } exceptionsArray = exceptions.toArray(new String[exceptions.size()]); } else if(!value.equals("\n")) throw new Exception(getCurrentTokenLine() + ": Error while parsing method: Expected \\n on method header, got: " + value); MethodVisitor method = cl.visitMethod(access, methodName, desc, signature, exceptionsArray); //TODO: Generics and exceptions //Read any annotations for the method while(true) { value = nextToken(); if(value.startsWith("@")) { int valuesOffset = value.indexOf("("); String valuesToken = ""; if(valuesOffset != -1) { valuesToken = value.substring(valuesOffset); valuesToken += getLine(); //There is whitespace inside the (), so we have to combine the tokens } desc = value.substring(1, valuesOffset); AnnotationVisitor av = method.visitAnnotation(desc, true); parseAnnotation(av, valuesToken); av.visitEnd(); } else //End of method header if we reach anything else { currentToken--; break; } } //Parse method instructions boolean lineData = false; String insnSignature = null; while(true) { value = nextToken(); if(value.equals("}")) { currentToken--; //Allow main loop to detect the class end break; } else if(value.startsWith("//")) { //Check for pre-method generics signature value = nextToken(); if(value.equals("signature")) insnSignature = nextToken(); else currentToken--; skipLine(); continue; } if(!value.equals("\n")) { lineData = true; currentToken--; parseInstruction(method, labels, insnSignature); insnSignature = null; //Reset signature after known instruction } else { if(lineData == false) break; //End of method else lineData = false; } } method.visitEnd(); } //Parse a instruction, consisting for a single line with tokens protected void parseInstruction(MethodVisitor method, Map<String, Label> labels, String signature) throws Exception { String value = nextToken(); //System.out.println("Parsing instruction: " + value); //Ops that have no arguments final String[] insn = {"NOP", "ACONST_NULL", "ICONST_M1", "ICONST_0", "ICONST_1", "ICONST_2", "ICONST_3", "ICONST_4", "ICONST_5", "LCONST_0", "LCONST_1", "FCONST_0", "FCONST_1", "FCONST_2", "DCONST_0", "DCONST_1", "IALOAD", "LALOAD", "FALOAD", "DALOAD", "AALOAD", "BALOAD", "CALOAD", "SALOAD", "IASTORE", "LASTORE", "FASTORE", "DASTORE", "AASTORE", "BASTORE", "CASTORE", "SASTORE", "POP", "POP2", "DUP", "DUP_X1", "DUP_X2", "DUP2", "DUP2_X1", "DUP2_X2", "SWAP", "IADD", "LADD", "FADD", "DADD", "ISUB", "LSUB", "FSUB", "DSUB", "IMUL", "LMUL", "FMUL", "DMUL", "IDIV", "LDIV", "FDIV", "DDIV", "IREM", "LREM", "FREM", "DREM", "INEG", "LNEG", "FNEG", "DNEG", "ISHL", "LSHL", "ISHR", "LSHR", "IUSHR", "LUSHR", "IAND", "LAND", "IOR", "LOR", "IXOR", "LXOR", "I2L", "I2F", "I2D", "L2I", "L2F", "L2D", "F2I", "F2L", "F2D", "D2I", "D2L", "D2F", "I2B", "I2C", "I2S", "LCMP", "FCMPL", "FCMPG", "DCMPL", "DCMPG", "IRETURN", "LRETURN", "FRETURN", "DRETURN", "ARETURN", "RETURN", "ARRAYLENGTH", "ATHROW", "MONITORENTER", "MONITOREXIT"}; final String[] intInsn = {"BIPUSH", "SIPUSH", "NEWARRAY"}; final String[] varInsn = {"ILOAD", "LLOAD", "FLOAD", "DLOAD", "ALOAD", "ISTORE", "LSTORE", "FSTORE", "DSTORE", "ASTORE", "RET"}; final String[] typeInsn = {"NEW", "ANEWARRAY", "CHECKCAST", "INSTANCEOF"}; final String[] fieldInsn = {"GETSTATIC", "PUTSTATIC", "GETFIELD", "PUTFIELD"}; final String[] methodInsn = {"INVOKEVIRTUAL", "INVOKESPECIAL", "INVOKESTATIC", "INVOKEINTERFACE"}; final String[] jumpInsn = {"IFEQ", "IFNE", "IFLT", "IFGE", "IFGT", "IFLE", "IF_ICMPEQ", "IF_ICMPNE", "IF_ICMPLT", "IF_ICMPGE", "IF_ICMPGT", "IF_ICMPLE", "IF_ACMPEQ", "IF_ACMPNE", "GOTO", "JSR", "IFNULL", "IFNONNULL"}; //Instructions(No arguments) if(stringArrayContains(insn, value)) { int opcode = Opcodes.class.getField(value).getInt(null); //The ASM library already has the name to number binding, so we use it. How slow is this? method.visitInsn(opcode); } //Integer instructions else if(stringArrayContains(intInsn, value)) { int opcode = Opcodes.class.getField(value).getInt(null); if(value.equals("NEWARRAY")) method.visitIntInsn(opcode, Opcodes.class.getField(nextToken()).getInt(null)); else method.visitIntInsn(opcode, Integer.parseInt(nextToken())); } //Variable instructions else if(stringArrayContains(varInsn, value)) { int opcode = Opcodes.class.getField(value).getInt(null); method.visitVarInsn(opcode, Integer.parseInt(nextToken())); } //Type Instructions else if(stringArrayContains(typeInsn, value)) { int opcode = Opcodes.class.getField(value).getInt(null); method.visitTypeInsn(opcode, nextToken()); } //Field instructions else if(stringArrayContains(fieldInsn, value)) { int opcode = Opcodes.class.getField(value).getInt(null); String temp = nextToken(); int index = temp.indexOf("."); String owner = temp.substring(0, index); String field = temp.substring(index+1); currentToken++; //Skip the ':' token temp = nextToken(); method.visitFieldInsn(opcode, owner, field, temp); } //Method instructions else if(stringArrayContains(methodInsn, value)) { int opcode = Opcodes.class.getField(value).getInt(null); String temp = nextToken(); int index = temp.indexOf("."); String owner = temp.substring(0, index); String field = temp.substring(index+1); temp = nextToken(); //We don't know if the owner class is an interface, but it seems to be able to figure it out itself //TODO: if value == INVOKEINTERFACE then parent class is an interface? Inheritance? What's the point of having a flag for it if it's in the opcode? method.visitMethodInsn(opcode, owner, field, temp); } else if(stringArrayContains(jumpInsn, value)) { int opcode = Opcodes.class.getField(value).getInt(null); //Get the Label index String labelIndex = nextToken(); method.visitJumpInsn(opcode, getLabel(labels, labelIndex)); } else if(value.startsWith("L") && StringUtils.isNumeric(value.substring(1))) { method.visitLabel(getLabel(labels, value)); } else if(value.equals("LDC")) { ValueType obj = parseValue(nextToken()); method.visitLdcInsn(obj.value); } else if(value.equals("IINC")) { int index = Integer.parseInt(nextToken()); int increase = Integer.parseInt(nextToken()); method.visitIincInsn(index, increase); } else if(value.equals("TABLESWITCH")) { //This is a multiline instruction currentToken++; //Go past the newline int min = 0; int max = 0; String temp; Label defLabel; List<Label> labelList = new ArrayList<Label>(); while(true) { int index; temp = nextToken(); if(temp.equals("default:")) { defLabel = getLabel(labels, nextToken()); break; } index = Integer.parseInt(temp.substring(0, temp.length()-1)); labelList.add(getLabel(labels, nextToken())); if(index > max) max = index; else if(index < min) min = index; } method.visitTableSwitchInsn(min, max, defLabel, (Label[])labelList.toArray()); } else if(value.equals("LOOKUPSWITCH")) { String temp; Label defLabel; List<Integer> keyList = new ArrayList<Integer>(); List<Label> labelList = new ArrayList<Label>(); while(true) { temp = nextToken(); if(temp.equals("default:")) { defLabel = getLabel(labels, nextToken()); break; } keyList.add(Integer.parseInt(temp.substring(0, temp.length()-1))); labelList.add(getLabel(labels, nextToken())); } method.visitLookupSwitchInsn(defLabel, Ints.toArray(keyList), (Label[])labelList.toArray()); } else if(value.equals("MULTIANEWARRAY")) { method.visitMultiANewArrayInsn(nextToken(), Integer.parseInt(nextToken())); } else if(value.equals("INVOKEDYNAMIC")) { throw new Exception(getCurrentTokenLine() + ": Error while parsing method instructions: Handling 'INVOKEDYNAMIC' not yet implemented!"); } else if(value.equals("TRYCATCHBLOCK")) { Label start = getLabel(labels, nextToken()); Label end = getLabel(labels, nextToken()); Label handler = getLabel(labels, nextToken()); method.visitTryCatchBlock(start, end, handler, nextToken()); } else if(value.equals("LOCALVARIABLE")) { String name = nextToken(); String desc = nextToken(); Label start = getLabel(labels, nextToken()); Label end = getLabel(labels, nextToken()); int index = Integer.parseInt(nextToken()); method.visitLocalVariable(name, desc, signature, start, end, index); } else if(value.equals("LINENUMBER")) { int line = Integer.parseInt(nextToken()); Label start = getLabel(labels, nextToken()); method.visitLineNumber(line, start); } else if(value.equals("FRAME")) { String typeStr = nextToken(); int type = 0; //Frame operation type if(typeStr.equals("NEW")) type = Opcodes.F_NEW; else if(typeStr.equals("FULL")) type = Opcodes.F_FULL; else if(typeStr.equals("SAME")) type = Opcodes.F_SAME; else if(typeStr.equals("SAME1")) type = Opcodes.F_SAME1; else if(typeStr.equals("APPEND")) type = Opcodes.F_APPEND; else if(typeStr.equals("CHOP")) type = Opcodes.F_CHOP; else throw new Exception(getCurrentTokenLine() + ": Error while parsing method frame: No known FRAME type found. Got: " + typeStr); Object[] localTypes = null; Object[] stackTypes = null; int localCount = 0; int stackCount = 0; if(type == Opcodes.F_NEW || type == Opcodes.F_FULL) { //Parse local types localTypes = parseFrameElements(labels); localCount = localTypes.length; //Parse stack types stackTypes = parseFrameElements(labels); stackCount = stackTypes.length; } else if (type == Opcodes.F_SAME1) { stackTypes = new Object[] { nextToken() }; stackCount = 1; } else if(type == Opcodes.F_APPEND) { localTypes = parseFrameElements(labels); localCount = localTypes.length; } else if(type == Opcodes.F_CHOP) { localCount = Integer.parseInt(nextToken()); } method.visitFrame(type, localCount, localTypes, stackCount, stackTypes); } else if(value.equals("MAXSTACK")) { currentToken++; //Skip the '=' int maxStack = Integer.parseInt(nextToken()); currentToken++; //Skip to next line if(!nextToken().equals("MAXLOCALS")) throw new Exception(getCurrentTokenLine() + ": Error while parsing method: Expected MAXLOCALS, got: " + getCurrentToken()); currentToken++; int maxLocals = Integer.parseInt(nextToken()); method.visitMaxs(maxStack, maxLocals); } else { throw new RuntimeException(getCurrentTokenLine() + ": Parser got unknown method instruction: " + value); } //For now we asume(As has been observed) that MAXSTACK and MAXLOCALS always are together in the same order /*else if(value.equals("MAXLOCALS")) { }*/ } protected Object[] parseFrameElements(Map<String, Label> labels) throws Exception { List<Object> objects = new ArrayList<Object>(); String value = nextToken(); if(value.equals("[]")) //Empty return objects.toArray(); if(value.endsWith("]")) { objects.add(parseFrameType(labels, value.substring(1, value.length()-1))); //Remove both '[' and ']' return objects.toArray(); } objects.add(parseFrameType(labels, value.substring(1))); //Get first value without the '[' while(true) { value = nextToken(); if(value.endsWith("]")) { objects.add(parseFrameType(labels, value.substring(0,value.length()-1))); return objects.toArray(); } objects.add(parseFrameType(labels, value)); } } //Parse the element/object type for a frame protected Object parseFrameType(Map<String, Label> labels, String typeStr) throws Exception { if(typeStr.length() == 1) //Base type { if(typeStr.equals("T")) return Opcodes.TOP; else if(typeStr.equals("I")) return Opcodes.INTEGER; else if(typeStr.equals("F")) return Opcodes.FLOAT; else if(typeStr.equals("D")) return Opcodes.DOUBLE; else if(typeStr.equals("J")) return Opcodes.LONG; else if(typeStr.equals("N")) return Opcodes.NULL; else if(typeStr.equals("U")) return Opcodes.UNINITIALIZED_THIS; else throw new Exception(getCurrentTokenLine() + ": Error while parsing frame type, found no type for " + typeStr); } //Label if(typeStr.startsWith("L") && StringUtils.isNumeric(typeStr.substring(1))) return getLabel(labels, typeStr); //Class name return typeStr; } //Return a label for the given labelIndex, creating it in the map if it doesn't exist protected Label getLabel(Map<String, Label> labelMap, String labelIndex) { Label label = labelMap.get(labelIndex); if(label == null) { label = new Label(); labelMap.put(labelIndex, label); } return label; } protected boolean stringArrayContains(String[] arr, String value) { for(int i=0; i<arr.length; i++) { if(arr[i].equals(value)) return true; } return false; } }