# HG changeset patch # User David Drysdale # Date 1316373918 -3600 # Node ID fa7d2529c54a77c3507daaa18dc650e2a00e8a4e # Parent 6f971ce41019ed57d4419712e150ea1560f975c0 Add support for Java 1.5 + 1.6 class files. Also add more tests cases, fix some bugs, and check that serialization of loaded class files returns the same as the input. diff -r 6f971ce41019 -r fa7d2529c54a javaclass/classfile.py --- a/javaclass/classfile.py Thu Mar 24 01:00:49 2011 +0100 +++ b/javaclass/classfile.py Sun Sep 18 20:25:18 2011 +0100 @@ -6,6 +6,7 @@ Copyright (C) 2004, 2005, 2006, 2011 Paul Boddie Copyright (C) 2010 Braden Thomas +Copyright (C) 2011 David Drysdale This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free @@ -537,9 +538,415 @@ od += lv.serialize() return od +class LocalVariableTypeAttributeInfo(AttributeInfo): + def init(self, data, class_file): + self.class_file = class_file + self.attribute_length = u4(data[0:4]) + local_variable_type_table_length = u2(data[4:6]) + data = data[6:] + self.local_variable_type_table = [] + for i in range(0, local_variable_type_table_length): + local_variable = LocalVariableInfo() + data = local_variable.init(data, self.class_file) + self.local_variable_type_table.append(local_variable) + return data + + def serialize(self): + od = su4(self.attribute_length)+su2(len(self.local_variable_type_table)) + od += "".join([lv.serialize() for lv in self.local_variable_type_table]) + return od + class DeprecatedAttributeInfo(AttributeInfo): pass +class VerificationTypeInfo(object): + def __init__(self, tag): + self.tag = tag + def init(self, data, class_file): + self.class_file = class_file + tag = u1(data[0:1]) + assert(tag == self.tag) + return data[1:] + def serialize(self): + return su1(self.tag) +class TopVariableInfo(VerificationTypeInfo): + TAG = 0 +class IntegerVariableInfo(VerificationTypeInfo): + TAG = 1 +class FloatVariableInfo(VerificationTypeInfo): + TAG = 2 +class DoubleVariableInfo(VerificationTypeInfo): + TAG = 3 +class LongVariableInfo(VerificationTypeInfo): + TAG = 4 +class NullVariableInfo(VerificationTypeInfo): + TAG = 5 +class UninitializedThisVariableInfo(VerificationTypeInfo): + TAG = 6 +class ObjectVariableInfo(VerificationTypeInfo): + TAG = 7 + def init(self, data, class_file): + data = super(ObjectVariableInfo, self).init(data, class_file) + self.cpool_index = u2(data) + return data[2:] + def serialize(self): + return super(ObjectVariableInfo, self).serialize() + su2(self.cpool_index) +class UninitializedVariableInfo(VerificationTypeInfo): + TAG = 8 + def init(self, data, class_file): + data = super(UninitializedVariableInfo, self).init(data, class_file) + self.offset = u2(data) + return data[2:] + def serialize(self): + return super(UninitializedVariableInfo, self).serialize() + su2(self.offset) + +VARIABLE_INFO_CLASSES = (TopVariableInfo, IntegerVariableInfo, FloatVariableInfo, DoubleVariableInfo, + LongVariableInfo, NullVariableInfo, UninitializedThisVariableInfo, + ObjectVariableInfo, UninitializedVariableInfo) +VARIABLE_INFO_TAG_MAP = dict([(cls.TAG, cls) for cls in VARIABLE_INFO_CLASSES]) + +# Exception +class UnknownVariableInfo: + def __init__(self, tag): + self.tag = tag + def __str__(self): + return repr(self.tag) + +def create_verification_type_info(data): + # Does not consume data, just does lookahead + tag = u1(data[0:1]) + if tag in VARIABLE_INFO_TAG_MAP: + return VARIABLE_INFO_TAG_MAP[tag](tag) + else: + raise UnknownVariableInfo, tag + + +class StackMapFrame(object): + def __init__(self, frame_type): + self.frame_type = frame_type + def init(self, data, class_file): + self.class_file = class_file + frame_type = u1(data[0:1]) + assert(frame_type == self.frame_type) + return data[1:] + def serialize(self): + return su1(self.frame_type) +class SameFrame(StackMapFrame): + TYPE_LOWER = 0 + TYPE_UPPER = 63 +class SameLocals1StackItemFrame(StackMapFrame): + TYPE_LOWER = 64 + TYPE_UPPER = 127 + def init(self, data, class_file): + data = super(SameLocals1StackItemFrame, self).init(data, class_file) + self.offset_delta = self.frame_type - 64 + self.stack = [create_verification_type_info(data)] + return self.stack[0].init(data, class_file) + def serialize(self): + return super(SameLocals1StackItemFrame, self).serialize()+self.stack[0].serialize() +class SameLocals1StackItemFrameExtended(StackMapFrame): + TYPE_LOWER = 247 + TYPE_UPPER = 247 + def init(self, data, class_file): + data = super(SameLocals1StackItemFrameExtended, self).init(data, class_file) + self.offset_delta = u2(data[0:2]) + data = data[2:] + self.stack = [create_verification_type_info(data)] + return self.stack[0].init(data, class_file) + def serialize(self): + return super(SameLocals1StackItemFrameExtended, self).serialize()+su2(self.offset_delta)+self.stack[0].serialize() +class ChopFrame(StackMapFrame): + TYPE_LOWER = 248 + TYPE_UPPER = 250 + def init(self, data, class_file): + data = super(ChopFrame, self).init(data, class_file) + self.offset_delta = u2(data[0:2]) + return data[2:] + def serialize(self): + return super(ChopFrame, self).serialize()+su2(self.offset_delta) +class SameFrameExtended(StackMapFrame): + TYPE_LOWER = 251 + TYPE_UPPER = 251 + def init(self, data, class_file): + data = super(SameFrameExtended, self).init(data, class_file) + self.offset_delta = u2(data[0:2]) + return data[2:] + def serialize(self): + return super(SameFrameExtended, self).serialize()+su2(self.offset_delta) +class AppendFrame(StackMapFrame): + TYPE_LOWER = 252 + TYPE_UPPER = 254 + def init(self, data, class_file): + data = super(AppendFrame, self).init(data, class_file) + self.offset_delta = u2(data[0:2]) + data = data[2:] + num_locals = self.frame_type - 251 + self.locals = [] + for ii in xrange(num_locals): + info = create_verification_type_info(data) + data = info.init(data, class_file) + self.locals.append(info) + return data + def serialize(self): + od = super(AppendFrame, self).serialize()+su2(self.offset_delta) + od += "".join([l.serialize() for l in self.locals]) + return od +class FullFrame(StackMapFrame): + TYPE_LOWER = 255 + TYPE_UPPER = 255 + def init(self, data, class_file): + data = super(FullFrame, self).init(data, class_file) + self.offset_delta = u2(data[0:2]) + num_locals = u2(data[2:4]) + data = data[4:] + self.locals = [] + for ii in xrange(num_locals): + info = create_verification_type_info(data) + data = info.init(data, class_file) + self.locals.append(info) + num_stack_items = u2(data[0:2]) + data = data[2:] + self.stack = [] + for ii in xrange(num_stack_items): + stack_item = create_verification_type_info(data) + data = stack_item.init(data, class_file) + self.stack.append(stack_item) + return data + def serialize(self): + od = super(FullFrame, self).serialize()+su2(self.offset_delta)+su2(len(self.locals)) + od += "".join([l.serialize() for l in self.locals]) + od += su2(len(self.stack)) + od += "".join([s.serialize() for s in self.stack]) + return od + +FRAME_CLASSES = (SameFrame, SameLocals1StackItemFrame, SameLocals1StackItemFrameExtended, + ChopFrame, SameFrameExtended, AppendFrame, FullFrame) + +# Exception +class UnknownStackFrame: + def __init__(self, frame_type): + self.frame_type = frame_type + def __str__(self): + return repr(self.frame_type) + +def create_stack_frame(data): + # Does not consume data, just does lookahead + frame_type = u1(data[0:1]) + for cls in FRAME_CLASSES: + if frame_type >= cls.TYPE_LOWER and frame_type <= cls.TYPE_UPPER: + return cls(frame_type) + raise UnknownStackFrame, frame_type + +class StackMapTableAttributeInfo(AttributeInfo): + def init(self, data, class_file): + self.class_file = class_file + self.attribute_length = u4(data[0:4]) + num_entries = u2(data[4:6]) + self.entries = [] + data = data[6:] + for i in range(0, num_entries): + frame = create_stack_frame(data) + data = frame.init(data, class_file) + self.entries.append(frame) + return data + def serialize(self): + od = su4(self.attribute_length)+su2(len(self.entries)) + od += "".join([e.serialize() for e in self.entries]) + return od + + +class EnclosingMethodAttributeInfo(AttributeInfo): + def init(self, data, class_file): + self.class_file = class_file + self.attribute_length = u4(data[0:4]) + self.class_index = u2(data[4:6]) + self.method_index = u2(data[6:8]) + return data[8:] + def serialize(self): + return su4(self.attribute_length)+su2(self.class_index)+su2(self.method_index) + + +class SignatureAttributeInfo(AttributeInfo): + def init(self, data, class_file): + self.class_file = class_file + self.attribute_length = u4(data[0:4]) + self.signature_index = u2(data[4:6]) + return data[6:] + def serialize(self): + return su4(self.attribute_length)+su2(self.signature_index) + + +class SourceDebugExtensionAttributeInfo(AttributeInfo): + def init(self, data, class_file): + self.class_file = class_file + self.attribute_length = u4(data[0:4]) + self.debug_extension = data[4:(4 + self.attribute_length)] + return data[(4+ self.attribute_length):] + def serialize(self): + return su4(self.attribute_length)+self.debug_extension + + +class ElementValue(object): + def __init__(self, tag): + self.tag = tag + def init(self, data, class_file): + self.class_file = class_file + tag = chr(u1(data[0:1])) + assert(tag == self.tag) + return data[1:] + def serialize(self): + return su1(ord(self.tag)) +class ConstValue(ElementValue): + def init(self, data, class_file): + data = super(ConstValue, self).init(data, class_file) + self.const_value_index = u2(data[0:2]) + return data[2:] + def serialize(self): + return super(ConstValue, self).serialize()+su2(self.const_value_index) +class EnumConstValue(ElementValue): + def init(self, data, class_file): + data = super(EnumConstValue, self).init(data, class_file) + self.type_name_index = u2(data[0:2]) + self.const_name_index = u2(data[2:4]) + return data[4:] + def serialize(self): + return super(EnumConstValue, self).serialize()+su2(self.type_name_index)+su2(self.const_name_index) +class ClassInfoValue(ElementValue): + def init(self, data, class_file): + data = super(ClassInfoValue, self).init(data, class_file) + self.class_info_index = u2(data[0:2]) + return data[2:] + def serialize(self): + return super(ClassInfoValue, self).serialize()+su2(self.class_info_index) +class AnnotationValue(ElementValue): + def init(self, data, class_file): + data = super(AnnotationValue, self).init(data, class_file) + self.annotation_value = Annotation() + return self.annotation_value.init(data, class_file) + def serialize(self): + return super(AnnotationValue, self).serialize()+self.annotation_value.serialize() +class ArrayValue(ElementValue): + def init(self, data, class_file): + data = super(ArrayValue, self).init(data, class_file) + num_values = u2(data[0:2]) + data = data[2:] + self.values = [] + for ii in xrange(num_values): + element_value = create_element_value(data) + data = element_value.init(data, class_file) + self.values.append(element_value) + return data + def serialize(self): + od = super(ArrayValue, self).serialize()+su2(len(self.values)) + od += "".join([v.serialize() for v in self.values]) + return od +# Exception +class UnknownElementValue: + def __init__(self, tag): + self.tag = tag + def __str__(self): + return repr(self.tag) + +def create_element_value(data): + tag = chr(u1(data[0:1])) + if tag in ('B', 'C', 'D', 'F', 'I', 'J', 'S', 'Z', 's'): + return ConstValue(tag) + elif tag == 'e': + return EnumConstValue(tag) + elif tag == 'c': + return ClassInfoValue(tag) + elif tag == '@': + return AnnotationValue(tag) + elif tag == '[': + return ArrayValue(tag) + else: + raise UnknownElementValue, tag + + +class Annotation(object): + def init(self, data, class_file): + self.class_file = class_file + self.type_index = u2(data[0:2]) + num_element_value_pairs = u2(data[2:4]) + data = data[4:] + self.element_value_pairs = [] + for ii in xrange(num_element_value_pairs): + element_name_index = u2(data[0:2]) + data = data[2:] + element_value = create_element_value(data) + data = element_value.init(data, class_file) + self.element_value_pairs.append((element_name_index, element_value)) + return data + def serialize(self): + od = su2(self.type_index)+su2(len(self.element_value_pairs)) + od += "".join([su2(evp[0])+evp[1].serialize() for evp in self.element_value_pairs]) + return od + + +class RuntimeAnnotationsAttributeInfo(AttributeInfo): + def init(self, data, class_file): + self.class_file = class_file + self.attribute_length = u4(data[0:4]) + num_annotations = u2(data[4:6]) + data = data[6:] + self.annotations = [] + for ii in xrange(num_annotations): + annotation = Annotation() + data = annotation.init(data, class_file) + self.annotations.append(annotation) + return data + def serialize(self): + od = su4(self.attribute_length)+su2(len(self.annotations)) + od += "".join([a.serialize() for a in self.annotations]) + return od + +class RuntimeVisibleAnnotationsAttributeInfo(RuntimeAnnotationsAttributeInfo): + pass + +class RuntimeInvisibleAnnotationsAttributeInfo(RuntimeAnnotationsAttributeInfo): + pass + +class RuntimeParameterAnnotationsAttributeInfo(AttributeInfo): + def init(self, data, class_file): + self.class_file = class_file + self.attribute_length = u4(data[0:4]) + num_parameters = u1(data[4:5]) + data = data[5:] + self.parameter_annotations = [] + for ii in xrange(num_parameters): + num_annotations = u2(data[0:2]) + data = data[2:] + annotations = [] + for jj in xrange(num_annotations): + annotation = Annotation() + data = annotation.init(data, class_file) + annotations.append(annotation) + self.parameter_annotations.append(annotations) + return data + def serialize(self): + od = su4(self.attribute_length)+su1(len(self.parameter_annotations)) + for pa in self.parameter_annotations: + od += su2(len(pa)) + od += "".join([a.serialize() for a in pa]) + return od + +class RuntimeVisibleParameterAnnotationsAttributeInfo(RuntimeParameterAnnotationsAttributeInfo): + pass + +class RuntimeInvisibleParameterAnnotationsAttributeInfo(RuntimeParameterAnnotationsAttributeInfo): + pass + +class AnnotationDefaultAttributeInfo(AttributeInfo): + def init(self, data, class_file): + self.class_file = class_file + self.attribute_length = u4(data[0:4]) + data = data[4:] + self.default_value = create_element_value(data) + return self.default_value.init(data, class_file) + def serialize(self): + return su4(self.attribute_length)+self.default_value.serialize() + + # Child classes of the attribute information classes. class ExceptionInfo: @@ -592,11 +999,45 @@ # Exceptions. class UnknownTag(Exception): - pass + def __init__(self, tag): + self.tag = tag + def __str__(self): + return repr(self.tag) class UnknownAttribute(Exception): - pass + def __init__(self, name): + self.name = name +ATTR_NAMES_TO_CLASS = {"SourceFile": SourceFileAttributeInfo, + "ConstantValue": ConstantValueAttributeInfo, + "Code": CodeAttributeInfo, + "Exceptions": ExceptionsAttributeInfo, + "InnerClasses": InnerClassesAttributeInfo, + "Synthetic": SyntheticAttributeInfo, + "LineNumberTable": LineNumberAttributeInfo, + "LocalVariableTable": LocalVariableAttributeInfo, + "Deprecated": DeprecatedAttributeInfo, + # Java SE 1.6, class file >= 50.0, VMSpec v3 s4.7.4 + "StackMapTable": StackMapTableAttributeInfo, + # Java SE 1.5, class file >= 49.0, VMSpec v3 s4.7.7 + "EnclosingMethod": EnclosingMethodAttributeInfo, + # Java SE 1.5, class file >= 49.0, VMSpec v3 s4.7.9 + "Signature": SignatureAttributeInfo, + # Java SE 1.5, class file >= 49.0, VMSpec v3 s4.7.11 + "SourceDebugExtension": SourceDebugExtensionAttributeInfo, + # Java SE 1.5, class file >= 49.0, VMSpec v3 s4.7.14 + "LocalVariableTypeTable": LocalVariableTypeAttributeInfo, + # Java SE 1.5, class file >= 49.0, VMSpec v3 s4.7.16 + "RuntimeVisibleAnnotations": RuntimeVisibleAnnotationsAttributeInfo, + # Java SE 1.5, class file >= 49.0, VMSpec v3 s4.7.17 + "RuntimeInvisibleAnnotations": RuntimeInvisibleAnnotationsAttributeInfo, + # Java SE 1.5, class file >= 49.0, VMSpec v3 s4.7.18 + "RuntimeVisibleParameterAnnotations": RuntimeVisibleParameterAnnotationsAttributeInfo, + # Java SE 1.5, class file >= 49.0, VMSpec v3 s4.7.19 + "RuntimeInvisibleParameterAnnotations": RuntimeInvisibleParameterAnnotationsAttributeInfo, + # Java SE 1.5, class file >= 49.0, VMSpec v3 s4.7.20 + "AnnotationDefault": AnnotationDefaultAttributeInfo,} + # Abstractions for the main structures. class ClassFile: @@ -611,6 +1052,9 @@ """ self.attribute_class_to_index = None + magic = u4(s[0:]) + if magic != 0xCAFEBABE: + raise UnknownAttribute, magic self.minorv,self.majorv = u2(s[4:]),u2(s[6:]) self.constants, s = self._get_constants(s[8:]) self.access_flags, s = self._get_access_flags(s) @@ -651,10 +1095,10 @@ od += su1(8) elif isinstance(c, FieldRefInfo): od += su1(9) + elif isinstance(c, InterfaceMethodRefInfo): # check subclass first + od += su1(11) elif isinstance(c, MethodRefInfo): od += su1(10) - elif isinstance(c, InterfaceMethodRefInfo): - od += su1(11) elif isinstance(c, NameAndTypeInfo): od += su1(12) else: @@ -725,24 +1169,8 @@ def _get_attribute_from_table(self, s): attribute_name_index = u2(s[0:2]) constant_name = self.constants[attribute_name_index - 1].bytes - if constant_name == "SourceFile": - attribute = SourceFileAttributeInfo() - elif constant_name == "ConstantValue": - attribute = ConstantValueAttributeInfo() - elif constant_name == "Code": - attribute = CodeAttributeInfo() - elif constant_name == "Exceptions": - attribute = ExceptionsAttributeInfo() - elif constant_name == "InnerClasses": - attribute = InnerClassesAttributeInfo() - elif constant_name == "Synthetic": - attribute = SyntheticAttributeInfo() - elif constant_name == "LineNumberTable": - attribute = LineNumberAttributeInfo() - elif constant_name == "LocalVariableTable": - attribute = LocalVariableAttributeInfo() - elif constant_name == "Deprecated": - attribute = DeprecatedAttributeInfo() + if constant_name in ATTR_NAMES_TO_CLASS: + attribute = ATTR_NAMES_TO_CLASS[constant_name]() else: raise UnknownAttribute, constant_name s = attribute.init(s[2:], self) @@ -796,7 +1224,7 @@ return interfaces, s def _serialize_interfaces(self): - return su2(len(self.interfaces))+"".join([su2(self.interfaces.index(interf)+1) for interf in self.interfaces]) + return su2(len(self.interfaces))+"".join([su2(self.constants.index(interf)+1) for interf in self.interfaces]) def _get_fields(self, s): number = u2(s[0:2]) @@ -816,16 +1244,11 @@ if len(attrs) == 0: return od if self.attribute_class_to_index == None: self.attribute_class_to_index = {} - attr_names_to_class = {"SourceFile":SourceFileAttributeInfo, "ConstantValue":ConstantValueAttributeInfo, - "Code":CodeAttributeInfo, "Exceptions":ExceptionsAttributeInfo, - "InnerClasses":InnerClassesAttributeInfo, "Synthetic":SyntheticAttributeInfo, - "LineNumberTable":LineNumberAttributeInfo, "LocalVariableTable":LocalVariableAttributeInfo, - "Deprecated":DeprecatedAttributeInfo} index = 0 for c in self.constants: index += 1 - if isinstance(c, Utf8Info) and str(c) in attr_names_to_class.keys(): - self.attribute_class_to_index[attr_names_to_class[str(c)]]=index + if isinstance(c, Utf8Info) and str(c) in ATTR_NAMES_TO_CLASS.keys(): + self.attribute_class_to_index[ATTR_NAMES_TO_CLASS[str(c)]]=index for attribute in attrs: for (classtype,name_index) in self.attribute_class_to_index.iteritems(): if isinstance(attribute, classtype): @@ -847,7 +1270,10 @@ if __name__ == "__main__": import sys f = open(sys.argv[1], "rb") - c = ClassFile(f.read()) + in_data = f.read() + c = ClassFile(in_data) f.close() + out_data = c.serialize() + assert(in_data == out_data) # vim: tabstop=4 expandtab shiftwidth=4 diff -r 6f971ce41019 -r fa7d2529c54a tests/Annotation.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/Annotation.java Sun Sep 18 20:25:18 2011 +0100 @@ -0,0 +1,5 @@ + +public @interface Annotation { + int a() default 99; + String b(); +} diff -r 6f971ce41019 -r fa7d2529c54a tests/AnnotationMarker.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/AnnotationMarker.java Sun Sep 18 20:25:18 2011 +0100 @@ -0,0 +1,5 @@ +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface AnnotationMarker {} \ No newline at end of file diff -r 6f971ce41019 -r fa7d2529c54a tests/AnnotationTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/AnnotationTest.java Sun Sep 18 20:25:18 2011 +0100 @@ -0,0 +1,14 @@ + +class AnnotationTest { + @Annotation(a=1, b="foo") + public static void hello() { + } + @AnnotationMarker + private static int getx() { + return 1; + } + private static int addup(@Annotation(b="param") int x, + int y) { + return x+y; + } +} \ No newline at end of file diff -r 6f971ce41019 -r fa7d2529c54a tests/EnclosingMethodTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/EnclosingMethodTest.java Sun Sep 18 20:25:18 2011 +0100 @@ -0,0 +1,12 @@ +public class EnclosingMethodTest { + public int outer(int a) { + class InnerClass { + public int inner(int b) { + return b+1; + } + } + InnerClass inc = new InnerClass(); + return inc.inner(a); + } +} + diff -r 6f971ce41019 -r fa7d2529c54a tests/GenericTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/GenericTest.java Sun Sep 18 20:25:18 2011 +0100 @@ -0,0 +1,9 @@ +import java.util.List; +import java.util.ArrayList; +class GenericTest { + public T mT; + public GenericTest(T t) { + mT = t; + List l = new ArrayList(); + } +} \ No newline at end of file