Example #1
0
File: code.py Project: TkTech/Jawa
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}
        )
Example #2
0
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})>'
Example #3
0
File: cf.py Project: fonkap/Jawa
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)
Example #4
0
File: code.py Project: nexB/Lawu
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
            })
Example #5
0
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)
Example #6
0
File: code.py Project: fonkap/Jawa
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
Example #7
0
File: methods.py Project: nexB/Lawu
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)
Example #8
0
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)
Example #9
0
File: fields.py Project: nexB/Lawu
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)
Example #10
0
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)
Example #11
0
File: cf.py Project: TkTech/Jawa
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})>'