Exemple #1
0
    def ascode(self, func_name: str, argcount: int) -> CodeType:
        code = bytes(self)
        stacksize = max(
            accumulate(
                stack_effect(op, arg
                             ) if op >= HAVE_ARGUMENT else stack_effect(op)
                for op, arg in zip(code[::2], code[1::2])))

        return CodeType(
            argcount,
            0,  # posonlyargcount
            0,  # kwonlyargcount
            len(self.varnames),
            stacksize,  # stacksize
            (CompilerFlags.OPTIMIZED | CompilerFlags.NEWLOCALS),
            code,
            tuple(self.consts),  # insertion order
            tuple(self.names),
            tuple(self.varnames),
            "",
            func_name,
            0,
            bytes(),
            tuple(self.freevars),
            tuple(self.cellvars),
        )
Exemple #2
0
def resolve_stacksize(insts):
    max_stacksize = 0
    pending = [(0, 0)]

    while pending:
        (i, stacksize), *pending = pending
        inst = insts[i]
        if isinstance(inst, Label):
            if inst.stacksize is None:
                inst.stacksize = stacksize
            else:
                assert stacksize == inst.stacksize
                continue
            i += 1
        else:
            assert i == 0

        while True:
            inst = insts[i]
            if isinstance(inst, Label):
                if inst.stacksize is None:
                    inst.stacksize = stacksize
                assert inst.stacksize == stacksize
            elif isinstance(inst, Instruction):
                if inst.opcode == opmap["RETURN_VALUE"]:
                    assert stacksize == 1
                    break
                if inst.opcode == opmap["JUMP_ABSOLUTE"]:
                    pending.append((insts.index(inst.arg), stacksize))
                    break

                if inst.opcode in hasconst:
                    stacksize += stack_effect(inst.opcode, inst.slot)
                elif inst.opcode < HAVE_ARGUMENT:
                    stacksize += stack_effect(inst.opcode)
                elif inst.opcode not in hasjrel and inst.opcode not in hasjabs:
                    stacksize += stack_effect(inst.opcode, inst.arg)
                else:
                    notjump, jump = _stack_effect[opname[inst.opcode]]
                    jump += stacksize
                    pending.append((insts.index(inst.arg), jump))
                    max_stacksize = max(jump, max_stacksize)
                    stacksize += notjump

            i += 1
            max_stacksize = max(stacksize, max_stacksize)

    return max_stacksize
Exemple #3
0
  def get_stack_effect(self) -> int:
    # dis.stack_effect does not work for EXTENDED_ARG and NOP
    if self.mnemonic in ["EXTENDED_ARG", "NOP"]:
      return 0

    return dis.stack_effect(self.opcode,
                            (self.arg if self.has_argument() else None))
Exemple #4
0
    def stack_effect(self):
        """
        The net effect of executing this instruction on the interpreter stack.

        Instructions that pop values off the stack have negative stack effect
        equal to the number of popped values.

        Instructions that push values onto the stack have positive stack effect
        equal to the number of popped values.

        Examples
        --------
        - LOAD_{FAST,NAME,GLOBAL,DEREF} push one value onto the stack.
          They have a stack_effect of 1.
        - POP_JUMP_IF_{TRUE,FALSE} always pop one value off the stack.
          They have a stack effect of -1.
        - BINARY_* instructions pop two instructions off the stack, apply a
          binary operator, and push the resulting value onto the stack.
          They have a stack effect of -1 (-2 values consumed + 1 value pushed).
        """
        if self.opcode == NOP.opcode:  # noqa
            # dis.stack_effect is broken here
            return 0

        return stack_effect(
            self.opcode,
            *((self.arg if isinstance(self.arg, int) else 0, )
              if self.have_arg else ()))
Exemple #5
0
def stack_effect(opcode, oparg=None):
    "Return the stack effect as seen by the following instruction."
    if opcode in or_pop_instructions:
        # N.B. these are 0 in the dis version; -1 if jump not taken
        return -1
    else:
        return dis.stack_effect(opcode, oparg if isinstance(oparg, (int, type(None))) else 0)
    def stack_effect(self):
        """
        The net effect of executing this instruction on the interpreter stack.

        Instructions that pop values off the stack have negative stack effect
        equal to the number of popped values.

        Instructions that push values onto the stack have positive stack effect
        equal to the number of popped values.

        Examples
        --------
        - LOAD_{FAST,NAME,GLOBAL,DEREF} push one value onto the stack.
          They have a stack_effect of 1.
        - POP_JUMP_IF_{TRUE,FALSE} always pop one value off the stack.
          They have a stack effect of -1.
        - BINARY_* instructions pop two instructions off the stack, apply a
          binary operator, and push the resulting value onto the stack.
          They have a stack effect of -1 (-2 values consumed + 1 value pushed).
        """
        return stack_effect(
            self.opcode,
            *((self.arg if isinstance(self.arg, int) else 0,)
              if self.have_arg else ())
        )
Exemple #7
0
def stack_effect(opcode, oparg=None):
    "Return the stack effect as seen by the following instruction."
    if opcode in or_pop_instructions:
        # N.B. these are 0 in the dis version; -1 if jump not taken
        return -1
    else:
        return dis.stack_effect(opcode, oparg if isinstance(oparg, (int, type(None))) else 0)
Exemple #8
0
    def stack_effect(self, jump=None):
        if self._opcode < _opcode.HAVE_ARGUMENT:
            arg = None
        elif not isinstance(self._arg, int) or self._opcode in _opcode.hasconst:
            # Argument is either a non-integer or an integer constant,
            # not oparg.
            arg = 0
        else:
            arg = self._arg

        if sys.version_info < (3, 8):
            effect = _stack_effects.get(self._opcode, None)
            if effect is not None:
                return max(effect) if jump is None else effect[jump]
            return dis.stack_effect(self._opcode, arg)
        else:
            return dis.stack_effect(self._opcode, arg, jump=jump)
Exemple #9
0
 def stack_effect(self):
     '''not exact.
     see https://github.com/python/cpython/blob/master/Python/compile.c#L860'''
     if self.opname in ('SETUP_EXCEPT', 'SETUP_FINALLY', 'POP_EXCEPT', 'END_FINALLY'):
         assert False, 'for all we know. we assume no exceptions'
     if self.is_raise:
         # if we wish to analyze exception path, we should break to except: and push 3, or somthing.
         return -1
     if self.opname == 'BREAK_LOOP' and self.argrepr.startswith('FOR'):
         return -1
     return dis.stack_effect(self.opcode, self.arg) 
Exemple #10
0
def check_stack_effect():
    import dis
    from xdis import IS_PYPY
    from xdis.op_imports import get_opcode_module

    if IS_PYPY:
        variant = "pypy"
    else:
        variant = ""
    opc = get_opcode_module(None, variant)
    for (
        opname,
        opcode,
    ) in opc.opmap.items():
        if opname in ("EXTENDED_ARG", "NOP"):
            continue
        xdis_args = [opcode, opc]
        dis_args = [opcode]
        if op_has_argument(opcode, opc):
            xdis_args.append(0)
            dis_args.append(0)
        if (
            PYTHON_VERSION_TRIPLE > (3, 7)
            and opcode in opc.CONDITION_OPS
            and opname
            not in (
                "JUMP_IF_FALSE_OR_POP",
                "JUMP_IF_TRUE_OR_POP",
                "POP_JUMP_IF_FALSE",
                "POP_JUMP_IF_TRUE",
                "SETUP_FINALLY",
            )
        ):
            xdis_args.append(0)
            dis_args.append(0)

        effect = xstack_effect(*xdis_args)
        check_effect = dis.stack_effect(*dis_args)
        if effect == -100:
            print(
                "%d (%s) needs adjusting; should be: should have effect %d"
                % (opcode, opname, check_effect)
            )
        elif check_effect == effect:
            pass
            # print("%d (%s) is good: effect %d" % (opcode, opname, effect))
        else:
            print(
                "%d (%s) not okay; effect %d vs %d"
                % (opcode, opname, effect, check_effect)
            )
            pass
        pass
    return
Exemple #11
0
	def _compute_stacksize(self):
		code = self.code
		label_pos = {op[0]:pos for pos,op in enumerate(code) if isinstance(op[0],Label)}
		# sf_targets are the targets of SETUP_FINALLY opcodes. They are recorded
		# because they have special stack behaviour. If an exception was raised
		# in the block pushed by a SETUP_FINALLY opcode, the block is popped
		# and 3 objects are pushed. On return or continue, the block is popped
		# and 2 objects are pushed. If nothing happened, the block is popped by
		# a POP_BLOCK opcode and 1 object is pushed by a (LOAD_CONST, None)
		# operation
		# Our solution is to record the stack state of SETUP_FINALLY targets
		# as having 3 objects pushed, which is the maximum. However, to make
		# stack recording consistent, the get_next_stacks function will always
		# yield the stack state of the target as if 1 object was pushed, but
		# this will be corrected in the actual stack recording
		sf_targets={label_pos[arg] for op,arg in code if op==SETUP_FINALLY}
		stacks=[None]*len(code)
		maxsize=0
		op=[(0,(0,))]
		def newstack(n):
			if curstack[-1]<-n:raise ValueError("Popped a non-existing element at %s %s"%(pos,code[pos-3:pos+2]))
			return curstack[:-1]+(curstack[-1]+n,)
		while op:
			pos,curstack=op.pop()
			o=sum(curstack)
			if o>maxsize:maxsize=o
			o,arg=code[pos]
			if isinstance(o,Label):
				if pos in sf_targets:curstack=curstack[:-1]+(curstack[-1]+2,)
				if stacks[pos] is None:
					stacks[pos]=curstack
					if o not in (BREAK_LOOP,RETURN_VALUE,RAISE_VARARGS,STOP_CODE):
						pos+=1
						if not isopcode(o):op+=(pos,curstack),
						elif o not in hasflow:op+=(pos,newstack(stack_effect(o,arg))),
						elif o == FOR_ITER:op+=(label_pos[arg],newstack(-1)),(pos,newstack(1))
						elif o in (JUMP_FORWARD,JUMP_ABSOLUTE):op+=(label_pos[arg],curstack),
						elif o in (POP_JUMP_IF_FALSE,POP_JUMP_IF_TRUE):
							curstack=newstack(-1)
							op+=(label_pos[arg],curstack),(pos,curstack)
						elif o in (JUMP_IF_FALSE_OR_POP,JUMP_IF_TRUE_OR_POP):op+=(label_pos[arg],curstack),(pos,newstack(-1))
						elif o == CONTINUE_LOOP:op+=(label_pos[arg],curstack[:-1]),
						elif o == SETUP_LOOP:op+=(pos,curstack+(0,)),(label_pos[arg],curstack)
						elif o == SETUP_EXCEPT:op+=(pos,curstack+(0,)),(label_pos[arg],newstack(3))
						elif o == SETUP_FINALLY:op+=(pos,curstack+(0,)),(label_pos[arg],newstack(1))
						elif o == POP_BLOCK:op+=(pos,curstack[:-1]),
						elif o == END_FINALLY:op+=(pos,newstack(-3)),
						elif o == WITH_CLEANUP:op+=(pos,newstack(-1)),
						else:raise ValueError("Unhandled opcode %s"%op)
				elif stacks[pos]!=curstack:
					op=pos+1
					while code[op][0] not in hasflow:op+=1
					if code[op][0] not in (RETURN_VALUE,RAISE_VARARGS,STOP_CODE):raise ValueError("Inconsistent code at %s %s %s\n%s"%(pos,curstack,stacks[pos],code[pos-5:pos+4]))
		return maxsize
def expr_that_added_elem_to_stack(instructions: List[Instruction],
                                  start_index: int, stack_pos: int):
    """Backwards traverse instructions

    Backwards traverse the instructions starting at `start_index` until we find the
    instruction that added the element at stack position `stack_pos` (where 0 means top
    of stack). For example, if the instructions are:

    ```
    0: LOAD_GLOBAL              0 (func)
    1: LOAD_CONST               1 (42)
    2: CALL_FUNCTION            1
    ```

    We can look for the function that is called by invoking this function with
    `start_index = 1` and `stack_pos = 1`. It will see that `LOAD_CONST` added the top
    element to the stack, and find that `LOAD_GLOBAL` was the instruction to add element
    in stack position 1 to the stack -- so `expr_from_instruction(instructions, 0)` is
    returned.

    It is assumed that if `stack_pos == 0` then the instruction you are looking for is
    the one at `instructions[start_index]`. This might not hold, in case of using `NOP`
    instructions.

    If any jump instruction is found, `SomethingInvolvingScaryBytecodeJump` is returned
    immediately. (since correctly process the bytecode when faced with jumps is not as
    straight forward).
    """
    if DEBUG:
        LOGGER.debug(
            f"find_inst_that_added_elem_to_stack start_index={start_index} stack_pos={stack_pos}"
        )
    assert stack_pos >= 0
    for inst in reversed(instructions[:start_index + 1]):
        # Return immediately if faced with a jump
        if inst.opcode in dis.hasjabs or inst.opcode in dis.hasjrel:
            return SomethingInvolvingScaryBytecodeJump(inst.opname)

        if stack_pos == 0:
            if DEBUG:
                LOGGER.debug(f"Found it: {inst}")
            found_index = instructions.index(inst)
            break
        old = stack_pos
        stack_pos -= dis.stack_effect(inst.opcode, inst.arg)
        new = stack_pos
        if DEBUG:
            LOGGER.debug(f"Skipping ({old} -> {new}) {inst}")
    else:
        raise Exception("inst_index_for_stack_diff failed")

    return expr_from_instruction(instructions, found_index)
Exemple #13
0
    def stack_effect(self, jump=None):
        effect = _stack_effects.get(self._opcode, None)
        if effect is not None:
            return max(effect) if jump is None else effect[jump]

        # TODO: if dis.stack_effect ever expands to take the 'jump' parameter
        # then we should pass that through, and perhaps remove some of the
        # overrides that are set up in _init_stack_effects()

        # All opcodes whose arguments are not represented by integers have
        # a stack_effect indepent of their argument.
        arg = (self._arg if isinstance(self._arg, int) else
               0 if self._opcode >= _opcode.HAVE_ARGUMENT else None)
        return dis.stack_effect(self._opcode, arg)
    def test_one(xdis_args, dis_args, has_arg):
        effect = xstack_effect(*xdis_args)
        check_effect = dis.stack_effect(*dis_args)
        assert effect != -100, (
            "%d (%s) needs adjusting; should be: should have effect %d" %
            (opcode, opname, check_effect))
        if has_arg:
            op_val = "with operand %d" % dis_args[1]
        else:
            op_val = ""

        assert check_effect == effect, (
            "%d (%s) %s not okay; effect %d vs %d" %
            (opcode, opname, op_val, effect, check_effect))
        print("%d (%s) is good: effect %d" % (opcode, opname, effect))
Exemple #15
0
def compute_stack_effect(decomp, jmp_tbl, end, start):
    SE = 0
    idx = 0
    segment = decomp[start + 1: end]
    while idx < len(segment):
        (op, arg) = lst = segment[idx]
        isjump = 'JUMP' in op
        SE += dis.stack_effect(
            dis.opmap[op],
            arg if dis.opmap[op] > dis.HAVE_ARGUMENT else None,
            jump = isjump
        )
        if isjump:
            idx = find(segment, jmp_tbl[id(lst)])
        else:
            idx += 1
    return SE
Exemple #16
0
    def stack_effect(self, jump=None):
        effect = _stack_effects.get(self.opcode, None)
        if effect is not None:
            return max(effect) if jump is None else effect[jump]

        # TODO: if dis.stack_effect ever expands to take the 'jump' parameter
        # then we should pass that through, and perhaps remove some of the
        # overrides that are set up in _init_stack_effects()

        # Each of following opcodes has a stack_effect indepent of its
        # argument:
        # 1. Whose argument is not represented by an integer.
        # 2. Whose stack effect can be calculated without using oparg
        #    from this link:
        # https://github.com/python/cpython/blob/master/Python/compile.c#L859

        use_oparg = self.opcode in _stack_effects_use_opargs
        arg = (self._arg if use_oparg and isinstance(self._arg, int) else
               0 if self.opcode >= opcode.HAVE_ARGUMENT else None)
        return dis.stack_effect(self.opcode, arg)
Exemple #17
0
 def stack_effect(self, arg=None):
     return dis.stack_effect(self.value, arg)
Exemple #18
0
    def _compute_stacksize(self, logging=False):
        code = self.code
        label_pos = {op[0]: pos for pos, op in enumerate(code) if isinstance(op[0], Label)}
        # sf_targets are the targets of SETUP_FINALLY opcodes. They are recorded
        # because they have special stack behaviour. If an exception was raised
        # in the block pushed by a SETUP_FINALLY opcode, the block is popped
        # and 3 objects are pushed. On return or continue, the block is popped
        # and 2 objects are pushed. If nothing happened, the block is popped by
        # a POP_BLOCK opcode and 1 object is pushed by a (LOAD_CONST, None)
        # operation
        # Our solution is to record the stack state of SETUP_FINALLY targets
        # as having 3 objects pushed, which is the maximum. However, to make
        # stack recording consistent, the get_next_stacks function will always
        # yield the stack state of the target as if 1 object was pushed, but
        # this will be corrected in the actual stack recording
        if version_info < (3, 5):
            sf_targets = {label_pos[arg] for op, arg in code
                          if (op == SETUP_FINALLY or op == SETUP_WITH)}
        else:
            sf_targets = {label_pos[arg] for op, arg in code
                          if (op == SETUP_FINALLY or op == SETUP_WITH or op == SETUP_ASYNC_WITH)}

        states = [None] * len(code)
        maxsize = 0

        class BlockType(Enum):
            DEFAULT = 0,
            TRY_FINALLY = 1,
            TRY_EXCEPT = 2,
            LOOP_BODY = 3,
            WITH_BLOCK = 4,
            EXCEPTION = 5,
            SILENCED_EXCEPTION_BLOCK = 6,

        class State:

            def __init__(self, pos=0, stack=(0,), block_stack=(BlockType.DEFAULT,), log=[]):
                self._pos = pos
                self._stack = stack
                self._block_stack = block_stack
                self._log = log

            @property
            def pos(self):
                return self._pos

            @property
            def stack(self):
                return self._stack

            @stack.setter
            def stack(self, val):
                self._stack = val

            def newstack(self, n):
                if self._stack[-1] < -n:
                    raise ValueError("Popped a non-existing element at %s %s" %
                                     (self._pos, code[self._pos - 4: self._pos + 3]))
                return self._stack[:-1] + (self._stack[-1] + n,)

            @property
            def block_stack(self):
                return self._block_stack

            @property
            def log(self):
                return self._log

            def newlog(self, msg):
                if not logging:
                    return None

                log_msg = str(self._pos) + ": " + msg
                if self._stack:
                    log_msg += " (on stack: "
                    log_depth = 2
                    log_depth = min(log_depth, len(self._stack))
                    for pos in range(-1, -log_depth, -1):
                        log_msg += str(self._stack[pos]) + ", "
                    log_msg += str(self._stack[-log_depth])
                    log_msg += ")"
                else:
                    log_msg += " (empty stack)"
                return [log_msg] + self._log

        op = [State()]

        while op:
            cur_state = op.pop()
            o = sum(cur_state.stack)
            if o > maxsize:
                maxsize = o

            o, arg = code[cur_state.pos]

            if isinstance(o, Label):
                if cur_state.pos in sf_targets:
                    cur_state.stack = cur_state.newstack(5)
                if states[cur_state.pos] is None:
                    states[cur_state.pos] = cur_state
                elif states[cur_state.pos].stack != cur_state.stack:
                    check_pos = cur_state.pos + 1
                    while code[check_pos][0] not in hasflow:
                        check_pos += 1
                    if code[check_pos][0] not in (RETURN_VALUE, RAISE_VARARGS, STOP_CODE):
                        if cur_state.pos not in sf_targets:
                            raise ValueError("Inconsistent code at %s %s %s\n%s" %
                                             (cur_state.pos, cur_state.stack, states[cur_state.pos].stack,
                                              code[cur_state.pos - 5:cur_state.pos + 4]))
                        else:
                            # SETUP_FINALLY target inconsistent code!
                            #
                            # Since Python 3.2 assigned exception is cleared at the end of
                            # the except clause (named exception handler).
                            # To perform this CPython (checked in version 3.4.3) adds special
                            # bytecode in exception handler which currently breaks 'regularity' of bytecode.
                            # Exception handler is wrapped in try/finally block and POP_EXCEPT opcode
                            # is inserted before END_FINALLY, as a result cleanup-finally block is executed outside
                            # except handler. It's not a bug, as it doesn't cause any problems during execution, but
                            # it breaks 'regularity' and we can't check inconsistency here. Maybe issue should be
                            # posted to Python bug tracker.
                            pass
                    continue
                else:
                    continue

            if o not in (BREAK_LOOP, RETURN_VALUE, RAISE_VARARGS, STOP_CODE):
                next_pos = cur_state.pos + 1

                if not isopcode(o):
                    op += State(next_pos, cur_state.stack, cur_state.block_stack, cur_state.log),

                elif o not in hasflow:
                    if o in (LOAD_GLOBAL, LOAD_CONST, LOAD_NAME, LOAD_FAST, LOAD_ATTR, LOAD_DEREF,
                             LOAD_CLASSDEREF, LOAD_CLOSURE,
                             STORE_GLOBAL, STORE_NAME, STORE_FAST, STORE_ATTR, STORE_DEREF,
                             DELETE_GLOBAL, DELETE_NAME, DELETE_FAST, DELETE_ATTR, DELETE_DEREF,
                             IMPORT_NAME, IMPORT_FROM, COMPARE_OP):
                        se = stack_effect(o, 0)
                    else:
                        se = stack_effect(o, arg)

                    log = cur_state.newlog("non-flow command (" + str(o) + ", se = " + str(se) + ")")
                    op += State(next_pos, cur_state.newstack(se), cur_state.block_stack, log),

                elif o == FOR_ITER:
                    inside_for_log = cur_state.newlog("FOR_ITER (+1)")
                    op += State(label_pos[arg], cur_state.newstack(-1), cur_state.block_stack, cur_state.log),\
                          State(next_pos, cur_state.newstack(1), cur_state.block_stack, inside_for_log)

                elif o in (JUMP_FORWARD, JUMP_ABSOLUTE):
                    after_jump_log = cur_state.newlog(str(o))
                    op += State(label_pos[arg], cur_state.stack, cur_state.block_stack, after_jump_log),

                elif o in (JUMP_IF_FALSE_OR_POP, JUMP_IF_TRUE_OR_POP):
                    after_jump_log = cur_state.newlog(str(o) + ", jumped")
                    log = cur_state.newlog(str(o) + ", not jumped (-1)")
                    op += State(label_pos[arg], cur_state.stack, cur_state.block_stack, after_jump_log),\
                          State(next_pos, cur_state.newstack(-1), cur_state.block_stack, log)

                elif o in {POP_JUMP_IF_TRUE, POP_JUMP_IF_FALSE}:
                    after_jump_log = cur_state.newlog(str(o) + ", jumped (-1)")
                    log = cur_state.newlog(str(o) + ", not jumped (-1)")
                    op += State(label_pos[arg], cur_state.newstack(-1), cur_state.block_stack, after_jump_log),\
                          State(next_pos, cur_state.newstack(-1), cur_state.block_stack, log)

                elif o == CONTINUE_LOOP:
                    next_stack, next_block_stack = cur_state.stack, cur_state.block_stack
                    last_popped_block = None
                    while next_block_stack[-1] != BlockType.LOOP_BODY:
                        last_popped_block = next_block_stack[-1]
                        next_stack, next_block_stack = next_stack[:-1], next_block_stack[:-1]

                    if next_stack != cur_state.stack:
                        log = cur_state.newlog("CONTINUE_LOOP, from non-loop block")
                    else:
                        log = cur_state.newlog("CONTINUE_LOOP")

                    jump_to_pos = label_pos[arg]
                    if last_popped_block == BlockType.WITH_BLOCK:
                        next_stack = next_stack[:-1] + (next_stack[-1] - 1,)
                    op += State(jump_to_pos, next_stack, next_block_stack, log),

                elif o == SETUP_LOOP:
                    inside_loop_log = cur_state.newlog("SETUP_LOOP (+block)")
                    op += State(label_pos[arg], cur_state.stack, cur_state.block_stack, cur_state.log),\
                          State(next_pos, cur_state.stack + (0,), cur_state.block_stack + (BlockType.LOOP_BODY,), inside_loop_log)

                elif o == SETUP_EXCEPT:
                    inside_except_log = cur_state.newlog("SETUP_EXCEPT, exception (+6, +block)")
                    inside_try_log = cur_state.newlog("SETUP_EXCEPT, try-block (+block)")
                    op += State(label_pos[arg], cur_state.stack + (6,), cur_state.block_stack + (BlockType.EXCEPTION,), inside_except_log),\
                          State(next_pos, cur_state.stack + (0,), cur_state.block_stack + (BlockType.TRY_EXCEPT,), inside_try_log)

                elif o == SETUP_FINALLY:
                    inside_finally_block = cur_state.newlog("SETUP_FINALLY (+1)")
                    inside_try_log = cur_state.newlog("SETUP_FINALLY try-block (+block)")
                    op += State(label_pos[arg], cur_state.newstack(1), cur_state.block_stack, inside_finally_block),\
                          State(next_pos, cur_state.stack + (0,), cur_state.block_stack + (BlockType.TRY_FINALLY,), inside_try_log)

                elif o == POP_BLOCK:
                    log = cur_state.newlog("POP_BLOCK (-block)")
                    op += State(next_pos, cur_state.stack[:-1], cur_state.block_stack[:-1], log),

                elif o == POP_EXCEPT:
                    log = cur_state.newlog("POP_EXCEPT (-block)")
                    op += State(next_pos, cur_state.stack[:-1], cur_state.block_stack[:-1], log),

                elif o == END_FINALLY:
                    if cur_state.block_stack[-1] == BlockType.SILENCED_EXCEPTION_BLOCK:
                        log = cur_state.newlog("END_FINALLY pop silenced exception block (-block)")
                        op += State(next_pos, cur_state.stack[:-1], cur_state.block_stack[:-1], log),
                    elif cur_state.block_stack[-1] == BlockType.EXCEPTION:
                        # Reraise exception
                        pass
                    else:
                        log = cur_state.newlog("END_FINALLY (-6)")
                        op += State(next_pos, cur_state.newstack(-6), cur_state.block_stack, log),

                elif o == SETUP_WITH or (version_info >= (3, 5,) and o == SETUP_ASYNC_WITH):
                    inside_with_block = cur_state.newlog("SETUP_WITH, with-block (+1, +block)")
                    inside_finally_block = cur_state.newlog("SETUP_WITH, finally (+1)")
                    op += State(label_pos[arg], cur_state.newstack(1), cur_state.block_stack, inside_finally_block),\
                          State(next_pos, cur_state.stack + (1,), cur_state.block_stack + (BlockType.WITH_BLOCK,), inside_with_block)

                elif version_info < (3, 5) and o == WITH_CLEANUP:
                    # There is special case when 'with' __exit__ function returns True,
                    # that's the signal to silence exception, in this case additional element is pushed
                    # and next END_FINALLY command won't reraise exception.
                    log = cur_state.newlog("WITH_CLEANUP (-1)")
                    silenced_exception_log = cur_state.newlog("WITH_CLEANUP silenced_exception (+1, +block)")
                    op += State(next_pos, cur_state.newstack(-1), cur_state.block_stack, log),\
                          State(next_pos, cur_state.newstack(-7) + (8,), cur_state.block_stack + (BlockType.SILENCED_EXCEPTION_BLOCK,), silenced_exception_log)

                elif version_info >= (3, 5,) and o == WITH_CLEANUP_START:
                    # There is special case when 'with' __exit__ function returns True,
                    # that's the signal to silence exception, in this case additional element is pushed
                    # and next END_FINALLY command won't reraise exception.
                    # Emulate this situation on WITH_CLEANUP_START with creating special block which will be
                    # handled differently by WITH_CLEANUP_FINISH and will cause END_FINALLY not to reraise exception.
                    log = cur_state.newlog("WITH_CLEANUP_START (+1)")
                    silenced_exception_log = cur_state.newlog("WITH_CLEANUP_START silenced_exception (+block)")
                    op += State(next_pos, cur_state.newstack(1), cur_state.block_stack, log),\
                          State(next_pos, cur_state.newstack(-7) + (9,), cur_state.block_stack + (BlockType.SILENCED_EXCEPTION_BLOCK,), silenced_exception_log)

                elif version_info >= (3, 5,) and o == WITH_CLEANUP_FINISH:
                    if cur_state.block_stack[-1] == BlockType.SILENCED_EXCEPTION_BLOCK:
                        # See comment in WITH_CLEANUP_START handler
                        log = cur_state.newlog("WITH_CLEANUP_FINISH silenced_exception (-1)")
                        op += State(next_pos, cur_state.newstack(-1), cur_state.block_stack, log),
                    else:
                        log = cur_state.newlog("WITH_CLEANUP_FINISH (-2)")
                        op += State(next_pos, cur_state.newstack(-2), cur_state.block_stack, log),

                else:
                    raise ValueError("Unhandled opcode %s" % o)

        return maxsize + 6  # for exception raise in deepest place
 def stack_effect(self):
     return stack_effect(
         self.opcode, *((self.arg,) if self.have_arg else ())
     )
Exemple #20
0
 def plumb(self, depths):
     arg = 0 if isinstance(self.arg, Label) else self.arg
     depths.append(depths[-1] + dis.stack_effect(self.opcode, arg))
Exemple #21
0
 def update_event(self, inp=-1):
     self.set_output_val(0, dis.stack_effect(self.input(0), self.input(1)))
Exemple #22
0
    def _compute_stacksize(self, logging=False):
        code = self.code
        label_pos = {
            op[0]: pos
            for pos, op in enumerate(code) if isinstance(op[0], Label)
        }
        # sf_targets are the targets of SETUP_FINALLY opcodes. They are recorded
        # because they have special stack behaviour. If an exception was raised
        # in the block pushed by a SETUP_FINALLY opcode, the block is popped
        # and 3 objects are pushed. On return or continue, the block is popped
        # and 2 objects are pushed. If nothing happened, the block is popped by
        # a POP_BLOCK opcode and 1 object is pushed by a (LOAD_CONST, None)
        # operation
        # Our solution is to record the stack state of SETUP_FINALLY targets
        # as having 3 objects pushed, which is the maximum. However, to make
        # stack recording consistent, the get_next_stacks function will always
        # yield the stack state of the target as if 1 object was pushed, but
        # this will be corrected in the actual stack recording
        if version_info < (3, 5):
            sf_targets = {
                label_pos[arg]
                for op, arg in code
                if (op == SETUP_FINALLY or op == SETUP_WITH)
            }
        else:
            sf_targets = {
                label_pos[arg]
                for op, arg in code if (op == SETUP_FINALLY or op == SETUP_WITH
                                        or op == SETUP_ASYNC_WITH)
            }

        states = [None] * len(code)
        maxsize = 0

        class BlockType(Enum):
            DEFAULT = 0,
            TRY_FINALLY = 1,
            TRY_EXCEPT = 2,
            LOOP_BODY = 3,
            WITH_BLOCK = 4,
            EXCEPTION = 5,
            SILENCED_EXCEPTION_BLOCK = 6,

        class State:
            def __init__(self,
                         pos=0,
                         stack=(0, ),
                         block_stack=(BlockType.DEFAULT, ),
                         log=[]):
                self._pos = pos
                self._stack = stack
                self._block_stack = block_stack
                self._log = log

            @property
            def pos(self):
                return self._pos

            @property
            def stack(self):
                return self._stack

            @stack.setter
            def stack(self, val):
                self._stack = val

            def newstack(self, n):
                if self._stack[-1] < -n:
                    raise ValueError(
                        "Popped a non-existing element at %s %s" %
                        (self._pos, code[self._pos - 4:self._pos + 3]))
                return self._stack[:-1] + (self._stack[-1] + n, )

            @property
            def block_stack(self):
                return self._block_stack

            @property
            def log(self):
                return self._log

            def newlog(self, msg):
                if not logging:
                    return None

                log_msg = str(self._pos) + ": " + msg
                if self._stack:
                    log_msg += " (on stack: "
                    log_depth = 2
                    log_depth = min(log_depth, len(self._stack))
                    for pos in range(-1, -log_depth, -1):
                        log_msg += str(self._stack[pos]) + ", "
                    log_msg += str(self._stack[-log_depth])
                    log_msg += ")"
                else:
                    log_msg += " (empty stack)"
                return [log_msg] + self._log

        op = [State()]

        while op:
            cur_state = op.pop()
            o = sum(cur_state.stack)
            if o > maxsize:
                maxsize = o

            o, arg = code[cur_state.pos]

            if isinstance(o, Label):
                if cur_state.pos in sf_targets:
                    cur_state.stack = cur_state.newstack(5)
                if states[cur_state.pos] is None:
                    states[cur_state.pos] = cur_state
                elif states[cur_state.pos].stack != cur_state.stack:
                    check_pos = cur_state.pos + 1
                    while code[check_pos][0] not in hasflow:
                        check_pos += 1
                    if code[check_pos][0] not in (RETURN_VALUE, RAISE_VARARGS,
                                                  STOP_CODE):
                        if cur_state.pos not in sf_targets:
                            raise ValueError(
                                "Inconsistent code at %s %s %s\n%s" %
                                (cur_state.pos, cur_state.stack,
                                 states[cur_state.pos].stack,
                                 code[cur_state.pos - 5:cur_state.pos + 4]))
                        else:
                            # SETUP_FINALLY target inconsistent code!
                            #
                            # Since Python 3.2 assigned exception is cleared at the end of
                            # the except clause (named exception handler).
                            # To perform this CPython (checked in version 3.4.3) adds special
                            # bytecode in exception handler which currently breaks 'regularity' of bytecode.
                            # Exception handler is wrapped in try/finally block and POP_EXCEPT opcode
                            # is inserted before END_FINALLY, as a result cleanup-finally block is executed outside
                            # except handler. It's not a bug, as it doesn't cause any problems during execution, but
                            # it breaks 'regularity' and we can't check inconsistency here. Maybe issue should be
                            # posted to Python bug tracker.
                            pass
                    continue
                else:
                    continue

            if o not in (BREAK_LOOP, RETURN_VALUE, RAISE_VARARGS, STOP_CODE):
                next_pos = cur_state.pos + 1

                if not isopcode(o):
                    op += State(next_pos, cur_state.stack,
                                cur_state.block_stack, cur_state.log),

                elif o not in hasflow:
                    if o in (LOAD_GLOBAL, LOAD_CONST, LOAD_NAME, LOAD_FAST,
                             LOAD_ATTR, LOAD_DEREF, LOAD_CLASSDEREF,
                             LOAD_CLOSURE, STORE_GLOBAL, STORE_NAME,
                             STORE_FAST, STORE_ATTR, STORE_DEREF,
                             DELETE_GLOBAL, DELETE_NAME, DELETE_FAST,
                             DELETE_ATTR, DELETE_DEREF, IMPORT_NAME,
                             IMPORT_FROM, COMPARE_OP):
                        se = stack_effect(o, 0)
                    else:
                        se = stack_effect(o, arg)

                    log = cur_state.newlog("non-flow command (" + str(o) +
                                           ", se = " + str(se) + ")")
                    op += State(next_pos, cur_state.newstack(se),
                                cur_state.block_stack, log),

                elif o == FOR_ITER:
                    inside_for_log = cur_state.newlog("FOR_ITER (+1)")
                    op += State(label_pos[arg], cur_state.newstack(-1), cur_state.block_stack, cur_state.log),\
                          State(next_pos, cur_state.newstack(1), cur_state.block_stack, inside_for_log)

                elif o in (JUMP_FORWARD, JUMP_ABSOLUTE):
                    after_jump_log = cur_state.newlog(str(o))
                    op += State(label_pos[arg], cur_state.stack,
                                cur_state.block_stack, after_jump_log),

                elif o in (JUMP_IF_FALSE_OR_POP, JUMP_IF_TRUE_OR_POP):
                    after_jump_log = cur_state.newlog(str(o) + ", jumped")
                    log = cur_state.newlog(str(o) + ", not jumped (-1)")
                    op += State(label_pos[arg], cur_state.stack, cur_state.block_stack, after_jump_log),\
                          State(next_pos, cur_state.newstack(-1), cur_state.block_stack, log)

                elif o in {POP_JUMP_IF_TRUE, POP_JUMP_IF_FALSE}:
                    after_jump_log = cur_state.newlog(str(o) + ", jumped (-1)")
                    log = cur_state.newlog(str(o) + ", not jumped (-1)")
                    op += State(label_pos[arg], cur_state.newstack(-1), cur_state.block_stack, after_jump_log),\
                          State(next_pos, cur_state.newstack(-1), cur_state.block_stack, log)

                elif o == CONTINUE_LOOP:
                    next_stack, next_block_stack = cur_state.stack, cur_state.block_stack
                    last_popped_block = None
                    while next_block_stack[-1] != BlockType.LOOP_BODY:
                        last_popped_block = next_block_stack[-1]
                        next_stack, next_block_stack = next_stack[:
                                                                  -1], next_block_stack[:
                                                                                        -1]

                    if next_stack != cur_state.stack:
                        log = cur_state.newlog(
                            "CONTINUE_LOOP, from non-loop block")
                    else:
                        log = cur_state.newlog("CONTINUE_LOOP")

                    jump_to_pos = label_pos[arg]
                    if last_popped_block == BlockType.WITH_BLOCK:
                        next_stack = next_stack[:-1] + (next_stack[-1] - 1, )
                    op += State(jump_to_pos, next_stack, next_block_stack,
                                log),

                elif o == SETUP_LOOP:
                    inside_loop_log = cur_state.newlog("SETUP_LOOP (+block)")
                    op += State(label_pos[arg], cur_state.stack, cur_state.block_stack, cur_state.log),\
                          State(next_pos, cur_state.stack + (0,), cur_state.block_stack + (BlockType.LOOP_BODY,), inside_loop_log)

                elif o == SETUP_EXCEPT:
                    inside_except_log = cur_state.newlog(
                        "SETUP_EXCEPT, exception (+6, +block)")
                    inside_try_log = cur_state.newlog(
                        "SETUP_EXCEPT, try-block (+block)")
                    op += State(label_pos[arg], cur_state.stack + (6,), cur_state.block_stack + (BlockType.EXCEPTION,), inside_except_log),\
                          State(next_pos, cur_state.stack + (0,), cur_state.block_stack + (BlockType.TRY_EXCEPT,), inside_try_log)

                elif o == SETUP_FINALLY:
                    inside_finally_block = cur_state.newlog(
                        "SETUP_FINALLY (+1)")
                    inside_try_log = cur_state.newlog(
                        "SETUP_FINALLY try-block (+block)")
                    op += State(label_pos[arg], cur_state.newstack(1), cur_state.block_stack, inside_finally_block),\
                          State(next_pos, cur_state.stack + (0,), cur_state.block_stack + (BlockType.TRY_FINALLY,), inside_try_log)

                elif o == POP_BLOCK:
                    log = cur_state.newlog("POP_BLOCK (-block)")
                    op += State(next_pos, cur_state.stack[:-1],
                                cur_state.block_stack[:-1], log),

                elif o == POP_EXCEPT:
                    log = cur_state.newlog("POP_EXCEPT (-block)")
                    op += State(next_pos, cur_state.stack[:-1],
                                cur_state.block_stack[:-1], log),

                elif o == END_FINALLY:
                    if cur_state.block_stack[
                            -1] == BlockType.SILENCED_EXCEPTION_BLOCK:
                        log = cur_state.newlog(
                            "END_FINALLY pop silenced exception block (-block)"
                        )
                        op += State(next_pos, cur_state.stack[:-1],
                                    cur_state.block_stack[:-1], log),
                    elif cur_state.block_stack[-1] == BlockType.EXCEPTION:
                        # Reraise exception
                        pass
                    else:
                        log = cur_state.newlog("END_FINALLY (-6)")
                        op += State(next_pos, cur_state.newstack(-6),
                                    cur_state.block_stack, log),

                elif o == SETUP_WITH or (version_info >= (
                        3,
                        5,
                ) and o == SETUP_ASYNC_WITH):
                    inside_with_block = cur_state.newlog(
                        "SETUP_WITH, with-block (+1, +block)")
                    inside_finally_block = cur_state.newlog(
                        "SETUP_WITH, finally (+1)")
                    op += State(label_pos[arg], cur_state.newstack(1), cur_state.block_stack, inside_finally_block),\
                          State(next_pos, cur_state.stack + (1,), cur_state.block_stack + (BlockType.WITH_BLOCK,), inside_with_block)

                elif version_info < (3, 5) and o == WITH_CLEANUP:
                    # There is special case when 'with' __exit__ function returns True,
                    # that's the signal to silence exception, in this case additional element is pushed
                    # and next END_FINALLY command won't reraise exception.
                    log = cur_state.newlog("WITH_CLEANUP (-1)")
                    silenced_exception_log = cur_state.newlog(
                        "WITH_CLEANUP silenced_exception (+1, +block)")
                    op += State(next_pos, cur_state.newstack(-1), cur_state.block_stack, log),\
                          State(next_pos, cur_state.newstack(-7) + (8,), cur_state.block_stack + (BlockType.SILENCED_EXCEPTION_BLOCK,), silenced_exception_log)

                elif version_info >= (
                        3,
                        5,
                ) and o == WITH_CLEANUP_START:
                    # There is special case when 'with' __exit__ function returns True,
                    # that's the signal to silence exception, in this case additional element is pushed
                    # and next END_FINALLY command won't reraise exception.
                    # Emulate this situation on WITH_CLEANUP_START with creating special block which will be
                    # handled differently by WITH_CLEANUP_FINISH and will cause END_FINALLY not to reraise exception.
                    log = cur_state.newlog("WITH_CLEANUP_START (+1)")
                    silenced_exception_log = cur_state.newlog(
                        "WITH_CLEANUP_START silenced_exception (+block)")
                    op += State(next_pos, cur_state.newstack(1), cur_state.block_stack, log),\
                          State(next_pos, cur_state.newstack(-7) + (9,), cur_state.block_stack + (BlockType.SILENCED_EXCEPTION_BLOCK,), silenced_exception_log)

                elif version_info >= (
                        3,
                        5,
                ) and o == WITH_CLEANUP_FINISH:
                    if cur_state.block_stack[
                            -1] == BlockType.SILENCED_EXCEPTION_BLOCK:
                        # See comment in WITH_CLEANUP_START handler
                        log = cur_state.newlog(
                            "WITH_CLEANUP_FINISH silenced_exception (-1)")
                        op += State(next_pos, cur_state.newstack(-1),
                                    cur_state.block_stack, log),
                    else:
                        log = cur_state.newlog("WITH_CLEANUP_FINISH (-2)")
                        op += State(next_pos, cur_state.newstack(-2),
                                    cur_state.block_stack, log),

                else:
                    raise ValueError("Unhandled opcode %s" % o)

        return maxsize + 6  # for exception raise in deepest place
Exemple #23
0
def _hax(bytecode: CodeType) -> CodeType:

    ops = instructions_with_lines(bytecode)

    used = False

    code: List[int] = []
    last_line = bytecode.co_firstlineno
    lnotab: List[int] = []
    consts: List[object] = [bytecode.co_consts[0]]
    names: Dict[str, int] = {}
    stacksize = 0
    jumps: Dict[int, List[Dict[str, Any]]] = {}
    deferred: Dict[int, int] = {}
    varnames: Dict[str, int] = {
        name: index
        for index, name in enumerate(
            bytecode.co_varnames[
                : bytecode.co_argcount
                + bytecode.co_kwonlyargcount
                + getattr(bytecode, "co_posonlyargcount", 0)
            ]
        )
    }
    flags = bytecode.co_flags

    labels: Dict[Hashable, int] = {}
    deferred_labels: Dict[Hashable, List[Dict[str, Any]]] = {}

    while True:

        extended: List[int] = []

        for op, line in ops:

            if op.is_jump_target:
                deferred[op.offset] = len(code) // OFFSET_SCALE
                offset = len(code) // OFFSET_SCALE
                for info in jumps.get(op.offset, ()):
                    info["arg"] = offset - info["arg"]
                    code[info["start"] : info["start"] + info["min_size"]] = backfill(
                        **info
                    )
                    # assert len(code) == offset, "Code changed size!"  # Look into this!

            if op.opcode < HAVE_ARGUMENT:
                if op.opcode != NOP:  # Just skip these?
                    stacksize += max(0, stack_effect(op.opcode))
                lnotab += 1, line - last_line
                code += op.opcode, 0
                last_line = line
                continue

            if op.opcode != EXTENDED_ARG:
                break

            assert isinstance(op.arg, int), "Non-integer argument!"
            extended += EXTENDED_ARG, op.arg

        else:
            break

        if op.argval not in opmap and op.argval not in {"HAX_LABEL", "LABEL"}:

            info = dict(
                arg=op.arg,
                start=len(code),
                line=line,
                following=op,
                min_size=len(extended) + 2,
                new_op=op.opcode,
                filename=bytecode.co_filename,
            )

            if op.opcode in HASLOCAL:
                info["arg"] = varnames.setdefault(op.argval, len(varnames))
            elif op.opcode in HASNAME:
                info["arg"] = names.setdefault(op.argval, len(names))
            elif op.opcode in HASCONST:
                try:
                    info["arg"] = consts.index(op.argval)
                except ValueError:
                    consts.append(op.argval)
                    info["arg"] = len(consts) - 1
            elif op.opcode in HASJABS:
                if op.argval <= op.offset:
                    info["arg"] = deferred[op.argval]
                else:
                    info["arg"] = 0
                    jumps.setdefault(op.argval, []).append(info)
            elif op.opcode in HASJREL:
                info["arg"] = (len(code) + len(extended) + 2) // OFFSET_SCALE
                jumps.setdefault(op.argval, []).append(info)

            assert op.opcode != EXTENDED_ARG
            stacksize += max(
                0,
                stack_effect(
                    op.opcode, info["arg"] if HAVE_ARGUMENT <= op.opcode else None
                ),
            )
            new_code = [*backfill(**info)]
            lnotab += 1, line - last_line, len(new_code) - 1, 0
            code += new_code
            last_line = line
            continue

        used = True

        if op.opname not in {"LOAD_FAST", "LOAD_GLOBAL", "LOAD_NAME"}:
            raise HaxCompileError(
                "Ops must consist of a simple call.",
                (bytecode.co_filename, line, None, None),
            )

        args = 0
        arg = 0

        for following, _ in ops:

            if following.opcode == EXTENDED_ARG:
                continue

            if following.opcode == LOAD_CONST:
                arg = following.argval
                args += 1
                continue

            break
        else:  # pragma: no cover
            assert False

        if following.opcode != CALL_FUNCTION:
            raise HaxCompileError(
                "Ops must consist of a simple call.",
                (bytecode.co_filename, line, None, None),
            )

        following, _ = next(ops)

        if following.opcode != POP_TOP:
            raise HaxCompileError(
                "Ops must be standalone statements.",
                (bytecode.co_filename, line, None, None),
            )

        line = following.starts_line or line

        if op.argval in {"HAX_LABEL", "LABEL"}:
            if op.argval == "LABEL":
                warn(
                    DeprecationWarning("LABEL is deprecated (use HAX_LABEL instead)"),
                    stacklevel=3,
                )
            if arg in labels:
                raise HaxCompileError(
                    f"Label {arg!r} already exists!",
                    (bytecode.co_filename, line, None, None),
                )
            offset = len(code) // OFFSET_SCALE
            labels[arg] = offset
            for info in deferred_labels.pop(arg, ()):
                info["arg"] = offset - info["arg"]
                code[info["start"] : info["start"] + info["min_size"]] = backfill(
                    **info
                )
                assert len(code) == offset * OFFSET_SCALE, "Code changed size!"
            last_line = line
            continue

        if op.argval in {"YIELD_FROM", "YIELD_VALUE"}:
            if flags & CO_COROUTINE:
                flags ^= CO_COROUTINE
                flags |= CO_ASYNC_GENERATOR
                assert flags & CO_ASYNC_GENERATOR
                assert not flags & CO_GENERATOR
            elif not flags & CO_ASYNC_GENERATOR:
                flags |= CO_GENERATOR
                assert not flags & CO_ASYNC_GENERATOR
                assert flags & CO_GENERATOR
            assert not flags & CO_COROUTINE

        new_op = opmap[op.argval]

        has_arg = HAVE_ARGUMENT <= new_op

        if args != has_arg:
            raise HaxCompileError(
                f"Number of arguments is wrong (expected {int(has_arg)}, got {args}).",
                (bytecode.co_filename, line, None, None),
            )

        info = dict(
            arg=arg,
            start=0,
            line=line,
            following=following,
            min_size=2,
            new_op=new_op,
            filename=bytecode.co_filename,
        )

        if new_op in HASLOCAL:
            if not isinstance(arg, str):
                raise HaxCompileError(
                    f"Expected a string (got {arg!r}).",
                    (bytecode.co_filename, line, None, None),
                )
            info["arg"] = varnames.setdefault(arg, len(varnames))
        elif new_op in HASNAME:
            if not isinstance(arg, str):
                raise HaxCompileError(
                    f"Expected a string (got {arg!r}).",
                    (bytecode.co_filename, line, None, None),
                )
            info["arg"] = names.setdefault(arg, len(names))
        elif new_op in HASCONST:
            try:
                info["arg"] = consts.index(arg)
            except ValueError:
                consts.append(arg)
                info["arg"] = len(consts) - 1
        elif new_op in HASCOMPARE:
            if not isinstance(arg, str):
                raise HaxCompileError(
                    f"Expected a string (got {arg!r}).",
                    (bytecode.co_filename, line, None, None),
                )
            try:
                info["arg"] = cmp_op.index(arg)
            except ValueError:
                raise HaxCompileError(
                    f"Bad comparision operator {arg!r}; expected one of {' / '.join(map(repr, cmp_op))}!",
                    (bytecode.co_filename, line, None, None),
                ) from None
        elif new_op in HASFREE:
            if not isinstance(arg, str):
                raise HaxCompileError(
                    f"Expected a string (got {arg!r}).",
                    (bytecode.co_filename, line, None, None),
                )
            try:
                info["arg"] = (bytecode.co_cellvars + bytecode.co_freevars).index(arg)
            except ValueError:
                raise HaxCompileError(
                    # Just do this for them?
                    f'No free/cell variable {arg!r}; maybe use "nonlocal" in the inner scope to compile correctly?',
                    (bytecode.co_filename, line, None, None),
                ) from None
        elif new_op in HASJUMP:
            if arg in labels:
                if new_op in HASJREL:
                    raise HaxCompileError(
                        "Relative jumps must be forwards, not backwards!",
                        (bytecode.co_filename, line, None, None),
                    )
                info["arg"] = labels[arg]
            else:
                max_jump = (
                    len(bytecode.co_code)
                    - 1
                    - ((len(code) + 2) if new_op in HASJREL else 0)
                )
                if 1 << 24 <= max_jump:
                    padding = 6
                elif 1 << 16 <= max_jump:
                    padding = 4
                elif 1 << 8 <= max_jump:
                    padding = 2
                else:
                    padding = 0
                info["arg"] = (
                    ((len(code) + padding + 2) // OFFSET_SCALE)
                    if new_op in HASJREL
                    else 0
                )
                info["start"] = len(code)
                info["min_size"] = padding + 2
                deferred_labels.setdefault(arg, []).append(info)
        elif not isinstance(arg, int):
            raise HaxCompileError(
                f"Expected integer argument, got {arg!r}.",
                (bytecode.co_filename, line, None, None),
            )
        if new_op not in {EXTENDED_ARG, NOP}:
            stacksize += max(0, stack_effect(new_op, info["arg"] if has_arg else None))
        new_code = [*backfill(**info)]
        lnotab += 1, line - last_line, len(new_code) - 1, 0
        code += new_code
        last_line = line

    if not used:
        return bytecode

    if deferred_labels:  # Warn unused labels, too?
        raise HaxCompileError(
            f"The following labels don't exist: {', '.join(map(repr, deferred_labels))}"
        )

    if sys.version_info < (3, 8):  # pragma: no cover
        maybe_posonlyargcount = ()
    else:  # pragma: no cover
        maybe_posonlyargcount = (bytecode.co_posonlyargcount,)

    return CodeType(
        bytecode.co_argcount,
        *maybe_posonlyargcount,
        bytecode.co_kwonlyargcount,
        len(varnames),
        stacksize,
        flags,
        bytes(code),
        tuple(consts),
        tuple(names),
        tuple(varnames),
        bytecode.co_filename,
        bytecode.co_name,
        bytecode.co_firstlineno,
        bytes(lnotab),
        bytecode.co_freevars,  # Need to update? CO_NOFREE?
        bytecode.co_cellvars,  # Need to update? CO_NOFREE?
    )
Exemple #24
0
However this can only used in Python 3.4 and above which has dis.stack_effect().
"""
from xdis import PYTHON_VERSION
import dis
NOTFIXED = -100
from xdis.cross_dis import op_has_argument
from xdis import get_opcode

print("# Python %s Stack effects\n" % PYTHON_VERSION)
assert PYTHON_VERSION >= 3.4, "This only works for Python version 3.4 and above; you have version %s." % PYTHON_VERSION
print("[")
opc = get_opcode(PYTHON_VERSION, False)
for i in range(256):
    try:
        if op_has_argument(i, opc):
            effect = dis.stack_effect(i, 0)
            opargs_to_try = [
                -1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 256, 1000, 0xffff, 0
            ]
            for operand in opargs_to_try:
                with_oparg = dis.stack_effect(i, operand)
                if effect != with_oparg:
                    effect = NOTFIXED
                    break
                pass
        else:
            effect = dis.stack_effect(i)
            pass
        pass
    except:
        print("  %d, # %d" % (NOTFIXED, i))
Exemple #25
0
              builder.store(builder.load(builder.gep(args,(int32(de),))),stack[stack_ptr])
              stack_ptr+=1

           builder.call(stackrestore,[savestack])
       elif ins.opname=='BUILD_SLICE': 
           stack_ptr = pop_and_call(builder,func,stack_ptr,builtin_slice, range(ins.arg), [])
       else:
           assert(False)

       if 42 in branch_stack and branch_stack[42] != oldstuff:
          print("br stack: " + str(branch_stack[42]) + ", " + str(oldstuff))

       print(block.name)

       if (   ins.opname!='SETUP_FINALLY' and 
              ins.opname!='SETUP_EXCEPT' and
              ins.opname!='POP_EXCEPT' and
              ins.opname!='END_FINALLY' and
              ins.opname!='EXTENDED_ARG' and
              ins.opname!='SETUP_LOOP' and
              not dis.stack_effect(ins.opcode,ins.arg) == stack_ptr - save_stack_ptr):
          print(dis.stack_effect(ins.opcode,ins.arg), stack_ptr - save_stack_ptr)
          assert(False)
       if did_jmp == False and block_idx+1 < len(blocks) and ins_idx+1==blocks[block_idx+1][0]:
         builder.branch(blocks[block_idx+1][2])
       ins_idx+=1
   i+=1

open("foo.ll", "w+").write(str(module))

Exemple #26
0
 def stack_effect(o, arg):
     return (dis.stack_effect(o, arg) if o != CALL_FUNCTION_EX else
             -2 if arg else -1)
Exemple #27
0
 def stack_effect(o, arg):
     return (dis.stack_effect(o, arg)
             if o != CALL_FUNCTION_EX else -2 if arg else -1)
 def stack_effect(self):
     return stack_effect(
         self.opcode,
         *((self.arg if isinstance(self.arg, int) else 0,)
           if self.have_arg else ())
     )
Exemple #29
0
 def stack_effect(self, arg=None):
     return dis.stack_effect(self.value, arg)
Exemple #30
0
 def stack_effect(self):
     return stack_effect(self.opcode,
                         *((self.arg, ) if self.have_arg else ()))
Exemple #31
0
 def stack_effect(self):
     return stack_effect(
         self.opcode,
         *((self.arg if isinstance(self.arg, int) else 0, )
           if self.have_arg else ()))
Exemple #32
0
 def plumb(self, depths):
     arg = 0 if isinstance(self.arg, Label) else self.arg
     depths.append(depths[-1] + dis.stack_effect(self.opcode, arg))
Exemple #33
0
    def _compute_stacksize(self):
        code = self.code
        label_pos = {
            op[0]: pos
            for pos, op in enumerate(code) if isinstance(op[0], Label)
        }
        # sf_targets are the targets of SETUP_FINALLY opcodes. They are recorded
        # because they have special stack behaviour. If an exception was raised
        # in the block pushed by a SETUP_FINALLY opcode, the block is popped
        # and 3 objects are pushed. On return or continue, the block is popped
        # and 2 objects are pushed. If nothing happened, the block is popped by
        # a POP_BLOCK opcode and 1 object is pushed by a (LOAD_CONST, None)
        # operation
        # Our solution is to record the stack state of SETUP_FINALLY targets
        # as having 3 objects pushed, which is the maximum. However, to make
        # stack recording consistent, the get_next_stacks function will always
        # yield the stack state of the target as if 1 object was pushed, but
        # this will be corrected in the actual stack recording
        sf_targets = {
            label_pos[arg]
            for op, arg in code if op == SETUP_FINALLY
        }
        stacks = [None] * len(code)
        maxsize = 0
        op = [(0, (0, ))]

        def newstack(n):
            if curstack[-1] < -n:
                raise ValueError("Popped a non-existing element at %s %s" %
                                 (pos, code[pos - 3:pos + 2]))
            return curstack[:-1] + (curstack[-1] + n, )

        while op:
            pos, curstack = op.pop()
            o = sum(curstack)
            if o > maxsize: maxsize = o
            o, arg = code[pos]
            if isinstance(o, Label):
                if pos in sf_targets:
                    curstack = curstack[:-1] + (curstack[-1] + 2, )
                if stacks[pos] is None:
                    stacks[pos] = curstack
                    if o not in (BREAK_LOOP, RETURN_VALUE, RAISE_VARARGS,
                                 STOP_CODE):
                        pos += 1
                        if not isopcode(o): op += (pos, curstack),
                        elif o not in hasflow:
                            op += (pos, newstack(stack_effect(o, arg))),
                        elif o == FOR_ITER:
                            op += (label_pos[arg], newstack(-1)), (pos,
                                                                   newstack(1))
                        elif o in (JUMP_FORWARD, JUMP_ABSOLUTE):
                            op += (label_pos[arg], curstack),
                        elif o in (POP_JUMP_IF_FALSE, POP_JUMP_IF_TRUE):
                            curstack = newstack(-1)
                            op += (label_pos[arg], curstack), (pos, curstack)
                        elif o in (JUMP_IF_FALSE_OR_POP, JUMP_IF_TRUE_OR_POP):
                            op += (label_pos[arg], curstack), (pos,
                                                               newstack(-1))
                        elif o == CONTINUE_LOOP:
                            op += (label_pos[arg], curstack[:-1]),
                        elif o == SETUP_LOOP:
                            op += (pos, curstack + (0, )), (label_pos[arg],
                                                            curstack)
                        elif o == SETUP_EXCEPT:
                            op += (pos, curstack + (0, )), (label_pos[arg],
                                                            newstack(3))
                        elif o == SETUP_FINALLY:
                            op += (pos, curstack + (0, )), (label_pos[arg],
                                                            newstack(1))
                        elif o == POP_BLOCK:
                            op += (pos, curstack[:-1]),
                        elif o == END_FINALLY:
                            op += (pos, newstack(-3)),
                        elif o == WITH_CLEANUP:
                            op += (pos, newstack(-1)),
                        else:
                            raise ValueError("Unhandled opcode %s" % op)
                elif stacks[pos] != curstack:
                    op = pos + 1
                    while code[op][0] not in hasflow:
                        op += 1
                    if code[op][0] not in (RETURN_VALUE, RAISE_VARARGS,
                                           STOP_CODE):
                        raise ValueError("Inconsistent code at %s %s %s\n%s" %
                                         (pos, curstack, stacks[pos],
                                          code[pos - 5:pos + 4]))
        return maxsize
Exemple #34
0
 def stack_effect(self):
     # All opcodes whose arguments are not represented by integers have
     # a stack_effect indepent of their argument.
     arg = (self._arg if isinstance(self._arg, int) else
            0 if self._opcode >= _opcode.HAVE_ARGUMENT else None)
     return dis.stack_effect(self._opcode, arg)
Exemple #35
0
"""
Based on a question in Freenode #python on March 6th, 2019 about computing the
effect of code on the size of the stack
"""
import dis
from pprint import pprint
from codetransformer import Code


def example(arg):
    try:
        arg.x
    except:
        return


print(dis.code_info(example))
instr = list(dis.get_instructions(example))
sfx = [dis.stack_effect(op.opcode, op.arg) for op in instr]

for i, s in zip(instr, sfx):
    opline = '\t'.join([
        f"{thing:<15}" for thing in ('>>' if i.is_jump_target else '',
                                     i.offset, i.opname, i.argrepr, f'{s:>5d}')
    ])
    print(opline)

c = Code.from_pyfunc(example)