equip/bytecode/utils.py
def show_bytecode(bytecode, start=0, end=2**32):
  from ..analysis.python.effects import get_stack_effect

  if bytecode is None:
    return ''
  buffer = []
  j = start
  end = min(end, len(bytecode) - 1)
  while j <= end:
    index, lineno, op, arg, _, co = bytecode[j]
    uid = hex(id(co))[-5:]

    pop_push_str = ''
    try:
      pop, push = get_stack_effect(op, arg)
      pop_push_str = ' (-%d +%d) ' % (pop, push)
    except ValueError, ex:
      pop_push_str = '         '

    if op >= opcode.HAVE_ARGUMENT:
      rts = repr(arg)
      if len(rts) > 40:
        rts = rts[:40] + '[...]'
      jump_target = ''
      if op in opcode.hasjrel or op in opcode.hasjabs:
        jump_address = arg if op in opcode.hasjabs else index + arg + 3
        jump_target = ' -------------> (%4d)' % jump_address

      buffer.append("[%5s]%4d(%4d) %20s(%3d)%s (%s)%s"
                    % (uid, lineno, index, opcode.opname[op], op, pop_push_str, rts, jump_target))
    else:
      buffer.append("[%5s]%4d(%4d) %20s(%3d)%s"
                    % (uid, lineno, index, opcode.opname[op], op, pop_push_str))
    j += 1
  return '\n'.join(buffer)
equip/analysis/graph/dominators.py
class DominatorTree(object):
  """
    Handles the dominator trees (dominator/post-dominator), and the
    computation of the dominance/post-dominance frontier.
  """

  def __init__(self, cfg):
    self._cfg = cfg
    self._doms = {}
    self._pdoms = {}
    self._df = {}
    self._pdf = {}
    self.build()

  @property
  def cfg(self):
    """
      Returns the CFG used for computing the dominator trees.
    """
    return self._cfg

  @property
  def dom(self):
    """
      Returns the dict containing the mapping of each node to its
      immediate dominator.
    """
    return self._doms

  @property
  def post_dom(self):
    """
      Returns the dict containing the mapping of each node to its
      immediate post-dominator.
    """
    return self._pdoms

  @property
  def frontier(self):
    """
      Returns the dict containing the mapping of each node to its
      dominance frontier (a set).
    """
    return self._df

  @property
  def post_frontier(self):
    """
      Returns the dict containing the mapping of each node to its
      post-dominance frontier (a set).
    """
    return self._pdf

  def build(self):
    graph = self.cfg.graph
    entry = self.cfg.entry_node
    exit = self.cfg.exit_node

    # Inverse the CFG to compute the post dominators using the same algo
    inverted_graph = graph.inverse()

    self.__build_dominators(graph, entry)
    self.__build_df(graph)

    self.__build_dominators(inverted_graph, exit, post_dom=True)
    self.__build_df(inverted_graph, post_dom=True)

  def __build_dominators(self, graph, entry, post_dom=False):
    """
      Builds the dominator tree based on:
        http://www.cs.rice.edu/~keith/Embed/dom.pdf

      Also used to build the post-dominator tree.
    """
    doms = self._doms if not post_dom else self._pdoms
    doms[entry] = entry
    post_order = dfs_postorder_nodes(graph, entry)

    post_order_number = {}
    i = 0
    for n in post_order:
      post_order_number[n] = i
      i += 1

    def intersec(b1, b2):
      finger1 = b1
      finger2 = b2
      po_finger1 = post_order_number[finger1]
      po_finger2 = post_order_number[finger2]
      while po_finger1 != po_finger2:
        no_solution = False
        while po_finger1 < po_finger2:
          finger1 = doms.get(finger1, None)
          if finger1 is None:
            no_solution = True
            break
          po_finger1 = post_order_number[finger1]
        while po_finger2 < po_finger1:
          finger2 = doms.get(finger2, None)
          if finger2 is None:
            no_solution = True
            break
          po_finger2 = post_order_number[finger2]
        if no_solution:
          break
      return finger1

    changed = True
    while changed:
      changed = False
      for b in reversed(post_order):
        if b == entry:
          continue
        predecessors = graph.in_edges(b)
        new_idom = next(iter(predecessors)).source
        for p_edge in predecessors:
          p = p_edge.source
          if p == new_idom:
            continue
          if p in doms:
            new_idom = intersec(p, new_idom)
        if b not in doms or doms[b] != new_idom:
          doms[b] = new_idom
          changed = True
    self.print_tree(post_dom)

  def __build_df(self, graph, post_dom=False):
    """
      Builds the dominance frontier.
    """
    doms = self._doms if not post_dom else self._pdoms
    df = self._df if not post_dom else self._pdf

    for b in graph.nodes:
      df[b] = set()

    for b in graph.nodes:
      predecessors = graph.in_edges(b)
      if len(predecessors) > 1:
        for p_edge in predecessors:
          p = p_edge.source
          runner = p
          while runner != doms[b]:
            df[runner].add(b)
            runner = doms[runner]

  def print_tree(self, post_dom=False):
    g_nodes = {}
    doms = self._doms if not post_dom else self._pdoms
    g = DiGraph()

    for node in doms:
      if node not in g_nodes:
        cur_node = g.make_add_node(data=node)
        g_nodes[node] = cur_node
      cur_node = g_nodes[node]

      parent = doms.get(node, None)
      if parent is not None and parent != node:
        if parent not in g_nodes:
          parent_node = g.make_add_node(data=parent)
          g_nodes[parent] = parent_node
        parent_node = g_nodes[parent]
        g.make_add_edge(parent_node, cur_node)

    logger.debug("%sDOM-tree :=\n%s", 'POST-' if post_dom else '', g.to_dot())
equip/utils/structures.py
class intervalmap(object):
  def __init__(self):
    """
      Initializes an empty intervalmap.
      From http://code.activestate.com/recipes/457411-an-interval-mapping-data-structure/
    """
    self._bounds = []
    self._items = []
    self._upperitem = None

  def __setitem__(self,_slice,_value):
    """
      Sets an interval mapping.
    """
    assert isinstance(_slice,slice), 'The key must be a slice object'

    if _slice.start is None:
      start_point = -1
    else:
      start_point = bisect_left(self._bounds,_slice.start)

    if _slice.stop is None:
      end_point = -1
    else:
      end_point = bisect_left(self._bounds,_slice.stop)

    if start_point>=0:
      if start_point < len(self._bounds) and self._bounds[start_point]<_slice.start:
        start_point += 1

      if end_point>=0:
        self._bounds[start_point:end_point] = [_slice.start,_slice.stop]
        if start_point < len(self._items):
          self._items[start_point:end_point] = [self._items[start_point],_value]
        else:
          self._items[start_point:end_point] = [self._upperitem,_value]
      else:
        self._bounds[start_point:] = [_slice.start]
        if start_point < len(self._items):
          self._items[start_point:] = [self._items[start_point],_value]
        else:
          self._items[start_point:] = [self._upperitem]
        self._upperitem = _value
    else:
      if end_point>=0:
        self._bounds[:end_point] = [_slice.stop]
        self._items[:end_point] = [_value]
      else:
        self._bounds[:] = []
        self._items[:] = []
        self._upperitem = _value

  def __getitem__(self,_point):
    """
      Gets a value from the mapping.
    """
    assert not isinstance(_point,slice), 'The key cannot be a slice object'

    index = bisect_right(self._bounds,_point)
    if index < len(self._bounds):
      return self._items[index]
    else:
      return self._upperitem

  def items(self):
    """
      Returns an iterator with each item being
      ((low_bound,high_bound), value). The items are returned
      in order.
    """
    previous_bound = None
    for b,v in izip(self._bounds,self._items):
      if v is not None:
        yield (previous_bound,b), v
      previous_bound = b
    if self._upperitem is not None:
      yield (previous_bound,None), self._upperitem

  def values(self):
    """
      Returns an iterator with each item being a stored value. The items
      are returned in order.
    """
    for v in self._items:
      if v is not None:
        yield v
    if self._upperitem is not None:
      yield self._upperitem

  def __repr__(self):
    s = []
    for b,v in self.items():
      if v is not None:
        s.append('[%r, %r] => %r'%(
          b[0],
          b[1],
          v
        ))
    return '{'+', '.join(s)+'}'
equip/analysis/ast/stmt.py
class Statement(object):
  """
    A statement in the bytecode is a set of opcodes. The stack effect of the
    statement is zero. The kind of statement is a subset of what is possible
    from the Python grammar; this is due to the simplified representations.
  """
  def __init__(self, block, start_bytecode_index, end_bytecode_index):
    self._block = block
    self._start_bytecode_index = start_bytecode_index
    self._end_bytecode_index = end_bytecode_index
    self._native = None

  @property
  def block(self):
    return self._block

  @property
  def start_bytecode_index(self):
    return self._start_bytecode_index

  @start_bytecode_index.setter
  def start_bytecode_index(self, value):
    self._start_bytecode_index = value

  @property
  def end_bytecode_index(self):
    return self._end_bytecode_index

  @end_bytecode_index.setter
  def end_bytecode_index(self, value):
    self._end_bytecode_index = value

  @property
  def bytecode(self):
    return self.block.bytecode[self.start_bytecode_index : self.end_bytecode_index + 1]

  @property
  def native(self):
    if self._native is None:
      self._native = Statement.to_python_statment(self.bytecode)
    return self._native

  def __repr__(self):
    return 'Statement(parent=%s, %d->%d)' \
            % (self.block, self.start_bytecode_index, self.end_bytecode_index)

  @staticmethod
  def to_python_statment(bytecode):
    i = len(bytecode) - 1
    while i >= 0:
      index, lineno, op, arg, cflow_in, code_object = bytecode[i]
      prev_op = bytecode[i - 1][2] if i > 1 else -1
      if op in (PRINT_ITEM, PRINT_NEWLINE, PRINT_ITEM_TO, PRINT_NEWLINE_TO):
        _, stmt = Statement.to_print_statement(i, op, bytecode)
        return stmt
      elif op in CALL_OPCODES:
        _, call = Statement.make_call(i, bytecode)
        return _ast.Expr(call)
      elif op == COMPARE_OP:
        _, cmp_expr = Statement.make_comparator(i, bytecode)
        return _ast.Expr(cmp_expr)
      elif op in STORE_OPCODES and prev_op not in (MAKE_FUNCTION, BUILD_CLASS):
        _, store_stmt = Statement.make_assign(i, bytecode)
        return store_stmt
      elif op in STORE_SLICE_OPCODES or op in DELETE_SLICE_OPCODES:
        _, store_stmt = Statement.make_store_delete_slice(i, bytecode)
        return store_stmt
      elif op in DELETE_OPCODES:
        _, store_stmt = Statement.make_delete(i, bytecode)
        return store_stmt
      else:
        logger.debug("Unhandled >> STMT:\n%s", show_bytecode(bytecode))
      i -= 1
    return None


  @staticmethod
  def to_print_statement(i, op, bytecode):
    stmt = _ast.Print()
    stmt.nl = False
    if op in (PRINT_ITEM_TO, PRINT_NEWLINE_TO):
      logger.error("Print to not handled yet.")
      pass
    else:
      if op == PRINT_NEWLINE:
        stmt.nl = True
      else:
        i, expr = Statement.make_expr(i - 1, bytecode)
        stmt.values = expr
    return i, stmt


  UNDEFINED_COUNT = 0

  @staticmethod
  def make_expr(i, bytecode, context=None):
    if context is None:
      context = _ast.Load()

    op = bytecode[i][2]
    if op == LOAD_CONST:
      return Statement.make_const(i, bytecode)
    elif op in (LOAD_GLOBAL, LOAD_NAME, LOAD_FAST,        \
                STORE_GLOBAL, STORE_NAME, STORE_FAST,     \
                DELETE_GLOBAL, DELETE_NAME, DELETE_FAST):
      return Statement.make_name(i, bytecode, context=context)
    elif op in (LOAD_ATTR, STORE_ATTR, DELETE_ATTR):
      return Statement.make_attribute(i, bytecode, context=context)
    elif op in CALL_OPCODES:
      return Statement.make_call(i, bytecode)
    elif op in BINARY_OP_OPCODES:
      return Statement.make_binary_op(i, bytecode)
    elif op in (BUILD_TUPLE, BUILD_LIST):
      return Statement.make_tuple_list(i, bytecode)
    elif op in (STORE_MAP, BUILD_MAP):
      return Statement.make_dict(i, bytecode)
    elif op in (STORE_SUBSCR, BINARY_SUBSCR):
      return Statement.make_subscript(i, bytecode)
    elif op in STORE_SLICE_OPCODES or op in DELETE_SLICE_OPCODES:
      return Statement.make_store_delete_slice(i, bytecode)
    elif op == BUILD_SLICE:
      return Statement.make_slice(i, bytecode)

    logger.debug("Unhandled >> EXPR:\n%s", show_bytecode(bytecode[:i + 1]))

    # if we don't translate it, we generate a new named expr.
    Statement.UNDEFINED_COUNT += 1
    return i, _ast.Name('Undef_%d' % Statement.UNDEFINED_COUNT, _ast.Load())


  @staticmethod
  def make_call(i, bytecode):
    op = bytecode[i][2]

    def get_call_arg_length(op, arg):
      na = arg & 0xff         # num args
      nk = (arg >> 8) & 0xff  # num keywords
      return na, nk, na + 2 * nk + CALL_EXTRA_ARG_OFFSET[op]

    has_var, has_kw = 0, 0
    if op in (CALL_FUNCTION_VAR, CALL_FUNCTION_VAR_KW): has_var = 1
    if op in (CALL_FUNCTION_KW, CALL_FUNCTION_VAR_KW): has_kw = 1

    op, arg = bytecode[i][2], bytecode[i][3]
    num_args, num_keywords, offset = get_call_arg_length(op, arg)

    func, args, keywords, starargs, kwargs = None, None, None, None, None

    if has_kw > 0:
      i, kwargs = Statement.make_expr(i - 1, bytecode)

    if has_var > 0:
      i, starargs = Statement.make_expr(i - 1, bytecode)

    # Handle keywords
    if num_keywords > 0:
      keywords = []
      while num_keywords > 0:
        i, kw_value = Statement.make_expr(i - 1, bytecode)
        i, kw_name = Statement.make_expr(i - 1, bytecode)
        keywords.insert(0, _ast.keyword(kw_name, kw_value))
        num_keywords -= 1

    finger = i - 1

    if num_args > 0:
      n = num_args - 1
      args = [None] * num_args
      for k in range(num_args):
        cur_stack = 0
        loc_bytecode = []
        is_first = True
        while True:
          op, arg = bytecode[finger][2], bytecode[finger][3]
          pop, push = get_stack_effect(op, arg)
          cur_stack -= (pop - push) if not is_first else pop
          is_first = False
          loc_bytecode.insert(0, bytecode[finger])
          if cur_stack == 0:
            break
          finger -= 1
        _, args[n] = Statement.make_expr(len(loc_bytecode) - 1, loc_bytecode)
        n -= 1
        finger -= 1

    _, func = Statement.make_expr(finger, bytecode)

    call = _ast.Call(func, args, keywords, starargs, kwargs)
    logger.debug("\n%s", dump(call))
    return finger, call

  @staticmethod
  def make_delete(i, bytecode):
    logger.debug("Delete: \n%s", show_bytecode(bytecode))
    op = bytecode[i][2]
    if op == DELETE_SUBSCR:
      i, subscr_expr = Statement.make_subscript(i, bytecode, context=_ast.Del())
      return i, _ast.Delete([subscr_expr])
    else:
      i, attr_expr = Statement.make_expr(i, bytecode, context=_ast.Del())
      return i, _ast.Delete([attr_expr])

    return i, None


  INPLACE_OPERATORS = {
    INPLACE_FLOOR_DIVIDE: _ast.FloorDiv,
    INPLACE_TRUE_DIVIDE: _ast.Div,
    INPLACE_ADD: _ast.Add,
    INPLACE_SUBTRACT: _ast.Sub,
    INPLACE_MULTIPLY: _ast.Mult,
    INPLACE_DIVIDE: _ast.Div,
    INPLACE_MODULO: _ast.Mod,
    INPLACE_POWER: _ast.Pow,
    INPLACE_LSHIFT: _ast.LShift,
    INPLACE_RSHIFT: _ast.RShift,
    INPLACE_AND: _ast.BitAnd,
    INPLACE_XOR: _ast.BitXor,
    INPLACE_OR: _ast.BitOr,
  }

  @staticmethod
  def make_assign(i, bytecode):
    op = bytecode[i][2]
    if op == STORE_SUBSCR:
      return Statement.make_subscript(i, bytecode)

    prev_op = bytecode[i - 1][2] if i > 0 else -1

    if prev_op in INPLACE_OPCODES:
      in_cls = Statement.INPLACE_OPERATORS[prev_op]
      i -= 1
      i, rhs = Statement.make_expr(i - 1, bytecode, context=_ast.AugStore())
      i, lhs = Statement.make_expr(i - 1, bytecode, context=_ast.AugLoad())
      return i, _ast.AugAssign(lhs, in_cls(), rhs)
    else:
      # We can either have multiple assignments: a = b = c = 1
      # or unpacked sequences: a, b = 1, foo()
      # the compiler does some optimization so that: a, b = c, d
      # does not rely on UNPACK_SEQUENCE, but a ROT_TWO (or ROT_THREE & ROT_TWO for 3 elements).
      # This happens for 2 or 3 elements to unpack
      targets = []
      value = None
      has_unpack, has_ROT_2_3, has_multiple = False, False, 0
      num_unpack = -1
      j = i
      while j >= 0:
        op = bytecode[j][2]
        if op == UNPACK_SEQUENCE:
          has_unpack = True
          num_unpack = bytecode[j][3]
          break
        elif op in (ROT_TWO, ROT_THREE):
          has_ROT_2_3 = True
          break
        if op == DUP_TOP:
          has_multiple += 1
        j -= 1

      if has_unpack:
        return Statement.make_assign_unpack(i, bytecode, unpack_num=num_unpack)
      elif has_ROT_2_3:
        return Statement.make_assign_opt_unpack(i, bytecode)
      elif has_multiple > 0:
        return Statement.make_assign_chained(i, bytecode)
      else:
        # A simple assignment
        i, store_expr = Statement.make_expr(i, bytecode)
        i, value_expr = Statement.make_expr(i - 1, bytecode)
        return i, _ast.Assign([store_expr], value_expr)
    return i, None


  # 2 cases here:
  #  (1) a, b, c = foo() <=> v = foo(), a = v[0], b = v[1], c = v[2]
  #    => AST: _ast.Assign(targets=[Tuple(a, b, c)], value=foo())
  #
  #  (2) a, b = foo(), bar() <=> a = foo(), b = bar()
  #    => AST: _ast.Assign(targets=[Tuple(a, b)], value=Tuple(baz(), bar()))
  @staticmethod
  def make_assign_unpack(i, bytecode, unpack_num=-1):
    if unpack_num < 1:
      logger.error("Could not find the number of unpacked items. ")
      return i, None

    store_exprs = []
    value_exprs = []
    store_state, value_state = True, False

    while i >= 0:
      op, arg = bytecode[i][2], bytecode[i][3]
      if store_state:
        if op == UNPACK_SEQUENCE:
          store_state = False
          prev_op = bytecode[i - 1][2] if i > 0 else -1
          if prev_op == BUILD_TUPLE:
            value_state = True
          else:
            i, value_exprs = Statement.make_expr(i - 1, bytecode)
            break
        elif op in STORE_OPCODES:
          i, store_stmt = Statement.make_expr(i, bytecode, context=_ast.Store())
          store_exprs.insert(0, store_stmt)
      elif value_state:
        i, value_stmt = Statement.make_expr(i, bytecode)
        value_exprs.insert(0, value_stmt)

      i -= 1

    store_exprs = _ast.Tuple(store_exprs, _ast.Store())
    if not isinstance(value_exprs, _ast.AST):
      value_exprs = _ast.Tuple(value_exprs, _ast.Load())

    return i, _ast.Assign([store_exprs], value_exprs)


  @staticmethod
  def make_assign_opt_unpack(i, bytecode):
    store_exprs = []
    value_exprs = []
    store_state, value_state = True, False

    while i >= 0:
      op, arg = bytecode[i][2], bytecode[i][3]
      if store_state:
        if op == ROT_TWO:
          prev_op = bytecode[i - 1][2] if i > 0 else -1
          if prev_op == ROT_THREE:
            i -= 1
          value_state = True
          store_state = False
        elif op in STORE_OPCODES:
          i, store_stmt = Statement.make_expr(i, bytecode, context=_ast.Store())
          store_exprs.insert(0, store_stmt)
      elif value_state:
        i, value_stmt = Statement.make_expr(i, bytecode)
        value_exprs.insert(0, value_stmt)
      i -= 1

    store_exprs = _ast.Tuple(store_exprs, _ast.Store())
    if not isinstance(value_exprs, _ast.AST):
      value_exprs = _ast.Tuple(value_exprs, _ast.Load())

    return i, _ast.Assign([store_exprs], value_exprs)


  # Only one case here for:
  #   a = b = z.d.f = foo()
  #    => AST: _ast.Assign(targets=[Tuple(a, b, z.d.f)], value=foo())
  @staticmethod
  def make_assign_chained(i, bytecode):
    store_exprs = []
    value_exprs = []
    store_state, value_state = True, False

    while i >= 0:
      op, arg = bytecode[i][2], bytecode[i][3]
      if store_state:
        if op == DUP_TOP:
          prev_op = bytecode[i - 1][2] if i > 0 else -1
          if prev_op not in STORE_OPCODES:
            value_state = True
            store_state = False
        elif op in STORE_OPCODES:
          i, store_stmt = Statement.make_expr(i, bytecode, context=_ast.Store())
          store_exprs.insert(0, store_stmt)
      elif value_state:
        i, value_exprs = Statement.make_expr(i, bytecode)
        break
      i -= 1

    store_exprs = _ast.Tuple(store_exprs, _ast.Store())
    return i, _ast.Assign([store_exprs], value_exprs)


  CMP_OP_AST_NODE_MAP = {
    '<': _ast.Lt,
    '<=': _ast.LtE,
    '==': _ast.Eq,
    '!=': _ast.NotEq,
    '>': _ast.Gt,
    '>=': _ast.GtE,
    'in': _ast.In,
    'not in': _ast.NotIn,
    'is': _ast.Is,
    'is not': _ast.IsNot,
  }

  @staticmethod
  def make_comparator(i, bytecode):
    op, arg = bytecode[i][2], bytecode[i][3]
    if arg not in Statement.CMP_OP_AST_NODE_MAP:
      return Statement.make_exception(i, bytecode)

    cmp_op = Statement.CMP_OP_AST_NODE_MAP[arg]()
    i, rhs = Statement.make_expr(i - 1, bytecode)
    i, lhs = Statement.make_expr(i - 1, bytecode)
    comp_stmt = _ast.Compare(lhs, [cmp_op], [rhs])
    return i, comp_stmt


  @staticmethod
  def make_exception(i, bytecode):
    logger.error("Exception STMT not handled.")
    return i, None


  # Store slice has 4 opodes:
  #  (1) STORE_SLICE+0 => TOS[:] = TOS1
  #  (2) STORE_SLICE+1 => TOS1[TOS:] = TOS2
  #  (3) STORE_SLICE+2 => TOS1[:TOS] = TOS2
  #  (4) STORE_SLICE+3 => TOS2[TOS1:TOS] = TOS3
  @staticmethod
  def make_store_delete_slice(i, bytecode, context=None):
    op = bytecode[i][2]
    is_delete = op in DELETE_SLICE_OPCODES

    if context is None:
      context = _ast.Store() if not is_delete else _ast.Del()

    lhs_expr = None

    if op in (STORE_SLICE_0, DELETE_SLICE_0):
      i, lhs_expr = Statement.make_expr(i - 1, bytecode, context=context)
      lhs_expr = _ast.Subscript(lhs_expr,
                                _ast.Slice(None, None, None),
                                _ast.Store())
    elif op in (STORE_SLICE_1, STORE_SLICE_2, DELETE_SLICE_1, DELETE_SLICE_2):
      i, index_expr = Statement.make_expr(i - 1, bytecode)
      i, arr_expr = Statement.make_expr(i - 1, bytecode, context=context)

      args = [None] * 3
      index_index = 0 if op in (STORE_SLICE_1, DELETE_SLICE_1) else 1
      args[index_index] = index_expr
      lhs_expr = _ast.Subscript(arr_expr,
                                _ast.Slice(*args),
                                _ast.Store())
    else:
      i, end_index_expr = Statement.make_expr(i - 1, bytecode)
      i, start_index_expr = Statement.make_expr(i - 1, bytecode)
      i, arr_expr = Statement.make_expr(i - 1, bytecode, context=context)

      lhs_expr = _ast.Subscript(arr_expr,
                                _ast.Slice(start_index_expr, end_index_expr, None),
                                _ast.Store())

    if is_delete:
      return i, _ast.Delete([lhs_expr])
    else:
      i, rhs_expr = Statement.make_expr(i - 1, bytecode)
      return i, _ast.Assign([lhs_expr], rhs_expr)


  @staticmethod
  def make_slice(i, bytecode):
    i, step_expr = Statement.make_expr(i - 1, bytecode)
    i, upper_expr = Statement.make_expr(i - 1, bytecode)
    i, lower_expr = Statement.make_expr(i - 1, bytecode)
    return i, _ast.Slice(lower_expr, upper_expr, step_expr)


  @staticmethod
  def make_subscript(i, bytecode, context=None):
    op = bytecode[i][2]
    if op == STORE_SUBSCR:
      # TOS1[TOS] = TOS2
      i, index_expr = Statement.make_expr(i - 1, bytecode)
      i, arr_expr = Statement.make_expr(i - 1, bytecode, context=_ast.Store())
      i, rhs_expr = Statement.make_expr(i - 1, bytecode)
      lhs_expr = _ast.Subscript(arr_expr, index_expr, _ast.Store())
      return i, _ast.Assign([lhs_expr], rhs_expr)
    else:
      if context is None:
        context = _ast.Load()

      # BINARY_SUBSCR: TOS1[TOS] and DELETE_SUBSCR TOS1[TOS]
      i, index_expr = Statement.make_expr(i - 1, bytecode)
      i, arr_expr = Statement.make_expr(i - 1, bytecode)
      return i, _ast.Subscript(arr_expr, index_expr, context)


  BIN_OP_AST_NODE_MAP = {
    BINARY_OR: _ast.BitOr,
    BINARY_XOR: _ast.BitXor,
    BINARY_AND: _ast.BitAnd,
    BINARY_RSHIFT: _ast.RShift,
    BINARY_LSHIFT: _ast.LShift,
    BINARY_SUBTRACT: _ast.Sub,
    BINARY_ADD: _ast.Add,
    BINARY_MODULO: _ast.Mod,
    BINARY_DIVIDE: _ast.Div,
    BINARY_MULTIPLY: _ast.Mult,
    BINARY_POWER: _ast.Pow,
    BINARY_FLOOR_DIVIDE: _ast.FloorDiv,
    BINARY_TRUE_DIVIDE: _ast.Div,
  }

  @staticmethod
  def make_binary_op(i, bytecode):
    op = bytecode[i][2]
    bin_op = Statement.BIN_OP_AST_NODE_MAP[op]()
    i, rhs = Statement.make_expr(i - 1, bytecode)
    i, lhs = Statement.make_expr(i - 1, bytecode)
    return i, _ast.BinOp(lhs, bin_op, rhs)


  # Build Attr(value=Attr(value=Name(id=a), attr=b), attr=c) <=> a.b.c
  @staticmethod
  def make_attribute(i, bytecode, context=None):
    arg = bytecode[i][3]
    attr_path = []
    j = i
    while True:
      prev_op, prev_arg =  bytecode[j][2], bytecode[j][3]
      if prev_op not in (LOAD_ATTR, STORE_ATTR, DELETE_ATTR):
        break
      attr_path.append(prev_arg)
      if j < 0:
        break
      j -= 1
    # The parent of the ATTR can be whatever expression...
    i, name = Statement.make_expr(j, bytecode)
    attr = name
    while True:
      if not attr_path:
        break
      attr_name = attr_path.pop()
      attr = _ast.Attribute(attr, attr_name, context)
    return i, attr


  @staticmethod
  def make_name(i, bytecode, context=None):
    arg = bytecode[i][3]
    if context is None:
      context = _ast.Load()
    return i, _ast.Name(arg, context)


  @staticmethod
  def make_tuple_list(i, bytecode):
    op, arg = bytecode[i][2], bytecode[i][3]
    pop, push = get_stack_effect(op, arg)
    values = []
    for k in range(pop):
      i, expr = Statement.make_expr(i - 1, bytecode)
      values.insert(0, expr)
    cls = _ast.Tuple if op == BUILD_TUPLE else _ast.List
    return i, cls(values, _ast.Load())


  @staticmethod
  def make_dict(i, bytecode):
    """
      This is a trick. We don't try to resolve exactly what the dict
      contains, but just create a dict type with the proper number of args.
    """
    logger.debug("i=%d, bytecode=\n%s", i, show_bytecode(bytecode))
    num_keys = -1
    if bytecode[i][2] == BUILD_MAP:
      return i, _ast.Dict([], [])
    else:
      keys, values = [], []
      j = i - 1
      build_map_idx = i - 1
      while j >= 0:
        if bytecode[j][2] == BUILD_MAP:
          num_keys = bytecode[j][3]
          build_map_idx = j
        j -= 1
      return build_map_idx, _ast.Dict(range(num_keys), [None] * num_keys)

    return build_map_idx, None


  @staticmethod
  def make_const(i, bytecode):
    arg = bytecode[i][3]
    if isinstance(arg, basestring):
      return i, _ast.Str(arg)
    elif isinstance(arg, int) or isinstance(arg, float) or isinstance(arg, long):
      return i, _ast.Num(arg)
    elif isinstance(arg, dict):
      return i, _ast.Dict(arg.keys(), arg.values())
    elif isinstance(arg, set):
      return i, _ast.Dict(arg)
    elif isinstance(arg, tuple):
      return i, _ast.Tuple(arg, _ast.Load())
    elif isinstance(arg, list):
      return i, _ast.List(arg, _ast.Load())
    elif isinstance(arg, bytes):
      return i, _ast.Bytes(arg)
    return i, None
equip/analysis/python/effects.py
def get_stack_effect(op, arg=None):
  """
    Returns the stack effect tuple (pop, push) for the given opcode/arg.

    :param op: The opcode.
    :param arg: Dereferenced argument of the opcode.
  """
  return byteplay.getse(op, arg)
equip/analysis/constraint/container.py
class Constraint(object):
  """
    Represents a constraint in the bytecode. This is used to represent
    conditional expressions. We store both the bytecode AST constraint
    and a final internal representation (can be used to compare constraints
    or generate SMT clauses).
  """
  def __init__(self):
    self._ast = Tree()
    self._cstr = None
    self._live = None
    self._root = None

  @property
  def root(self):
    return self._root

  @root.setter
  def root(self, value):
    self._root = value
    self._ast.root = self._root

  @property
  def ast(self):
    return self._ast

  @property
  def live(self):
    if self._live is None:
      self._live = set()
      worklist = [self.tree]
      while worklist:
        cur = worklist.pop(0)
        if isinstance(cur, Ref):
          self._live.add(cur.data)
        else:
          if not cur.terminal:
            worklist.append(cur.rhs)
            if cur.binary:
              worklist.append(cur.lhs)

    return self._live

  @property
  def tree(self):
    if self._cstr is None:
      self.__finalize()
    return self._cstr

  def has_comparator(self, cmp_kind):
    worklist = [self.tree]
    while worklist:
      cur = worklist.pop(0)
      logger.debug("Cur := %s", cur)
      if isinstance(cur, Comparator):
        if cur.cmp_id == cmp_kind:
          return True
        worklist.append(cur.lhs)
        if cur.rhs is not None:
          worklist.append(cur.rhs)
      elif isinstance(cur, Operator):
        worklist.append(cur.lhs)
        if cur.rhs is not None:
          worklist.append(cur.rhs)
    return False

  def __ne__(self, obj):
    return not self == obj

  def __eq__(self, obj):
    return isinstance(obj, Constraint) and self.tree == obj.tree


  def __finalize(self):
    root = self.root
    self._cstr = None
    if root.data[0] != COMPARE_OP:
      self._cstr = Comparator.fromKind(CMP_IMPLICIT_NOT_EMPTY)
    else:
      self._cstr = Comparator.fromOpcode(*root.data)
    self._cstr.data = root.data

    def process_children(cstr_node, ast_node):
      if ast_node.has_children() and cstr_node.terminal:
        return

      if not cstr_node.terminal:
        if cstr_node.kind == Expr.COMPARATOR and cstr_node.cmp_id in (CMP_IMPLICIT_NOT_EMPTY, CMP_TYPE_CHECK):
          if cstr_node.cmp_id == CMP_IMPLICIT_NOT_EMPTY:
            cstr_node.rhs = convert_ast_constraint(ast_node)
            process_children(cstr_node.rhs, ast_node)
          else:
            lhs_child = ast_node.first_child
            cstr_node.lhs = convert_ast_constraint(lhs_child)
            process_children(cstr_node.lhs, lhs_child)

            if ast_node.num_children() > 1:
              rhs_child = ast_node.child(1)
              cstr_node.rhs = convert_ast_constraint(rhs_child)
              process_children(cstr_node.rhs, rhs_child)
        else:
          expected_children_num = 2 if cstr_node.binary else 1
          children = ast_node.children
          num_children = len([c for c in children if c is not None])
          if num_children != expected_children_num:
            logger.debug("Consistency error. expected %d, got %d children for %s",
                         expected_children_num, num_children, ast_node)
            cstr_node.rhs = Undef(data=None)
            if expected_children_num == 2:
              cstr_node.lhs = Undef(data=None)
          elif expected_children_num == 1:
            child = ast_node.first_child
            cstr_node.rhs = convert_ast_constraint(child)
            process_children(cstr_node.rhs, child)
          else:
            lhs_child = ast_node.first_child
            rhs_child = ast_node.last_child

            cstr_node.lhs = convert_ast_constraint(lhs_child)
            process_children(cstr_node.lhs, lhs_child)

            cstr_node.rhs = convert_ast_constraint(rhs_child)
            process_children(cstr_node.rhs, rhs_child)

    logger.debug("Current tree := %s", self._ast.to_dot())

    process_children(self._cstr, root)


  def __repr__(self):
    return repr(self._cstr)
equip/analysis/graph/traversals.py
class EdgeVisitor(object):
  def __init__(self):
    pass

  def visit(self, edge):
    pass
equip/analysis/graph/graphs.py
class Tree(object):
  def __init__(self):
    self._root = None

  @property
  def root(self):
    return self._root

  @root.setter
  def root(self, value):
    self._root = value

  def to_dot(self):
    from .io import DotConverter
    return DotConverter.process(self)
equip/analysis/graph/traversals.py
class Walker(object):
  """
    Traverses edges in the graph in DFS.
  """
  def __init__(self, graph, visitor, backwards=False):
    self._graph = graph
    self._visitor = visitor
    self._backwards = backwards
    self.worklist = None

  @property
  def graph(self):
    return self._graph

  @graph.setter
  def graph(self, value):
    self._graph = value

  @property
  def visitor(self):
    return self._visitor

  @visitor.setter
  def visitor(self, value):
    self._visitor = value

  def traverse(self, root):
    self.worklist = []
    self.__run(root)

  def __run(self, root=None):
    visited = set()
    if root is not None:
      self.__process(root)
    while self.worklist:
      current = self.worklist.pop(0)
      if current in visited:
        continue
      self.__process(current)
      visited.add(current)

  def __process(self, current):
    cur_node = None
    if isinstance(current, Edge):
      cur_node = current.dest if not self._backwards else current.source
      self.visitor.visit(current)
    else:
      cur_node = current

    list_edges = self.graph.out_edges(cur_node)     \
                 if not self._backwards             \
                 else self.graph.in_edges(cur_node)
    for edge in list_edges:
      self.worklist.insert(0, edge)
equip/analysis/graph/graphs.py
class Node(object):
  GLOBAL_COUNTER = 0

  def __init__(self, kind=None, data=None):
    Node.GLOBAL_COUNTER += 1
    self._id = Node.GLOBAL_COUNTER
    self._kind = kind
    self._data = data

  @property
  def gid(self):
    return self._id

  @property
  def kind(self):
    return self._kind

  @kind.setter
  def kind(self, value):
    self._kind = value

  @property
  def data(self):
    return self._data

  @data.setter
  def data(self, value):
    self._data = value

  def __ne__(self, obj):
    return not self == obj

  def __eq__(self, obj):
    return isinstance(obj, Node) and obj.gid == self.gid

  def __hash__(self):
    return hash('node-' + str(self.gid))

  def __repr__(self):
    return 'Node%d(kind=%s, data=%s)' % (self.gid, repr(self.kind), repr(self.data))
equip/analysis/graph/graphs.py
class DiGraph(object):
  """
    A simple directed-graph structure.
  """

  def __init__(self, multiple_edges=True):
    self._multiple_edges = multiple_edges
    self._nodes = set()
    self._edges = set()
    self._in = {} # dest -> source : set(edges)
    self._out = {} # source -> dest : set(edges)
    self._immutable = False
    self._imm_in = None # src -> set(in edges)
    self._imm_out = None # src -> set(out edges)

  @property
  def multiple_edges(self):
    return self._multiple_edges

  @multiple_edges.setter
  def multiple_edges(self, value):
    self._multiple_edges = value

  @property
  def immutable(self):
    return self._immutable

  @immutable.setter
  def immutable(self, value):
    self._immutable = value

  @property
  def nodes(self):
    return self._nodes

  @property
  def edges(self):
    return self._edges

  def add_edge(self, edge):
    if self.immutable:
      raise Exception('The graph is now immutable. Cannot add edge.')
    if edge in self._edges:
      raise Exception('Edge already present')
    source_node, dest_node = edge.source, edge.dest

    if not self.multiple_edges:
      # If we already connected src and dest, return
      if source_node in self._out and dest_node in self._out[source_node]:
        logger.error("Already connected nodes: %s", edge)
        return
      if dest_node in self._in and source_node in self._in[dest_node]:
        logger.error("Already connected nodes: %s", edge)
        return

    self._edges.add(edge)
    self.add_node(source_node)
    self.add_node(dest_node)
    DiGraph.__add_edge(self._out, source_node, dest_node, edge)
    DiGraph.__add_edge(self._in, dest_node, source_node, edge)

  def remove_edge(self, edge):
    if self.immutable:
      raise Exception('The graph is now immutable. Cannot remove edge.')
    if edge not in self.edges:
      return
    source_node, dest_node = edge.source, edge.dest
    DiGraph.__remove_edge(self._out, source_node, dest_node, edge)
    DiGraph.__remove_edge(self._in, dest_node, source_node, edge)
    self._edges.remove(edge)

  @staticmethod
  def __add_edge(in_out, source, dest, edge):
    if source not in in_out:
      in_out[source] = {}
    if dest not in in_out[source]:
      in_out[source][dest] = list()
    in_out[source][dest].append(edge)

  @staticmethod
  def __remove_edge(in_out, source, dest, edge):
    if source not in in_out:
      return
    if dest not in in_out[source]:
      return
    if edge in in_out[source][dest]:
      in_out[source][dest].remove(edge)
    if not in_out[source][dest]:
      in_out[source].pop(dest, None)
    if not in_out[source]:
      in_out.pop(source, None)

  def has_node(self, node):
    return node in self.nodes

  def add_node(self, node):
    if self.immutable:
      raise Exception('The graph is now immutable. Cannot add node.')
    self._nodes.add(node)

  def remove_node(self, node):
    if self.immutable:
      raise Exception('The graph is now immutable. Cannot remove node.')
    if node not in self._nodes:
      return
    # Remove all edges that touched this node
    self._edges = set([e for e in self._edges if e.source != node and e.dest != node])
    # Clean up _in/_out
    for src in self._in:
      self._in[src].pop(node, None)
    for dest in self._out:
      self._out[dest].pop(node, None)
    if node in self._in:
      self._in.pop(node, None)
    if node in self._out:
      self._out.pop(node, None)
    # Finally remove the node
    self._nodes.remove(node)

  def in_edges(self, node):
    if self.immutable:
      return self._imm_in[node]
    else:
      if not self.has_node(node) or node not in self._in:
        return list()
      return list([e for n in self._in[node] for e in self._in[node][n]])

  def out_edges(self, node):
    if self.immutable:
      return self._imm_out[node]
    else:
      if not self.has_node(node) or node not in self._out:
        return list()
      return list([e for n in self._out[node] for e in self._out[node][n]])

  def in_degree(self, node):
    return len(self.in_edges(node))

  def out_degree(self, node):
    return len(self.out_edges(node))

  def to_dot(self):
    from .io import DotConverter
    return DotConverter.process(self)

  def roots(self):
    r = set()
    for n in self.nodes:
      if self.in_degree(n) < 1:
        r.add(n)
    return r

  def freeze(self):
    if self.immutable:
      return

    self._imm_in = {}
    self._imm_out = {}
    for node in self.nodes:
      self._imm_in[node] = self.in_edges(node)
      self._imm_out[node] = self.out_edges(node)

    self._in = None
    self._out = None
    self.immutable = True

  def unfreeze(self):
    if not self.immutable:
      return
    self._in = {}
    self._out = {}

    for edge in self.edges:
      source_node, dest_node = edge.source, edge.dest
      DiGraph.__add_edge(self._out, source_node, dest_node, edge)
      DiGraph.__add_edge(self._in, dest_node, source_node, edge)

    self._imm_in = None
    self._imm_out = None
    self.immutable = False

  def copy(self):
    return copy.deepcopy(self)

  # Some helpers
  def make_add_node(self, kind=None, data=None):
    if self.immutable:
      raise Exception('The graph is now immutable. Cannot add node.')
    node = DiGraph.make_node(kind=kind, data=data)
    self.add_node(node)
    return node

  def make_add_edge(self, source=None, dest=None, kind=None, data=None):
    if self.immutable:
      raise Exception('The graph is now immutable. Cannot add edge.')
    edge = DiGraph.make_edge(source=source, dest=dest, kind=kind, data=data)
    self.add_edge(edge)
    return edge

  def inverse(self):
    """
      Returns a frozen copy of this graph where all edges have been inverted.
    """
    new_g = DiGraph(multiple_edges=self.multiple_edges)
    for edge in self.edges:
      new_edge = copy.deepcopy(edge)
      new_edge.inverse()
      new_g.add_edge(new_edge)
    new_g.freeze()
    return new_g

  @staticmethod
  def make_node(kind=None, data=None):
    return Node(kind=kind, data=data)

  @staticmethod
  def make_edge(source=None, dest=None, kind=None, data=None):
    return Edge(source=source, dest=dest, kind=kind, data=data)
equip/analysis/graph/graphs.py
class Edge(object):
  GLOBAL_COUNTER = 0

  def __init__(self, source=None, dest=None, kind=None, data=None):
    Edge.GLOBAL_COUNTER += 1
    self._id = Edge.GLOBAL_COUNTER
    self._source = source
    self._dest = dest
    self._kind = kind
    self._data = data
    self._inversed = False
    self._weight = 1

  @property
  def gid(self):
    return self._id

  @property
  def source(self):
    return self._source

  @source.setter
  def source(self, value):
    self._source = value

  @property
  def dest(self):
    return self._dest

  @dest.setter
  def dest(self, value):
    self._dest = value

  @property
  def kind(self):
    return self._kind

  @kind.setter
  def kind(self, value):
    self._kind = value

  @property
  def data(self):
    return self._data

  @data.setter
  def data(self, value):
    self._data = value

  @property
  def weight(self):
    return self._weight

  @weight.setter
  def weight(self, value):
    self._weight = value

  @property
  def inversed(self):
    return self._inversed

  def inverse(self):
    tmp = self._source
    self._source = self._dest
    self._dest = tmp
    self._inversed = True

  def __ne__(self, obj):
    return not self == obj

  def __eq__(self, obj):
    return isinstance(obj, Edge) and obj.gid == self.gid

  def __hash__(self):
    return hash('edge-' + str(self.gid))

  def __repr__(self):
    return 'Edge%d(src=%s, dst=%s, kind=%s, data=%s)' \
           % (self.gid, self.source, self.dest, repr(self.kind), repr(self.data))
equip/analysis/block.py
class BasicBlock(object):
  """
    Represents a basic block from the bytecode. It's a bit more than just
    the location in the bytecode since it also contains the jump targets
    which are used during the construction of the control flow graph.
  """
  ENTRY = 1
  IMPLICIT_RETURN = 2
  UNKNOWN = 3
  LOOP = 4
  IF = 5
  EXCEPT = 6

  def __init__(self, kind, decl, index):
    self._kind = kind
    self._decl = decl
    self._index = index
    self._length = 0
    self._jumps = set()
    self._end_target = -1
    self._fallthrough = False
    self._has_return_path = False
    self._bytecode = None
    self._statements = None

  @property
  def kind(self):
    """
      Returns the kind of basic block.
    """
    return self._kind

  @kind.setter
  def kind(self, value):
    self._kind = value

  @property
  def decl(self):
    """
      Returns the ``Declaration`` associated to this basic block.
    """
    return self._decl

  @decl.setter
  def decl(self, value):
    self._decl = value

  @property
  def index(self):
    """
      Returns the start index (in the bytecode) for this basic block.
    """
    return self._index

  @index.setter
  def index(self, value):
    self._index = value

  @property
  def bytecode(self):
    """
      Returns the bytecode associated to this basic block.
    """
    if self._bytecode is None:
      if self.kind in (BasicBlock.ENTRY, BasicBlock.IMPLICIT_RETURN):
        self._bytecode = []
      else:
        bc = self.decl.bytecode
        start_index = [j for j in range(len(bc)) if bc[j][0] == 0][0]
        prev_co = bc[start_index][5]
        slice_bytecode = [tpl for tpl in bc[start_index:] if tpl[5] == prev_co]
        bc = []
        for l in slice_bytecode:
          idx = l[0]
          if idx >= self._index and idx < self._index + self._length:
            bc.append(l)
        self._bytecode = bc
    return self._bytecode

  @property
  def statements(self):
    if self._statements is None:
      from .flow import ControlFlow
      self._statements = []
      ControlFlow.create_statements(self, self.bytecode, self._statements)
    return self._statements

  @property
  def length(self):
    """
      Returns the length of the basic block (size of all the instructions)
    """
    return self._length

  @length.setter
  def length(self, value):
    assert value >= 0
    self._length = value

  @property
  def fallthrough(self):
    return self._fallthrough

  @fallthrough.setter
  def fallthrough(self, value):
    self._fallthrough = value

  @property
  def has_return_path(self):
    return self._has_return_path

  @has_return_path.setter
  def has_return_path(self, value):
    self._has_return_path = value

  @property
  def jumps(self):
    """
      Returns the list of all the targets from this basic block.
    """
    return self._jumps

  def clear_jumps(self):
    self._jumps = set()

  def add_jump(self, jump_index, branch_kind):
    self._jumps.add((jump_index, branch_kind))

  @property
  def end_target(self):
    """
      Returns the end of scope of the target. This is only meaningful for
      a loop or ``with``.
    """
    return self._end_target

  @end_target.setter
  def end_target(self, value):
    self._end_target = value

  def __repr__(self):
    end_target = ''
    if self.end_target > -1:
      end_target = ', target=%d' % self.end_target
    return 'BasicBlock(%s, %d->%d, jumps=%s%s)' \
           % (self.kind, self.index, (self.index + self.length), self.jumps, end_target)
equip/analysis/graph/dependences.py
class ControlDependence(object):
  """
    Holds the control dependence. Each node in the CFG is mapped to
    its dependences.
  """
  def __init__(self, cfg):
    self._cfg = cfg
    self._dom = cfg.dominators
    self._graph = None
    self.build()

  @property
  def cfg(self):
    return self._cfg

  @property
  def graph(self):
    return self._graph

  def build(self):
    self._graph = DiGraph()

    for node in self._dom.post_dom:
      p_frontier = self._dom.post_frontier[node]
      if not p_frontier:
        continue
      self._graph.add_node(node)
      for f_node in p_frontier:
        self._graph.make_add_edge(f_node, node)

    self._graph.freeze()
equip/utils/log.py
LOGGING_FMT = '%(asctime)s - %(levelname)3s] %(filename)s::%(funcName)s(%(lineno)d) - %(message)s'
def removeOtherHandlers(to_keep=None):
def enableLogger(to_file=None):
equip/analysis/ast/expr.py
class Expression(object):
  def __init__(self, expr_kind=E_UNKNOWN):
    self._expr_kind = expr_kind

  @property
  def expr_kind(self):
    return self._expr_kind

  @expr_kind.setter
  def expr_kind(self, value):
    self._expr_kind = value
equip/analysis/graph/graphs.py
class TreeNode(object):
  GLOBAL_COUNTER = 0

  def __init__(self, kind=None, data=None):
    TreeNode.GLOBAL_COUNTER += 1
    self._id = TreeNode.GLOBAL_COUNTER
    self._kind = kind
    self._data = data
    self._children = list()

  @property
  def gid(self):
    return self._id

  @property
  def kind(self):
    return self._kind

  @kind.setter
  def kind(self, value):
    self._kind = value

  @property
  def data(self):
    return self._data

  @data.setter
  def data(self, value):
    self._data = value

  @property
  def children(self):
    return self._children

  def has_children(self):
    return len(self._children) > 0

  def num_children(self):
    return len(self._children)

  @property
  def first_child(self):
    return self._children[0]

  @property
  def last_child(self):
    return self._children[len(self._children) - 1]

  def child(self, n):
    return self._children[n]

  def reserve_children(self, number_children):
    self._children = [None] * number_children

  def insert_child(self, n, child):
    self._children[n] = child

  def __ne__(self, obj):
    return not self == obj

  def __eq__(self, obj):
    return isinstance(obj, TreeNode) and obj.gid == self.gid

  def __hash__(self):
    return hash('treenode-' + str(self.gid))

  def __repr__(self):
    return 'TreeNode%d(kind=%s, data=%s, num_children=%d)'  \
           % (self.gid, repr(self.kind), repr(self.data), self.num_children())


Question: import opcode
from operator import itemgetter, attrgetter
from itertools import tee, izip
from ..utils.log import logger
from ..utils.structures import intervalmap
from ..bytecode.utils import show_bytecode
from .graph import DiGraph, Edge, Node, Walker, EdgeVisitor, Tree, TreeNode
from .graph import DominatorTree, ControlDependence
from .block import BasicBlock
from .ast import Statement, Expression
from .constraint import Constraint
from .python.effects import get_stack_effect
from .python.opcodes import *
  def block_constraints(self):
    """
      Returns the constraints associated with each ``N_CONDITION`` node
      in the CFG. This is lazily computed.
    """
    if self._conds is None:
      self.compute_conditions()
    return self._conds

  @property
  def frames(self):
    return self._frames

  @property
  def graph(self):
    """
      Returns the underlying graph that holds the CFG.
    """
    return self._graph

  @property
  def dominators(self):
    """
      Returns the ``DominatorTree`` that contains:
       - Dominator/Post-dominator tree (dict of IDom/PIDom)
       - Dominance/Post-domimance frontier (dict of CFG node -> set CFG nodes)
      This is lazily computed.
    """
    if self._dom is None:
      self._dom = DominatorTree(self)
    return self._dom

  @property
  def control_dependence(self):
    """
      Returns the ``ControlDependence`` graph. This is lazily computed.
    """
    if self._cdg is None:
      self._cdg = ControlDependence(self)
    return self._cdg

  def analyze(self):
    """
      Performs the CFA and stores the resulting CFG.
    """
    bytecode = self.decl.bytecode
    self.entry = BasicBlock(BasicBlock.ENTRY, self.decl, -1)
    self.exit = BasicBlock(BasicBlock.IMPLICIT_RETURN, self.decl, -1)

    self._blocks = ControlFlow.make_blocks(self.decl, bytecode)
    self.__build_flowgraph(bytecode)
    # logger.debug("CFG(%s) :=\n%s", self.decl, self.graph.to_dot())

  def __build_flowgraph(self, bytecode):
    g = DiGraph(multiple_edges=False)
    self.entry_node = g.make_add_node(kind=ControlFlow.N_ENTRY, data=self._entry)
    self.exit_node = g.make_add_node(kind=ControlFlow.N_IMPLICIT_RETURN, data=self._exit)

    self._block_idx_map = {}
    self._block_nodes = {}

    # Connect entry/implicit return blocks
    last_block_index, last_block = -1, None
    for block in self.blocks:
      self._block_idx_map[block.index] = block
      node_kind = ControlFlow.get_kind_from_block(block)
      block_node = g.make_add_node(kind=node_kind, data=block)
      self._block_nodes[block] = block_node
      if block.index == 0:
        g.make_add_edge(self.entry_node, self._block_nodes[block], kind=ControlFlow.E_UNCOND)
      if block.index >= last_block_index:
        last_block = block
        last_block_index = block.index
    g.make_add_edge(self._block_nodes[last_block], self.exit_node, kind=ControlFlow.E_UNCOND)

    sorted_blocks = sorted(self.blocks, key=attrgetter('_index'))
    i, length = 0, len(sorted_blocks)
    while i < length:
      cur_block = sorted_blocks[i]
      if cur_block.jumps:
        # Connect the current block to its jump targets
        for (jump_index, branch_kind) in cur_block.jumps:
          if jump_index <= ControlFlow.CFG_TMP_RETURN:
            continue
          target_block = self._block_idx_map[jump_index]
          g.make_add_edge(
            self._block_nodes[cur_block], self._block_nodes[target_block], kind=branch_kind)
      i += 1

    self._graph = g
    self.__finalize()
    self._graph.freeze()

    logger.debug("CFG :=\n%s", self._graph.to_dot())

  def __finalize(self):
    def has_true_false_branches(list_edges):
      has_true, has_false = False, False
      for edge in list_edges:
        if edge.kind == ControlFlow.E_TRUE: has_true = True
        elif edge.kind == ControlFlow.E_FALSE: has_false = True
      return has_true and has_false

    def get_cfg_tmp_values(node):
      values = set()
      for (jump_index, branch_kind) in node.data.jumps:
        if jump_index <= ControlFlow.CFG_TMP_RETURN:
          values.add(jump_index)
      return values

    def get_parent_loop(node):
      class BwdEdges(EdgeVisitor):
        def __init__(self):
          EdgeVisitor.__init__(self)
          self.edges = []

        def visit(self, edge):
          self.edges.append(edge)

      visitor = BwdEdges()

Answer:       walker = Walker(self.graph, visitor, backwards=True)