Graph traversal handler. It takes in a node or an edge, and create new functions or add nodes to existing
        functions accordingly. Oh, it also create edges on the transition map of functions.

        :param g:           The control flow graph that is currently being traversed.
        :param CFGNode src: Beginning of the edge, or a single node when dst is None.
        :param CFGNode dst: Destination of the edge. For processing a single node, `dst` is None.
        :param dict data: Edge data in the CFG. 'jumpkind' should be there if it's not None.
        :param dict blockaddr_to_function: A mapping between block addresses to Function instances.
        :param angr.knowledge_plugins.FunctionManager known_functions: Already recovered functions.
        :param list or None all_edges: All edges going out from src.
        :return: None

def _graph_traversal_handler(self, g, src, dst, data, blockaddr_to_function, known_functions, all_edges):
        """
        Graph traversal handler. It takes in a node or an edge, and create new functions or add nodes to existing
        functions accordingly. Oh, it also create edges on the transition map of functions.

        :param g:           The control flow graph that is currently being traversed.
        :param CFGNode src: Beginning of the edge, or a single node when dst is None.
        :param CFGNode dst: Destination of the edge. For processing a single node, `dst` is None.
        :param dict data: Edge data in the CFG. 'jumpkind' should be there if it's not None.
        :param dict blockaddr_to_function: A mapping between block addresses to Function instances.
        :param angr.knowledge_plugins.FunctionManager known_functions: Already recovered functions.
        :param list or None all_edges: All edges going out from src.
        :return: None
        """

        src_addr = src.addr
        src_function = self._addr_to_function(src_addr, blockaddr_to_function, known_functions)

        if src_addr not in src_function.block_addrs_set:
            n = self.model.get_any_node(src_addr)
            if n is None: node = src_addr
            else: node = self._to_snippet(n)
            self.kb.functions._add_node(src_function.addr, node)

        if data is None:
            # it's a single node only
            return

        jumpkind = data['jumpkind']

        if jumpkind == 'Ijk_Ret':
            n = self.model.get_any_node(src_addr)
            if n is None: from_node = src_addr
            else: from_node = self._to_snippet(n)
            self.kb.functions._add_return_from(src_function.addr, from_node, None)

        if dst is None:
            return

        dst_addr = dst.addr

        # get instruction address and statement index
        ins_addr = data.get('ins_addr', None)
        stmt_idx = data.get('stmt_idx', None)

        if jumpkind == 'Ijk_Call' or jumpkind.startswith('Ijk_Sys'):

            is_syscall = jumpkind.startswith('Ijk_Sys')

            # It must be calling a function
            dst_function = self._addr_to_function(dst_addr, blockaddr_to_function, known_functions)

            n = self.model.get_any_node(src_addr)
            if n is None: src_snippet = self._to_snippet(addr=src_addr, base_state=self._base_state)
            else:
                src_snippet = self._to_snippet(cfg_node=n)

            # HACK: FIXME: We need a better way of representing unresolved calls and whether they return.
            # For now, assume UnresolvedTarget returns if we're calling to it

            # If the function doesn't return, don't add a fakeret!
            if not all_edges or (dst_function.returning is False and not dst_function.name == 'UnresolvableCallTarget'):
                fakeret_node = None
            else:
                fakeret_node = self._one_fakeret_node(all_edges)

            if fakeret_node is None:
                fakeret_snippet = None
            else:
                fakeret_snippet = self._to_snippet(cfg_node=fakeret_node)

            if isinstance(dst_addr, SootAddressDescriptor):
                dst_addr = dst_addr.method

            self.kb.functions._add_call_to(src_function.addr, src_snippet, dst_addr, fakeret_snippet, syscall=is_syscall,
                                           ins_addr=ins_addr, stmt_idx=stmt_idx)

            if dst_function.returning:
                returning_target = src.addr + src.size
                if returning_target not in blockaddr_to_function:
                    if returning_target not in known_functions:
                        blockaddr_to_function[returning_target] = src_function
                    else:
                        self._addr_to_function(returning_target, blockaddr_to_function, known_functions)

                to_outside = not blockaddr_to_function[returning_target] is src_function

                n = self.model.get_any_node(returning_target)
                if n is None:
                    returning_snippet = self._to_snippet(addr=returning_target, base_state=self._base_state)
                else:
                    returning_snippet = self._to_snippet(cfg_node=n)

                self.kb.functions._add_fakeret_to(src_function.addr, src_snippet, returning_snippet, confirmed=True,
                                                  to_outside=to_outside
                                                  )

        elif jumpkind in ('Ijk_Boring', 'Ijk_InvalICache'):

            # convert src_addr and dst_addr to CodeNodes
            n = self.model.get_any_node(src_addr)
            if n is None: src_node = src_addr
            else: src_node = self._to_snippet(cfg_node=n)

            n = self.model.get_any_node(dst_addr)
            if n is None: dst_node = dst_addr
            else: dst_node = self._to_snippet(cfg_node=n)

            # pre-check: if source and destination do not belong to the same section, it must be jumping to another
            # function
            belong_to_same_section = self._addrs_belong_to_same_section(src_addr, dst_addr)
            if not belong_to_same_section:
                _ = self._addr_to_function(dst_addr, blockaddr_to_function, known_functions)

            if self._detect_tail_calls:
                if self._is_tail_call_optimization(g, src_addr, dst_addr, src_function, all_edges, known_functions,
                                                   blockaddr_to_function):
                    l.debug("Possible tail-call optimization detected at function %#x.", dst_addr)
                    # it's (probably) a tail-call optimization. we should make the destination node a new function
                    # instead.
                    blockaddr_to_function.pop(dst_addr, None)
                    _ = self._addr_to_function(dst_addr, blockaddr_to_function, known_functions)
                    self.kb.functions._add_outside_transition_to(src_function.addr, src_node, dst_node,
                                                                 to_function_addr=dst_addr
                                                                 )

            # is it a jump to another function?
            if isinstance(dst_addr, SootAddressDescriptor):
                is_known_function_addr = dst_addr.method in known_functions and dst_addr.method.addr == dst_addr
            else:
                is_known_function_addr = dst_addr in known_functions

            if is_known_function_addr or (
                dst_addr in blockaddr_to_function and blockaddr_to_function[dst_addr] is not src_function
            ):
                # yes it is
                dst_function_addr = blockaddr_to_function[dst_addr].addr if dst_addr in blockaddr_to_function else \
                    dst_addr

                self.kb.functions._add_outside_transition_to(src_function.addr, src_node, dst_node,
                                                             to_function_addr=dst_function_addr
                                                             )

                _ = self._addr_to_function(dst_addr, blockaddr_to_function, known_functions)
            else:
                # no it's not
                # add the transition code

                if dst_addr not in blockaddr_to_function:
                    blockaddr_to_function[dst_addr] = src_function

                self.kb.functions._add_transition_to(src_function.addr, src_node, dst_node, ins_addr=ins_addr,
                                                     stmt_idx=stmt_idx
                                                     )

        elif jumpkind == 'Ijk_FakeRet':

            # convert src_addr and dst_addr to CodeNodes
            n = self.model.get_any_node(src_addr)
            if n is None:
                src_node = src_addr
            else:
                src_node = self._to_snippet(n)

            n = self.model.get_any_node(dst_addr)
            if n is None:
                dst_node = dst_addr
            else:
                dst_node = self._to_snippet(n)


            if dst_addr not in blockaddr_to_function:
                if isinstance(dst_addr, SootAddressDescriptor):
                    if dst_addr.method not in known_functions:
                        blockaddr_to_function[dst_addr] = src_function
                        target_function = src_function
                    else:
                        target_function = self._addr_to_function(dst_addr, blockaddr_to_function, known_functions)
                else:
                    if dst_addr not in known_functions:
                        blockaddr_to_function[dst_addr] = src_function
                        target_function = src_function
                    else:
                        target_function = self._addr_to_function(dst_addr, blockaddr_to_function, known_functions)
            else:
                target_function = blockaddr_to_function[dst_addr]

            # Figure out if the function called (not the function returned to) returns.
            # We may have determined that this does not happen, since the time this path
            # was scheduled for exploration
            called_function = None
            # Try to find the call that this fakeret goes with
            for _, d, e in all_edges:
                if e['jumpkind'] == 'Ijk_Call':
                    if d.addr in blockaddr_to_function:
                        called_function = blockaddr_to_function[d.addr]
                        break
            # We may have since figured out that the called function doesn't ret.
            # It's important to assume that all unresolved targets do return
            if called_function is not None and \
                    called_function.returning is False:
                return

            to_outside = not target_function is src_function

            # FIXME: Not sure we should confirm this fakeret or not.
            self.kb.functions._add_fakeret_to(src_function.addr, src_node, dst_node, confirmed=True,
                                              to_outside=to_outside, to_function_addr=target_function.addr
                                              )

        else:
            l.debug('Ignored jumpkind %s', jumpkind)