示例#1
0
    def test_extreme_compute_jumps_convergence(self):
        """Test of compute_jumps() requiring absurd number of passes.

        NOTE:  This test also serves to demonstrate that there is no worst
        case: the number of passes can be unlimited (or, actually, limited by
        the size of the provided code).

        This is an extension of test_compute_jumps_convergence.  Instead of
        two jumps, where the earlier gets extended after the latter, we
        instead generate a series of many jumps.  Each pass of compute_jumps()
        extends one more instruction, which in turn causes the one behind it
        to be extended on the next pass.
        """
        if not WORDCODE:
            return

        # N: the number of unextended instructions that can be squeezed into a
        # set of bytes adressable by the arg of an unextended instruction.
        # The answer is "128", but here's how we arrive at it (and it also
        # hints at how to make this work for pre-WORDCODE).
        max_unextended_offset = 1 << 8
        unextended_branch_instr_size = 2
        N = max_unextended_offset // unextended_branch_instr_size

        nop = 'UNARY_POSITIVE'  # don't use NOP, dis.stack_effect will raise

        # The number of jumps will be equal to the number of labels.  The
        # number of passes of compute_jumps() required will be one greater
        # than this.
        labels = [Label() for x in range(0, 3 * N)]

        code = Bytecode()
        code.extend(
            Instr('JUMP_FORWARD', labels[len(labels) - x - 1])
            for x in range(0, len(labels)))
        end_of_jumps = len(code)
        code.extend(Instr(nop) for x in range(0, N))

        # Now insert the labels.  The first is N instructions (i.e. 256
        # bytes) after the last jump.  Then they proceed to earlier positions
        # 4 bytes at a time.  While the targets are in the range of the nop
        # instructions, 4 bytes is two instructions.  When the targets are in
        # the range of JUMP_FORWARD instructions we have to allow for the fact
        # that the instructions will have been extended to four bytes each, so
        # working backwards 4 bytes per label means just one instruction per
        # label.
        offset = end_of_jumps + N
        for l in range(0, len(labels)):
            code.insert(offset, labels[l])
            if offset <= end_of_jumps:
                offset -= 1
            else:
                offset -= 2

        code.insert(0, Instr("LOAD_CONST", 0))
        del end_of_jumps
        code.append(Instr('RETURN_VALUE'))

        code.to_code(compute_jumps_passes=(len(labels) + 1))
示例#2
0
    def test_extreme_compute_jumps_convergence(self):
        """Test of compute_jumps() requiring absurd number of passes.

        NOTE:  This test also serves to demonstrate that there is no worst
        case: the number of passes can be unlimited (or, actually, limited by
        the size of the provided code).

        This is an extension of test_compute_jumps_convergence.  Instead of
        two jumps, where the earlier gets extended after the latter, we
        instead generate a series of many jumps.  Each pass of compute_jumps()
        extends one more instruction, which in turn causes the one behind it
        to be extended on the next pass.
        """
        if not WORDCODE:
            return

        # N: the number of unextended instructions that can be squeezed into a
        # set of bytes adressable by the arg of an unextended instruction.
        # The answer is "128", but here's how we arrive at it (and it also
        # hints at how to make this work for pre-WORDCODE).
        max_unextended_offset = 1 << 8
        unextended_branch_instr_size = 2
        N = max_unextended_offset // unextended_branch_instr_size

        nop = 'UNARY_POSITIVE'   # don't use NOP, dis.stack_effect will raise

        # The number of jumps will be equal to the number of labels.  The
        # number of passes of compute_jumps() required will be one greater
        # than this.
        labels = [Label() for x in range(0, 3 * N)]

        code = Bytecode()
        code.extend(Instr('JUMP_FORWARD', labels[len(labels) - x - 1])
                    for x in range(0, len(labels)))
        end_of_jumps = len(code)
        code.extend(Instr(nop) for x in range(0, N))

        # Now insert the labels.  The first is N instructions (i.e. 256
        # bytes) after the last jump.  Then they proceed to earlier positions
        # 4 bytes at a time.  While the targets are in the range of the nop
        # instructions, 4 bytes is two instructions.  When the targets are in
        # the range of JUMP_FORWARD instructions we have to allow for the fact
        # that the instructions will have been extended to four bytes each, so
        # working backwards 4 bytes per label means just one instruction per
        # label.
        offset = end_of_jumps + N
        for l in range(0, len(labels)):
            code.insert(offset, labels[l])
            if offset <= end_of_jumps:
                offset -= 1
            else:
                offset -= 2

        code.insert(0, Instr("LOAD_CONST", 0))
        del end_of_jumps
        code.append(Instr('RETURN_VALUE'))

        code.to_code(compute_jumps_passes=(len(labels) + 1))
示例#3
0
def insert_mark(instrs: b.Bytecode, last_i):
    """Inserts a mark after the call instruction that last_i points to."""
    current_offset = 0
    for index, instr in enumerate(instrs):
        # Only increases offset if it's a real instruction.
        if not isinstance(instr, b.Instr):
            continue

        # When calling a function, there are two possible locations that
        # caller_frame.last_i can point to:
        #
        #   1. The CALL_XXX instruction. This is the "correct" behavior defined in
        #      https://docs.python.org/3.7/library/inspect.html#types-and-members.
        #
        #   2. The instruction just before CALL_XXX. This is caused by the "PREDICT"
        #      optimization, described in
        #      https://github.com/python/cpython/blob/v3.7.5/Python/ceval.c#L888-L900
        #      Note that in this case, The instruction last_i points to must not be
        #      another call.
        #
        # We want to find the CALL_XXX instruction that triggers the 'call' event, and
        # that is either at offset last_i or last_i + 2.
        if current_offset < last_i:
            current_offset += 2  # Since Cyberbrain is Python 3.6+ only, it's always 2.
            continue

        if current_offset == last_i and utils.is_call_instruction(instr):
            break

        if current_offset == last_i + 2 and utils.is_call_instruction(instr):
            break

        if current_offset > last_i + 2:
            raise RuntimeError("No way the program reaches here.")

    # Inserts __MARK__ after CALL_XXX.
    instrs.insert(index + 1, b.Instr("LOAD_ATTR", MARK))