class CodeAttribute(Attribute): """ A `CodeAttribute` contains the executable bytecode of a single method. As a quick example, lets make a "HelloWorld" class with a single method that simply returns when it's called: .. code-block:: python from jawa import ClassFile from jawa.util.bytecode import Instruction cf = ClassFile.create('HelloWorld') main = cf.methods.create( # The name of the method 'main', # The signature of the method '([Ljava/lang/String;)V', # Tell Jawa to automatically create an empty CodeAttribute for # us to use. code=True ) main.code.max_locals = 1 main.access_flags.acc_static = True main.code.assemble([ Instruction.from_mnemonic('return') ]) # Save it to disk so we can run it with the JVM. with open('HelloWorld.class', 'wb') as fout: cf.save(fout) """ ADDED_IN = '1.0.2' MINIMUM_CLASS_VERSION = (45, 3) def __init__(self, table, name_index=None): super(CodeAttribute, self).__init__( table, name_index or table.cf.constants.create_utf8( 'Code' ).index ) self.max_stack = 0 self.max_locals = 0 self.exception_table = [] self.attributes = AttributeTable(table.cf, parent=self) self._code = '' def unpack(self, info): """ Read the CodeAttribute from the byte string `info`. .. note:: Advanced usage only. You will typically never need to call this method as it will be called for you when loading a ClassFile. :param info: A byte string containing an unparsed CodeAttribute. """ self.max_stack, self.max_locals, c_len = info.unpack('>HHI') self._code = info.read(c_len) # The exception table ex_table_len = info.u2() for _ in repeat(None, ex_table_len): self.exception_table.append(CodeException( *info.unpack('>HHHH') )) self.attributes = AttributeTable(self.cf, parent=self) self.attributes.unpack(info) def pack(self): """ The `CodeAttribute` in packed byte string form. """ with io.BytesIO() as file_out: file_out.write(pack( '>HHI', self.max_stack, self.max_locals, len(self._code) )) file_out.write(self._code) file_out.write(pack('>H', len(self.exception_table))) for exception in self.exception_table: file_out.write(pack('>HHHH', *exception)) self.attributes.pack(file_out) return file_out.getvalue() def assemble(self, code): """ Assembles an iterable of :class:`~jawa.util.bytecode.Instruction` objects into a method's code body. """ with io.BytesIO() as code_out: for ins in code: write_instruction(code_out, code_out.tell(), ins) self._code = code_out.getvalue() def disassemble(self, *, transforms=None) -> Iterator[Instruction]: """ Disassembles this method, yielding an iterable of :class:`~jawa.util.bytecode.Instruction` objects. """ if transforms is None: if self.cf.classloader: transforms = self.cf.classloader.bytecode_transforms else: transforms = [] transforms = [self._bind_transform(t) for t in transforms] with io.BytesIO(self._code) as code: ins_iter = iter(lambda: read_instruction(code, code.tell()), None) for ins in ins_iter: for transform in transforms: ins = transform(ins) yield ins def _bind_transform(self, transform): sig = inspect.signature(transform, follow_wrapped=True) return functools.partial( transform, **{k: v for k, v in { 'cf': self.cf, 'attribute': self }.items() if k in sig.parameters} )
class ClassFile(object): """ Implements the JVM ClassFile (files typically ending in ``.class``). To open an existing ClassFile:: >>> with open('HelloWorld.class', 'rb') as fin: ... cf = ClassFile(fin) To save a newly created or modified ClassFile:: >>> cf = ClassFile.create('HelloWorld') >>> with open('HelloWorld.class', 'wb') as out: ... cf.save(out) :meth:`~ClassFile.create` sets up some reasonable defaults equivalent to: .. code-block:: java public class HelloWorld extends java.lang.Object{ } :param source: any file-like object providing ``.read()``. """ #: The JVM ClassFile magic number. MAGIC = 0xCAFEBABE def __init__(self, source: IO = None): # Default to J2SE_7 self._version = ClassVersion(0x32, 0) self._constants = ConstantPool() self.access_flags = Flags( '>H', { 'acc_public': 0x0001, 'acc_final': 0x0010, 'acc_super': 0x0020, 'acc_interface': 0x0200, 'acc_abstract': 0x0400, 'acc_synthetic': 0x1000, 'acc_annotation': 0x2000, 'acc_enum': 0x4000 }) self._this = 0 self._super = 0 self._interfaces = [] self.fields = FieldTable(self) self.methods = MethodTable(self) self.attributes = AttributeTable(self) #: The ClassLoader bound to this ClassFile, if any. self.classloader = None if source: self._from_io(source) @classmethod def create(cls, this: str, super_: str = u'java/lang/Object') -> 'ClassFile': """ A utility which sets up reasonable defaults for a new public class. :param this: The name of this class. :param super_: The name of this class's superclass. """ cf = ClassFile() cf.access_flags.acc_public = True cf.access_flags.acc_super = True cf.this = cf.constants.create_class(this) cf.super_ = cf.constants.create_class(super_) return cf def save(self, source: IO): """ Saves the class to the file-like object `source`. :param source: Any file-like object providing write(). """ write = source.write write( pack('>IHH', ClassFile.MAGIC, self.version.minor, self.version.major)) self._constants.pack(source) write(self.access_flags.pack()) write( pack(f'>HHH{len(self._interfaces)}H', self._this, self._super, len(self._interfaces), *self._interfaces)) self.fields.pack(source) self.methods.pack(source) self.attributes.pack(source) def _from_io(self, source: IO): """ Loads an existing JVM ClassFile from any file-like object. """ read = source.read if unpack('>I', source.read(4))[0] != ClassFile.MAGIC: raise ValueError('invalid magic number') # The version is swapped on disk to (minor, major), so swap it back. self.version = unpack('>HH', source.read(4))[::-1] self._constants.unpack(source) # ClassFile access_flags, see section #4.1 of the JVM specs. self.access_flags.unpack(read(2)) # The CONSTANT_Class indexes for "this" class and its superclass. # Interfaces are a simple list of CONSTANT_Class indexes. self._this, self._super, interfaces_count = unpack('>HHH', read(6)) self._interfaces = unpack(f'>{interfaces_count}H', read(2 * interfaces_count)) self.fields.unpack(source) self.methods.unpack(source) self.attributes.unpack(source) @property def version(self) -> ClassVersion: """ The :class:`~jawa.cf.ClassVersion` for this class. Example:: >>> cf = ClassFile.create('HelloWorld') >>> cf.version = 51, 0 >>> print(cf.version) ClassVersion(major=51, minor=0) >>> print(cf.version.major) 51 """ return self._version @version.setter def version(self, major_minor: Union[ClassVersion, Sequence]): self._version = ClassVersion(*major_minor) @property def constants(self) -> ConstantPool: """ The :class:`~jawa.cp.ConstantPool` for this class. """ return self._constants @property def this(self) -> ConstantClass: """ The :class:`~jawa.constants.ConstantClass` which represents this class. """ return self.constants.get(self._this) @this.setter def this(self, value): self._this = value.index @property def super_(self) -> ConstantClass: """ The :class:`~jawa.constants.ConstantClass` which represents this class's superclass. """ return self.constants.get(self._super) @super_.setter def super_(self, value: ConstantClass): self._super = value.index @property def interfaces(self) -> Iterable[ConstantClass]: """ A list of direct superinterfaces of this class as indexes into the constant pool, in left-to-right order. """ return [self._constants[idx] for idx in self._interfaces] @property def bootstrap_methods(self) -> BootstrapMethod: """ Returns the bootstrap methods table from the BootstrapMethods attribute, if one exists. If it does not, one will be created. :returns: Table of `BootstrapMethod` objects. """ bootstrap = self.attributes.find_one(name='BootstrapMethods') if bootstrap is None: bootstrap = self.attributes.create( ATTRIBUTE_CLASSES['BootstrapMethods']) return bootstrap.table def __repr__(self): return f'<ClassFile(this={self.this.name.value!r})>'
class ClassFile(object): """ Implements the JVM ClassFile (files typically ending in ``.class``). To open an existing ClassFile:: from jawa import ClassFile with open('HelloWorld.class') as fin: cf = ClassFile(fin) To save a newly created or modified ClassFile:: with open('HelloWorld.class', 'wb') as fout: cf.save(fout) To create a new ClassFile, use the helper :meth:`~ClassFile.create`:: from jawa import ClassFile cf = ClassFile.create('HelloWorld') with open('HelloWorld.class', 'wb') as fout: cf.save(fout) :meth:`~ClassFile.create` sets up some reasonable defaults equivelent to: .. code-block:: java public class HelloWorld extends java.lang.Object{ } :param fio: any file-like object providing ``.read()``. """ #: The JVM ClassFile magic number. MAGIC = 0xCAFEBABE def __init__(self, fio=None): # Default to J2SE_7 self._version = ClassVersion(0x32, 0) self._constants = ConstantPool() self._access_flags = Flags('>H', { 'acc_public': 0x0001, 'acc_final': 0x0010, 'acc_super': 0x0020, 'acc_interface': 0x0200, 'acc_abstract': 0x0400, 'acc_synthetic': 0x1000, 'acc_annotation': 0x2000, 'acc_enum': 0x4000 }) self._this = 0 self._super = 0 self._interfaces = [] self._fields = FieldTable(self) self._methods = MethodTable(self) self._attributes = AttributeTable(self) if fio: self._from_io(fio) @classmethod def create(cls, this, super_='java/lang/Object'): """ A utility which sets up reasonable defaults for a new public class. :param this: The name of this class. :param super_: The name of this class's superclass. """ cf = ClassFile() cf.access_flags.acc_public = True cf.access_flags.acc_super = True cf.this = cf.constants.create_class(this) cf.super_ = cf.constants.create_class(super_) return cf def save(self, fout): """ Saves the class to the file-like object `fout`. """ write = fout.write write(pack( '>IHH', ClassFile.MAGIC, self.version.minor, self.version.major )) self._constants.pack(fout) write(self.access_flags.pack()) write(pack( '>HHH{0}H'.format(len(self._interfaces)), self._this, self._super, len(self._interfaces), *self._interfaces )) self._fields.pack(fout) self._methods.pack(fout) self._attributes.pack(fout) def _from_io(self, fio): """ Loads an existing JVM ClassFile from any file-like object. """ read = fio.read if unpack('>I', fio.read(4))[0] != ClassFile.MAGIC: raise ValueError('invalid magic number') # The version is swapped on disk to (minor, major), so swap it back. self.version = unpack('>HH', fio.read(4))[::-1] self._constants.unpack(fio) # ClassFile access_flags, see section #4.1 of the JVM specs. self.access_flags.unpack(read(2)) # The CONSTANT_Class indexes for "this" class and its superclass. # Interfaces are a simple list of CONSTANT_Class indexes. self._this, self._super, interfaces_count = unpack('>HHH', read(6)) self._interfaces = unpack( '>{0}H'.format(interfaces_count), read(2 * interfaces_count) ) self._fields.unpack(fio) self._methods.unpack(fio) self._attributes.unpack(fio) @property def version(self): """ The :class:`~jawa.cf.ClassVersion` for this class. Example:: >>> cf = ClassFile.create('HelloWorld') >>> cf.version = 51, 0 >>> print(cf.version) ClassVersion(major=51, minor=0) >>> print(cf.version.major) 51 """ return self._version @version.setter def version(self, (major, minor)): self._version = ClassVersion(major, minor)
class CodeAttribute(Attribute): """ A `CodeAttribute` contains the executable bytecode of a single method. As a quick example, lets make a "HelloWorld" class with a single method that simply returns when it's called: .. code-block:: python from jawa import ClassFile from jawa.util.bytecode import Instruction cf = ClassFile.create('HelloWorld') main = cf.methods.create( # The name of the method 'main', # The signature of the method '([Ljava/lang/String;)V', # Tell Jawa to automatically create an empty CodeAttribute for # us to use. code=True ) main.code.max_locals = 1 main.access_flags.acc_static = True main.code.assemble([ Instruction.from_mnemonic('return') ]) # Save it to disk so we can run it with the JVM. with open('HelloWorld.class', 'wb') as fout: cf.save(fout) """ ADDED_IN = '1.0.2' MINIMUM_CLASS_VERSION = (45, 3) def __init__(self, table, name_index=None): super(CodeAttribute, self).__init__( table, name_index or table.cf.constants.create_utf8('Code').index) self.max_stack = 0 self.max_locals = 0 self.exception_table = [] self.attributes = AttributeTable(table.cf, parent=self) self._code = '' def unpack(self, info): """ Read the CodeAttribute from the byte string `info`. .. note:: Advanced usage only. You will typically never need to call this method as it will be called for you when loading a ClassFile. :param info: A byte string containing an unparsed CodeAttribute. """ self.max_stack, self.max_locals, c_len = info.unpack('>HHI') self._code = info.read(c_len) # The exception table ex_table_len = info.u2() for _ in repeat(None, ex_table_len): self.exception_table.append(CodeException(*info.unpack('>HHHH'))) self.attributes = AttributeTable(self.cf, parent=self) self.attributes.unpack(info) def pack(self): """ The `CodeAttribute` in packed byte string form. """ with io.BytesIO() as file_out: file_out.write( pack('>HHI', self.max_stack, self.max_locals, len(self._code))) file_out.write(self._code) file_out.write(pack('>H', len(self.exception_table))) for exception in self.exception_table: file_out.write(pack('>HHHH', *exception)) self.attributes.pack(file_out) return file_out.getvalue() def assemble(self, code): """ Assembles an iterable of :class:`~jawa.util.bytecode.Instruction` objects into a method's code body. """ with io.BytesIO() as code_out: for ins in code: write_instruction(code_out, code_out.tell(), ins) self._code = code_out.getvalue() def disassemble(self, *, transforms=None) -> Iterator[Instruction]: """ Disassembles this method, yielding an iterable of :class:`~jawa.util.bytecode.Instruction` objects. """ if transforms is None: if self.cf.classloader: transforms = self.cf.classloader.bytecode_transforms else: transforms = [] transforms = [self._bind_transform(t) for t in transforms] with io.BytesIO(self._code) as code: ins_iter = iter(lambda: read_instruction(code, code.tell()), None) for ins in ins_iter: for transform in transforms: ins = transform(ins) yield ins def _bind_transform(self, transform): sig = inspect.signature(transform, follow_wrapped=True) return functools.partial( transform, **{ k: v for k, v in { 'cf': self.cf, 'attribute': self }.items() if k in sig.parameters })
class Field(object): def __init__(self, cf): self._cf = cf self.access_flags = Flags('>H', { 'acc_public': 0x0001, 'acc_private': 0x0002, 'acc_protected': 0x0004, 'acc_static': 0x0008, 'acc_final': 0x0010, 'acc_volatile': 0x0040, 'acc_transient': 0x0080, 'acc_synthetic': 0x1000, 'acc_enum': 0x4000 }) self._name_index = 0 self._descriptor_index = 0 self.attributes = AttributeTable(cf) @property def descriptor(self) -> UTF8: """ The UTF8 Constant containing the field's descriptor. """ return self._cf.constants[self._descriptor_index] @property def type(self): """ A :class:`~jawa.util.descriptor.JVMType` representing the field's type. """ return field_descriptor(self.descriptor.value) @property def name(self) -> UTF8: """ The UTF8 Constant containing the field's name. """ return self._cf.constants[self._name_index] @property def value(self) -> ConstantValueAttribute: """ A shortcut for the field's ConstantValue attribute, should one exist. """ return self.attributes.find_one(name='ConstantValue') def unpack(self, source: IO): """ Read the Field from the file-like object `fio`. .. note:: Advanced usage only. You will typically never need to call this method as it will be called for you when loading a ClassFile. :param source: Any file-like object providing `read()` """ self.access_flags.unpack(source.read(2)) self._name_index, self._descriptor_index = unpack('>HH', source.read(4)) self.attributes.unpack(source) def pack(self, out: IO): """ Write the Field to the file-like object `out`. .. note:: Advanced usage only. You will typically never need to call this method as it will be called for you when saving a ClassFile. :param out: Any file-like object providing `write()` """ out.write(self.access_flags.pack()) out.write(pack('>HH', self._name_index, self._descriptor_index)) self.attributes.pack(out)
class CodeAttribute(Attribute): """ A `CodeAttribute` contains the executable bytecode of a single method. As a quick example, lets make a "HelloWorld" class with a single method that simply returns when it's called: .. code-block:: python from jawa import ClassFile from jawa.util.bytecode import Instruction cf = ClassFile.create('HelloWorld') main = cf.methods.create( # The name of the method 'main', # The signature of the method '([Ljava/lang/String;)V', # Tell Jawa to automatically create an empty CodeAttribute for # us to use. code=True ) main.code.max_locals = 1 main.access_flags.acc_static = True main.code.assemble([ Instruction.from_mnemonic('return') ]) # Save it to disk so we can run it with the JVM. with open('HelloWorld.class', 'wb') as fout: cf.save(fout) .. note:: Not all :class:`~jawa.methods.Method` objects will have an associated `CodeAttribute` - methods that are flagged as `acc_native` or `acc_abstract` will never have one. """ def __init__(self, table, name_index=None): super(CodeAttribute, self).__init__( table, name_index or table.cf.constants.create_utf8( 'Code' ).index ) self._max_stack = 0 self._max_locals = 0 self._ex_table = [] self._attributes = AttributeTable(table.cf, parent=self) self._code = '' def unpack(self, info): """ Read the CodeAttribute from the byte string `info`. .. note:: Advanced usage only. You will typically never need to call this method as it will be called for you when loading a ClassFile. :param info: A byte string containing an unparsed CodeAttribute. """ fio = StringIO(info) self._max_stack, self._max_locals, c_len = unpack('>HHI', fio.read(8)) self._code = fio.read(c_len) # The exception table ex_table_len = unpack('>H', fio.read(2))[0] self._ex_table = [] for _ in repeat(None, ex_table_len): self._ex_table.append(CodeException( *unpack('>HHHH', fio.read(8)) )) self._attributes = AttributeTable(self._cf, parent=self) self._attributes.unpack(fio) fio.close() @property def info(self): """ The `CodeAttribute` in packed byte string form. """ fout = StringIO() fout.write(pack( '>HHI', self._max_stack, self._max_locals, len(self._code) )) fout.write(self._code) fout.write(pack('>H', len(self._ex_table))) for exception in self._ex_table: fout.write(pack('>HHHH', *exception)) self._attributes.pack(fout) return fout.getvalue() @property def max_stack(self): """The maximum size of the stack.""" return self._max_stack @max_stack.setter def max_stack(self, value): self._max_stack = value @property def max_locals(self): """The maximum number of locals.""" return self._max_locals @max_locals.setter def max_locals(self, value): self._max_locals = value @property def exception_table(self): return self._ex_table @property def code(self): return self._code @property def attributes(self): """ An :class:`~jawa.attribute.AttributeTable` containing all of the attributes associated with this `CodeAttribute`. """ return self._attributes def assemble(self, code): """ Assembles an iterable of :class:`~jawa.util.bytecode.Instruction` objects into a method's code body. """ fout = StringIO() for ins in code: write_instruction(fout, fout.tell(), ins) self._code = fout.getvalue() fout.close() def disassemble(self): """ Disassembles this method, yielding an iterable of :class:`~jawa.util.bytecode.Instruction` objects. """ fio = StringIO(self._code) for ins in iter(lambda: read_instruction(fio, fio.tell()), None): yield ins
class Method(object): def __init__(self, cf): self._cf = cf self.access_flags = Flags('>H', { 'acc_public': 0x0001, 'acc_private': 0x0002, 'acc_protected': 0x0004, 'acc_static': 0x0008, 'acc_final': 0x0010, 'acc_synchronized': 0x0020, 'acc_bridge': 0x0040, 'acc_varargs': 0x0080, 'acc_native': 0x0100, 'acc_abstract': 0x0400, 'acc_strict': 0x0800, 'acc_synthetic': 0x1000 }) self._name_index = 0 self._descriptor_index = 0 self.attributes = AttributeTable(cf) @property def descriptor(self) -> UTF8: """ The UTF8 Constant containing the method's descriptor. """ return self._cf.constants[self._descriptor_index] @property def name(self) -> UTF8: """ The UTF8 Constant containing the method's name. """ return self._cf.constants[self._name_index] @property def returns(self) -> JVMType: """ A :class:`~jawa.util.descriptor.JVMType` representing the method's return type. """ return method_descriptor(self.descriptor.value).returns @property def args(self) -> List[JVMType]: """ A list of :class:`~jawa.util.descriptor.JVMType` representing the method's argument list. """ return method_descriptor(self.descriptor.value).args @property def code(self) -> CodeAttribute: """ A shortcut for :code:`method.attributes.find_one(name='Code')`. """ return self.attributes.find_one(name='Code') def __repr__(self): return f'<Method(name={self.name})>' def unpack(self, source: IO): """ Read the Method from the file-like object `fio`. .. note:: Advanced usage only. You will typically never need to call this method as it will be called for you when loading a ClassFile. :param source: Any file-like object providing `read()` """ self.access_flags.unpack(source.read(2)) self._name_index, self._descriptor_index = unpack('>HH', source.read(4)) self.attributes.unpack(source) def pack(self, out: IO): """ Write the Method to the file-like object `out`. .. note:: Advanced usage only. You will typically never need to call this method as it will be called for you when saving a ClassFile. :param out: Any file-like object providing `write()` """ out.write(self.access_flags.pack()) out.write(pack( '>HH', self._name_index, self._descriptor_index )) self.attributes.pack(out)
class Field(object): def __init__(self, cf): self._cf = cf self._access_flags = Flags( ">H", { "acc_public": 0x0001, "acc_private": 0x0002, "acc_protected": 0x0004, "acc_static": 0x0008, "acc_final": 0x0010, "acc_volatile": 0x0040, "acc_transient": 0x0080, "acc_synthetic": 0x1000, "acc_enum": 0x4000, }, ) self._name_index = 0 self._descriptor_index = 0 self._attributes = AttributeTable(cf) @property def descriptor(self): return self._cf.constants[self._descriptor_index] @property def name(self): return self._cf.constants[self._name_index] @property def access_flags(self): return self._access_flags @property def attributes(self): return self._attributes @property def value(self): """ A shortcut for the field's ConstantValue attribute, should one exist. """ constant_value = self.attributes.find_one(name="ConstantValue") return constant_value def unpack(self, fio): """ Read the Field from the file-like object `fio`. .. note:: Advanced usage only. You will typically never need to call this method as it will be called for you when loading a ClassFile. :param fio: Any file-like object providing `read()` """ self.access_flags.unpack(fio.read(2)) self._name_index, self._descriptor_index = unpack(">HH", fio.read(4)) self._attributes.unpack(fio) def pack(self, fout): """ Write the Field to the file-like object `fout`. .. note:: Advanced usage only. You will typically never need to call this method as it will be calle=d for you when saving a ClassFile. :param fout: Any file-like object providing `write()` """ fout.write(self.access_flags.pack()) fout.write(pack(">HH", self._name_index, self._descriptor_index)) self._attributes.pack(fout)
class Field(object): def __init__(self, cf): self._cf = cf self.access_flags = Flags( '>H', { 'acc_public': 0x0001, 'acc_private': 0x0002, 'acc_protected': 0x0004, 'acc_static': 0x0008, 'acc_final': 0x0010, 'acc_volatile': 0x0040, 'acc_transient': 0x0080, 'acc_synthetic': 0x1000, 'acc_enum': 0x4000 }) self._name_index = 0 self._descriptor_index = 0 self.attributes = AttributeTable(cf) @property def descriptor(self) -> UTF8: """ The UTF8 Constant containing the field's descriptor. """ return self._cf.constants[self._descriptor_index] @property def type(self): """ A :class:`~jawa.util.descriptor.JVMType` representing the field's type. """ return field_descriptor(self.descriptor.value) @property def name(self) -> UTF8: """ The UTF8 Constant containing the field's name. """ return self._cf.constants[self._name_index] @property def value(self) -> ConstantValueAttribute: """ A shortcut for the field's ConstantValue attribute, should one exist. """ return self.attributes.find_one(name='ConstantValue') def unpack(self, source: IO): """ Read the Field from the file-like object `fio`. .. note:: Advanced usage only. You will typically never need to call this method as it will be called for you when loading a ClassFile. :param source: Any file-like object providing `read()` """ self.access_flags.unpack(source.read(2)) self._name_index, self._descriptor_index = unpack( '>HH', source.read(4)) self.attributes.unpack(source) def pack(self, out: IO): """ Write the Field to the file-like object `out`. .. note:: Advanced usage only. You will typically never need to call this method as it will be called for you when saving a ClassFile. :param out: Any file-like object providing `write()` """ out.write(self.access_flags.pack()) out.write(pack('>HH', self._name_index, self._descriptor_index)) self.attributes.pack(out)
class Method(object): def __init__(self, cf): self._cf = cf self._access_flags = Flags('>H', { 'acc_public': 0x0001, 'acc_private': 0x0002, 'acc_protected': 0x0004, 'acc_static': 0x0008, 'acc_final': 0x0010, 'acc_synchronized': 0x0020, 'acc_bridge': 0x0040, 'acc_varargs': 0x0080, 'acc_native': 0x0100, 'acc_abstract': 0x0400, 'acc_strict': 0x0800, 'acc_synthetic': 0x1000 }) self._name_index = 0 self._descriptor_index = 0 self._attributes = AttributeTable(cf) @property def descriptor(self): return self._cf.constants[self._descriptor_index] @property def name(self): return self._cf.constants[self._name_index] @property def access_flags(self): return self._access_flags @property def attributes(self): return self._attributes @property def returns(self): return method_descriptor(self.descriptor.value).returns @property def args(self): return method_descriptor(self.descriptor.value).args @property def code(self): """ A shortcut for:: method.attributes.find_one(name='Code') """ return self.attributes.find_one(name='Code') def unpack(self, fio): """ Read the Method from the file-like object `fio`. .. note:: Advanced usage only. You will typically never need to call this method as it will be called for you when loading a ClassFile. :param fio: Any file-like object providing `read()` """ self.access_flags.unpack(fio.read(2)) self._name_index, self._descriptor_index = unpack('>HH', fio.read(4)) self._attributes.unpack(fio) def pack(self, fout): """ Write the Method to the file-like object `fout`. .. note:: Advanced usage only. You will typically never need to call this method as it will be calle=d for you when saving a ClassFile. :param fout: Any file-like object providing `write()` """ fout.write(self.access_flags.pack()) fout.write(pack( '>HH', self._name_index, self._descriptor_index )) self._attributes.pack(fout)
class ClassFile(object): """ Implements the JVM ClassFile (files typically ending in ``.class``). To open an existing ClassFile:: >>> with open('HelloWorld.class', 'rb') as fin: ... cf = ClassFile(fin) To save a newly created or modified ClassFile:: >>> cf = ClassFile.create('HelloWorld') >>> with open('HelloWorld.class', 'wb') as out: ... cf.save(out) :meth:`~ClassFile.create` sets up some reasonable defaults equivalent to: .. code-block:: java public class HelloWorld extends java.lang.Object{ } :param source: any file-like object providing ``.read()``. """ #: The JVM ClassFile magic number. MAGIC = 0xCAFEBABE def __init__(self, source: IO=None): # Default to J2SE_7 self._version = ClassVersion(0x32, 0) self._constants = ConstantPool() self.access_flags = Flags('>H', { 'acc_public': 0x0001, 'acc_final': 0x0010, 'acc_super': 0x0020, 'acc_interface': 0x0200, 'acc_abstract': 0x0400, 'acc_synthetic': 0x1000, 'acc_annotation': 0x2000, 'acc_enum': 0x4000 }) self._this = 0 self._super = 0 self._interfaces = [] self.fields = FieldTable(self) self.methods = MethodTable(self) self.attributes = AttributeTable(self) #: The ClassLoader bound to this ClassFile, if any. self.classloader = None if source: self._from_io(source) @classmethod def create(cls, this: str, super_: str=u'java/lang/Object') -> 'ClassFile': """ A utility which sets up reasonable defaults for a new public class. :param this: The name of this class. :param super_: The name of this class's superclass. """ cf = ClassFile() cf.access_flags.acc_public = True cf.access_flags.acc_super = True cf.this = cf.constants.create_class(this) cf.super_ = cf.constants.create_class(super_) return cf def save(self, source: IO): """ Saves the class to the file-like object `source`. :param source: Any file-like object providing write(). """ write = source.write write(pack( '>IHH', ClassFile.MAGIC, self.version.minor, self.version.major )) self._constants.pack(source) write(self.access_flags.pack()) write(pack( f'>HHH{len(self._interfaces)}H', self._this, self._super, len(self._interfaces), *self._interfaces )) self.fields.pack(source) self.methods.pack(source) self.attributes.pack(source) def _from_io(self, source: IO): """ Loads an existing JVM ClassFile from any file-like object. """ read = source.read if unpack('>I', source.read(4))[0] != ClassFile.MAGIC: raise ValueError('invalid magic number') # The version is swapped on disk to (minor, major), so swap it back. self.version = unpack('>HH', source.read(4))[::-1] self._constants.unpack(source) # ClassFile access_flags, see section #4.1 of the JVM specs. self.access_flags.unpack(read(2)) # The CONSTANT_Class indexes for "this" class and its superclass. # Interfaces are a simple list of CONSTANT_Class indexes. self._this, self._super, interfaces_count = unpack('>HHH', read(6)) self._interfaces = unpack( f'>{interfaces_count}H', read(2 * interfaces_count) ) self.fields.unpack(source) self.methods.unpack(source) self.attributes.unpack(source) @property def version(self) -> ClassVersion: """ The :class:`~jawa.cf.ClassVersion` for this class. Example:: >>> cf = ClassFile.create('HelloWorld') >>> cf.version = 51, 0 >>> print(cf.version) ClassVersion(major=51, minor=0) >>> print(cf.version.major) 51 """ return self._version @version.setter def version(self, major_minor: Union[ClassVersion, Sequence]): self._version = ClassVersion(*major_minor) @property def constants(self) -> ConstantPool: """ The :class:`~jawa.cp.ConstantPool` for this class. """ return self._constants @property def this(self) -> ConstantClass: """ The :class:`~jawa.constants.ConstantClass` which represents this class. """ return self.constants.get(self._this) @this.setter def this(self, value): self._this = value.index @property def super_(self) -> ConstantClass: """ The :class:`~jawa.constants.ConstantClass` which represents this class's superclass. """ return self.constants.get(self._super) @super_.setter def super_(self, value: ConstantClass): self._super = value.index @property def interfaces(self) -> Iterable[ConstantClass]: """ A list of direct superinterfaces of this class as indexes into the constant pool, in left-to-right order. """ return [self._constants[idx] for idx in self._interfaces] @property def bootstrap_methods(self) -> BootstrapMethod: """ Returns the bootstrap methods table from the BootstrapMethods attribute, if one exists. If it does not, one will be created. :returns: Table of `BootstrapMethod` objects. """ bootstrap = self.attributes.find_one(name='BootstrapMethods') if bootstrap is None: bootstrap = self.attributes.create( ATTRIBUTE_CLASSES['BootstrapMethods'] ) return bootstrap.table def __repr__(self): return f'<ClassFile(this={self.this.name.value!r})>'