![]() |
|||
![]() ![]() |
![]() |
|
![]() |
MethodsThe final piece of the disassembly puzzle is decoding the methods. As with the fields, this will take place inside a loop, because almost all classes have multiple methods. Every method has five parts that you must decode: the access specifiers, the return type, the name, the argument list, the exception list, and the byte codes. Heres a skeleton for the writeMethods() method: public void writeMethods() { for (int i = 0; i < methods.length; i++) { theOutput.println(); theOutput.print( ); // access specifiers //print the return type //print the name of the method //argument list //exceptions // method body } } Method access specifiers The access specifiers are quite simple to read with the methods of the MethodInfo class. Heres the code: if (methods[i].isPublic()) theOutput.print(public ); if (methods[i].isPrivate()) theOutput.print(private ); if (methods[i].isProtected()) theOutput.print(protected ); if (methods[i].isStatic()) theOutput.print(static ); if (methods[i].isNative()) theOutput.print(native ); if (methods[i].isSynchronized()) theOutput.print(synchronized ); if (methods[i].isAbstract()) theOutput.print(abstract ); Method arguments and return type The arguments and return type are considerably harder to get at. The method descriptor contains a complete list of all of a methods arguments and its return value. These are encoded much like the field type descriptor, except that there can be more than one at a time. The arguments appear in parentheses and the return value follows that. For example, a method with the signature public static void main(String[] args) has the descriptor ([Ljava/lang/String)V. This indicates that the method takes a one-dimensional array of java.lang.String objects and returns void. A method that takes two doubles as arguments and returns a double would have a signature of (DD)D. The disassembler program uses two separate methods to parse the method descriptor. The getReturnType() method gets the return type, and the getArguments() method handles the arguments. The getReturnType() method (see Listing 4-32) reads the descriptor and passes everything after the closing parenthesis to the decodeDescriptor() method. This is the same decodeDescriptor() method used to get the type of a field. For example, if the method descriptor is (DD)D, then the string D is passed to decodeDescriptor(). If the method descriptor is ([Ljava/lang/String)V, then the string V is passed to decodeDescriptor(). Listing 4-32 The getReturnType() method public String getReturnType(MethodInfo mi) { String descriptor = thePool.readUTF8(mi.descriptorIndex()); String d = descriptor.substring(descriptor.indexOf()) + 1); return decodeDescriptor(d); } The getArguments() method is more complex because it needs to parse several arguments at a time. Furthermore, there are no convenient separators between the types. Finally, to make matters even worse, different types can have different sizes in the method descriptor strings. Primitive and void types are always one character wide, but array and object types have undetermined sizes. Therefore, you must consider the character to decide what to do with it. If the character is one for a primitive data type, then you should pass that character (after converting it to a string) to the decodeDescriptor() method. However, if that character is an L, then you need to read up to the next semicolon and pass that string to decodeDescriptor(). Finally, if a character is a left bracket, then you must read as many brackets as follow and then read a type that may be a single character (that is, a primitive data type) or an object type. In essence, you need to embed the method inside itself to properly handle array types. Listing 4-33 is the getArguments() method. This uses the variable a to keep track of the number of arguments that have been processed (so that you can tell where commas are needed in the argument list). It uses the variable i to tell which character in the descriptor begins the next type. This method would be much simpler if the descriptor had a constant with format that allowed a and i to be kept in sync. Listing 4-33 The getArguments() method public String getArguments(MethodInfo mi) { String descriptor = thePool.readUTF8(mi.descriptorIndex()); String params = descriptor.substring(1,descriptor.indexOf())); String result = ; try { int i = 0; int a = 0; // number of arguments while (i < params.length()) { char c = params.charAt(i); switch (c) { case [: if (a++ != 0) result += , ; int dimensions = 0; while (params.charAt(i) == [) { i++; dimensions++; } char t = params.charAt(i); String type; if (t == L) { type = decodeDescriptor(params.substring(i, params.indexOf(;, i) + 1)); i = params.indexOf(;, i) + 1; } else { type = decodeDescriptor(String.valueOf(t)); i++; } for (int j=0; j < dimensions; j++) { type += []; } result += type; break; case L: if (a++ != 0) result += , ; String o = params.substring(i+1, params.indexOf(;, i)); result += o.replace(/, .); i = params.indexOf(;, i) + 1; break; case B: case C: case D: case F: case I: case J: case S: case Z: if (a++ != 0) result += , ; result += decodeDescriptor(String.valueOf(c)); i++; break; case V: i++; break; default: throw new ClassFormatError(Bad Parameter String: + params + + c); } } } catch (StringIndexOutOfBoundsException e) { } return result; }
|
![]() |
|