def _stack_trace_implementation(self): return BytecodeMethod( name='fillInStackTrace', descriptor='(Ljava/lang/Throwable;)Ljava/lang/VMThrowable;', instructions=[ named_tuple_replace(Instruction.create('aload_0'), pos=1), named_tuple_replace(Instruction.create('areturn'), pos=2) ], max_locals=1, max_stack=1, args=[ObjectReferenceType('java/lang/Throwable')], is_native=False, exception_handlers=Handlers() )
def test_hello_world(loader): """ An integration test for the complete parsing of a simple HelloWorld example. The source example is as follows: .. code:: class HelloWorld { public static void main(String[] args) { System.out.println("Hello World!"); } } """ cf = loader['HelloWorld'] assert len(cf.constants) == 21 assert len(cf.attributes) == 0 assert len(cf.fields) == 0 assert len(cf.methods) == 1 main_method = cf.methods.find_one(name='main') assert main_method is not None # 0x08 for ACC_STATIC, 0x01 for ACC_PUBLIC assert main_method.access_flags.value == 0x9 assert main_method.code.max_locals == 1 assert main_method.code.max_stack == 2 instruction_list = list(main_method.code.disassemble()) assert instruction_list == [ Instruction(mnemonic='getstatic', opcode=178, operands=[Operand(op_type=30, value=13)], pos=0), Instruction(mnemonic='ldc', opcode=18, operands=[Operand(op_type=30, value=15)], pos=3), Instruction(mnemonic='invokevirtual', opcode=182, operands=[Operand(op_type=30, value=21)], pos=5), Instruction(mnemonic='return', opcode=177, operands=[], pos=8) ]
def expand_constants(ins: Instruction, *, cf) -> Instruction: """Replace CONSTANT_INDEX operands with the literal Constant object from the constant pool. :param ins: Instruction to potentially modify. :param cf: The ClassFile instance used to resolve Constants. :return: Potentially modified instruction. """ for i, operand in enumerate(ins.operands): if not isinstance(operand, Operand): continue if operand.op_type == OperandTypes.CONSTANT_INDEX: ins.operands[i] = cf.constants[operand.value] return ins
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 assert_instruction(expected=None, **kwargs): """Assert that executing the instruction returns the `expected` Actions The `**kwargs` will be passed to a `DefaultInputs` instance. """ if expected is None: expected = [] args = dict(kwargs) # instruction can be a string, coerce it to an instruction if needed instruction = args[_INSTRUCTION_KEY] if isinstance(instruction, str): instruction = Instruction.create(instruction) args[_INSTRUCTION_KEY] = instruction inputs = DefaultInputs(**args) actions = execute_instruction(inputs) assert actions.has(*expected)
def test_iinc(): original_value = 8 local_index = 2 amount_to_add = 5 locals_ = Locals(local_index + 1) locals_.store(local_index, Integer.create_instance(original_value)) instruction = Instruction.create( 'iinc', [local_operand(local_index), literal_operand(amount_to_add)]) assert_incrementing_instruction( instruction=instruction, locals=locals_, expected=[ Push(Integer.create_instance(original_value + amount_to_add)) ])
def test_compare(): ins = Instruction.create('return') assert ins == 'return' assert ins == ins assert ins != 'not_return'
from jawa.util.bytecode import Instruction, Operand, OperandTypes GOOD_TABLE_SWITCH = [ Instruction(mnemonic='iconst_1', opcode=4, operands=[], pos=0), Instruction( mnemonic='tableswitch', opcode=170, operands=[ # DEFAULT Operand(OperandTypes.BRANCH, value=30), # LOW Operand(OperandTypes.LITERAL, value=1), # HIGH Operand(OperandTypes.LITERAL, value=3), # TABLE Operand(OperandTypes.BRANCH, value=27), Operand(OperandTypes.BRANCH, value=28), Operand(OperandTypes.BRANCH, value=29) ], pos=1), Instruction(mnemonic='return', opcode=177, operands=[], pos=28), Instruction(mnemonic='return', opcode=177, operands=[], pos=29), Instruction(mnemonic='return', opcode=177, operands=[], pos=30), Instruction(mnemonic='return', opcode=177, operands=[], pos=31) ] GOOD_LOOKUP_SWITCH = [ Instruction(mnemonic='iconst_1', opcode=4, operands=[], pos=0), Instruction(mnemonic='lookupswitch', opcode=171, operands=[{
def assemble(code): """ Assemble the given iterable of mnemonics, operands, and lables. A convienience over constructing individual Instruction and Operand objects, the output of this function can be directly piped to :class:`~jawa.attributes.code.CodeAttribute.assemble()` to produce executable bytecode. As a simple example, lets produce an infinite loop: >>> from jawa.assemble import assemble, Label >>> print(list(assemble(( ... Label('start'), ... ('goto', Label('start')) ... )))) [Instruction(mnemonic='goto', opcode=167, operands=[ Operand(op_type=40, value=0)], pos=0)] For a more complex example, see examples/hello_world.py. """ final = [] # We need to make three passes, because we cannot know the offset for # jump labels until after we've figured out the PC for each instructions, # which is complicated by the variable-width instructions set and # alignment padding. for line in code: if isinstance(line, Label): final.append(line) continue mnemonic, operands = line[0], line[1:] operand_fmts = opcode_table[mnemonic]['operands'] # We need to coerce each opcodes operands into their # final `Operand` form. final_operands = [] for i, operand in enumerate(operands): if isinstance(operand, Operand): # Already in Operand form. final_operands.append(operand) elif isinstance(operand, Constant): # Convert constants into CONSTANT_INDEX'es final_operands.append(Operand( OperandTypes.CONSTANT_INDEX, operand.index )) elif isinstance(operand, dict): # lookupswitch's operand is a dict as # a special usability case. final_operands.append(operand) elif isinstance(operand, Label): final_operands.append(operand) else: # For anything else, lookup that opcode's operand # type from its definition. final_operands.append(Operand( operand_fmts[i][1], operand )) # Build the final, immutable `Instruction`. final.append(Instruction.create(mnemonic, final_operands)) label_pcs = {} # The second pass, find the absolute PC for each label. current_pc = 0 for ins in final: if isinstance(ins, Label): label_pcs[ins.name] = current_pc continue # size_on_disk must know the current pc because of alignment on # tableswitch and lookupswitch. current_pc += ins.size_on_disk(current_pc) # The third pass, now that we know where each label is we can figure # out the offset for each jump. current_pc = 0 for ins in final: if isinstance(ins, Label): continue for i, operand in enumerate(ins.operands): if isinstance(operand, dict): # lookupswitch is a special case for k, v in operand.items(): if isinstance(v, Label): operand[k] = Operand(40, label_pcs[v.name] - current_pc) elif isinstance(operand, Label): ins.operands[i] = Operand( 40, label_pcs[operand.name] - current_pc ) current_pc += ins.size_on_disk(current_pc) yield ins
def literal_instruction(name, literal): """Return an instruction named `name` that has exactly one LITERAL operand with the value `literal`""" return Instruction.create(name, [literal_operand(literal)])
def constant_instruction(name, constant): """Return an instruction named `name` that has exactly one CONSTANT_INDEX operand for the index of `constant`""" return Instruction.create(name, [constant_operand(constant)])
def _create_instruction(name, pos, operands): ops = _create_operands(operands) instruction = Instruction.create(name, ops) if pos is not None: instruction = named_tuple_replace(instruction, pos=pos) return instruction
def assemble(code): """ A convienience method for 'assembling' bytecode over the regular :meth:`~jawa.attributes.code.CodeAttribute.assemble()` method with support for labels and direct constants. """ final = [] # We need to make three passes, because we cannot know the offset for # jump labels until after we've figured out the PC for each instructions, # which is complicated by the variable-width instructions set and # alignment padding. for line in code: if isinstance(line, Label): final.append(line) continue mnemonic, operands = line[0], line[1:] operand_fmts = definition_from_mnemonic(mnemonic)[1] # We need to coerce each opcodes operands into their # final `Operand` form. final_operands = [] for i, operand in enumerate(operands): if isinstance(operand, Operand): # Already in Operand form. final_operands.append(operand) elif isinstance(operand, Constant): # Convert constants into CONSTANT_INDEX'es final_operands.append(Operand( OperandTypes.CONSTANT_INDEX, operand.index )) elif isinstance(operand, dict): # lookupswitch's operand is a dict as # a special usability case. final_operands.append(operand) elif isinstance(operand, Label): final_operands.append(operand) else: # For anything else, lookup that opcode's operand # type from its definition. final_operands.append(Operand( operand_fmts[i][1], operand )) # Build the final, immutable `Instruction`. final.append(Instruction.from_mnemonic( mnemonic, operands=final_operands )) label_pcs = {} # The second pass, find the absolute PC for each label. current_pc = 0 for ins in final: if isinstance(ins, Label): label_pcs[ins.name] = current_pc continue # size_on_disk must know the current pc because of alignment on # tableswitch and lookupswitch. current_pc += ins.size_on_disk(current_pc) # The third pass, now that we know where each label is we can figure # out the offset for each jump. current_pc = 0 offset = lambda l: Operand(40, label_pcs[l.name] - current_pc) for ins in final: if isinstance(ins, Label): continue for i, operand in enumerate(ins.operands): if isinstance(operand, dict): # lookupswitch is a special case for k, v in operand.items(): if isinstance(v, Label): operand[k] = offset(v) elif isinstance(operand, Label): ins.operands[i] = offset(operand) current_pc += ins.size_on_disk(current_pc) yield ins
FIELD_DESCRIPTOR = 'I' METHOD_NAME = 'some_method' METHOD_DESCRIPTOR = '(II)I' METHOD_KEY = MethodKey(METHOD_NAME, METHOD_DESCRIPTOR) HANDLER = ExceptionHandler( start_pc=2, end_pc=3, handler_pc=4, catch_type=EXCEPTION_NAME ) 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={
from jawa.util.bytecode import Instruction from pyjvm.core.actions import IncrementProgramCounter from pyjvm.core.class_loaders import FixedClassLoader from pyjvm.core.frame_locals import Locals from pyjvm.core.jvm_types import Integer from pyjvm.core.stack import Stack from pyjvm.instructions.instructions import InstructorInputs, execute_instruction from pyjvm.utils.utils import literal_operand, constant_operand SOME_INT = Integer.create_instance(2) _OP_STACK_KEY = 'op_stack' _INSTRUCTION_KEY = 'instruction' NON_EMPTY_INSTRUCTION_LIST = [Instruction.create('nop')] NPE_CLASS_NAME = 'java/lang/NullPointerException' CHECK_CAST_CLASS_NAME = 'java/lang/CheckCastException' def dummy_loader(): return FixedClassLoader({}) class DefaultInputs(InstructorInputs): """An `InstructorInputs` with sensible defaults This is useful for tests where no all the inputs are relevant. The instruction input must always be present, but the others can be omitted. The omitted ones will be created using the factory functions in the `DEFAULTS` dictionary.
def assemble(code): """ Assemble the given iterable of mnemonics, operands, and lables. A convienience over constructing individual Instruction and Operand objects, the output of this function can be directly piped to :class:`~jawa.attributes.code.CodeAttribute.assemble()` to produce executable bytecode. As a simple example, lets produce an infinite loop: >>> from jawa.assemble import assemble, Label >>> print(list(assemble(( ... Label('start'), ... ('goto', Label('start')) ... )))) [Instruction(mnemonic='goto', opcode=167, operands=[ Operand(op_type=40, value=0)], pos=0)] For a more complex example, see examples/hello_world.py. """ final = [] # We need to make three passes, because we cannot know the offset for # jump labels until after we've figured out the PC for each instructions, # which is complicated by the variable-width instructions set and # alignment padding. for line in code: if isinstance(line, Label): final.append(line) continue mnemonic, operands = line[0], line[1:] operand_fmts = definition_from_mnemonic(mnemonic)[1] # We need to coerce each opcodes operands into their # final `Operand` form. final_operands = [] for i, operand in enumerate(operands): if isinstance(operand, Operand): # Already in Operand form. final_operands.append(operand) elif isinstance(operand, Constant): # Convert constants into CONSTANT_INDEX'es final_operands.append(Operand(OperandTypes.CONSTANT_INDEX, operand.index)) elif isinstance(operand, dict): # lookupswitch's operand is a dict as # a special usability case. final_operands.append(operand) elif isinstance(operand, Label): final_operands.append(operand) else: # For anything else, lookup that opcode's operand # type from its definition. final_operands.append(Operand(operand_fmts[i][1], operand)) # Build the final, immutable `Instruction`. final.append(Instruction.from_mnemonic(mnemonic, operands=final_operands)) label_pcs = {} # The second pass, find the absolute PC for each label. current_pc = 0 for ins in final: if isinstance(ins, Label): label_pcs[ins.name] = current_pc continue # size_on_disk must know the current pc because of alignment on # tableswitch and lookupswitch. current_pc += ins.size_on_disk(current_pc) # The third pass, now that we know where each label is we can figure # out the offset for each jump. current_pc = 0 offset = lambda l: Operand(40, label_pcs[l.name] - current_pc) for ins in final: if isinstance(ins, Label): continue for i, operand in enumerate(ins.operands): if isinstance(operand, dict): # lookupswitch is a special case for k, v in operand.items(): if isinstance(v, Label): operand[k] = offset(v) elif isinstance(operand, Label): ins.operands[i] = offset(operand) current_pc += ins.size_on_disk(current_pc) yield ins