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)
def test_ldc(): value = 5.0 constants = ConstantPool() constant = constants.create_double(value) assert_incrementing_instruction( instruction=constant_instruction('ldc', constant), constants=constants, expected=[Push(Double.create_instance(value))])
def search_constant_pool(self, *, path: str, **options): """Partially load the class at `path`, yield all matching constants from the ConstantPool. This is an optimization method that does not load a complete ClassFile, nor does it add the results to the ClassLoader cache. :param path: Fully-qualified path to a ClassFile. :param options: A list of options to pass into `ConstantPool.find()` """ with self.open(f'{path}.class') as source: # Skip over the magic, minor, and major version. source.read(8) pool = ConstantPool() pool.unpack(source) yield from pool.find(**options)
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)
def test_invoke_v(): method_name = 'method_name' class_name = 'class_name' consts = ConstantPool() descriptor = '(II)V' key = MethodKey(method_name, descriptor) no_op = Instruction.create('nop') method = BytecodeMethod( name='method_name', descriptor='(II)V', max_locals=5, max_stack=5, instructions=[no_op, no_op], args=[Integer, Integer], ) jvm_class = JvmClass( class_name, RootObjectType.refers_to, consts, methods={ key: method } ) method_ref = consts.create_method_ref(class_name, method_name, descriptor) instruction = constant_instruction('invokevirtual', method_ref) loader = FixedClassLoader({ class_name: jvm_class }) instance = loader.default_instance(class_name) arg_value = SOME_INT arguments = [instance, arg_value, arg_value] reversed_arguments = list(reversed(arguments)) assert_instruction( constants=consts, loader=loader, instruction=instruction, op_stack=reversed_arguments, expected=[ Pop(3), Invoke(class_name, key, arguments) ] )
def test_string_constant(): text = 'some_text' consts = ConstantPool() const = consts.create_string(text) chars = [ord(c) for c in text] char_array = ArrayReferenceType(Integer).create_instance(chars) hash_value = hash(text) % (2**32) hash_ = Integer.create_instance(hash_value) reference_type = ObjectReferenceType('java/lang/String') jvm_object = JvmObject({'hash': hash_, 'value': char_array}) assert_incrementing_instruction( instruction=constant_instruction('ldc', const), constants=consts, expected=[Push(reference_type.create_instance(jvm_object))])
def complex_machine(): machine = Machine(FixedClassLoader({ COMPLEX_CLASS_NAME: COMPLEX_CLASS, EXCEPTION_NAME: JvmClass( name=EXCEPTION_NAME, name_of_base=RootObjectType.refers_to, constants=ConstantPool() ), RootObjectType.refers_to: JvmClass( name=RootObjectType.refers_to, name_of_base=None, constants=ConstantPool() ) })) for _ in range(2): frame = Frame.from_class_and_method(COMPLEX_CLASS, METHOD) machine.frames.push(frame) return machine
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)
def test_printable_constants(): # Ensure we can successfully repr valid constants without crashing. pool = ConstantPool() repr(pool.create_utf8('HelloWorld')) repr(pool.create_class('HelloWorld')) repr(pool.create_double(1)) repr(pool.create_float(1)) repr(pool.create_integer(1)) repr(pool.create_long(1)) repr(pool.create_name_and_type('HelloWorld', 'I')) repr(pool.create_field_ref('HelloWorld', 'test', 'I')) repr(pool.create_method_ref('HelloWorld', 'test', 'I)V')) repr(pool.create_interface_method_ref( 'HelloWorld', 'test', 'I)V' )) repr(pool.create_string('HelloWorld'))
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 method 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).index cf._super = cf.constants.create_class(super_).index 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._to_io(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._to_io(fout) self._methods._to_io(fout) self._attributes._to_io(fout) # ------------ # Internal # ------------ 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._from_io(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._from_io(fio) self._methods._from_io(fio) self._attributes._from_io(fio) # ------------- # Properties # ------------- @property def version(self): """ The :class:`~jawa.cf.ClassVersion` for this class. Example:: >>> 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)
METHOD = BytecodeMethod( name='method_name', descriptor='(II)V', instructions=[ named_tuple_replace(Instruction.create('nop'), pos=i) for i in range(5) ], max_locals=5, max_stack=15, args=[Integer, Integer], exception_handlers=Handlers([HANDLER]) ) COMPLEX_CLASS = JvmClass( name=COMPLEX_CLASS_NAME, name_of_base=RootObjectType.refers_to, constants=ConstantPool(), fields={ FIELD_NAME: Integer }, static_fields={ FIELD_NAME: Integer }, methods={ METHOD_KEY: METHOD } ) def complex_machine(): machine = Machine(FixedClassLoader({ COMPLEX_CLASS_NAME: COMPLEX_CLASS,
def test_printable_constants(): # Ensure we can successfully repr valid constants without crashing. pool = ConstantPool() repr(pool.create_utf8('HelloWorld')) repr(pool.create_class('HelloWorld')) repr(pool.create_double(1)) repr(pool.create_float(1)) repr(pool.create_integer(1)) repr(pool.create_long(1)) repr(pool.create_name_and_type('HelloWorld', 'I')) repr(pool.create_field_ref('HelloWorld', 'test', 'I')) repr(pool.create_method_ref('HelloWorld', 'test', 'I)V')) repr(pool.create_interface_method_ref('HelloWorld', 'test', 'I)V')) repr(pool.create_string('HelloWorld'))
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})>'