If source and destination belong to the same function, and the following criteria apply:
        - source node has only one default exit
        - destination is not one of the known functions
        - destination does not belong to another function, or destination belongs to the same function that
          source belongs to
        - at the end of the block, the SP offset is 0
        - for all other edges that are pointing to the destination node, their source nodes must only have one default
          exit, too

        :return:    True if it is a tail-call optimization. False otherwise.
        :rtype:     bool

def _is_tail_call_optimization(self, g : networkx.DiGraph, src_addr, dst_addr, src_function, all_edges,
                                   known_functions, blockaddr_to_function):
        """
        If source and destination belong to the same function, and the following criteria apply:
        - source node has only one default exit
        - destination is not one of the known functions
        - destination does not belong to another function, or destination belongs to the same function that
          source belongs to
        - at the end of the block, the SP offset is 0
        - for all other edges that are pointing to the destination node, their source nodes must only have one default
          exit, too

        :return:    True if it is a tail-call optimization. False otherwise.
        :rtype:     bool
        """

        def _has_more_than_one_exit(node_):
            return len(g.out_edges(node_)) > 1

        if len(all_edges) == 1 and dst_addr != src_addr:
            the_edge = next(iter(all_edges))
            _, dst, data = the_edge
            if data.get('stmt_idx', None) != DEFAULT_STATEMENT:
                return False

            dst_in_edges = g.in_edges(dst, data=True)
            if len(dst_in_edges) > 1:
                # there are other edges going to the destination node. check all edges to make sure all source nodes
                # only have one default exit
                if any(data.get('stmt_idx', None) != DEFAULT_STATEMENT for _, _, data in dst_in_edges):
                    # some nodes are jumping to the destination node via non-default edges. skip.
                    return False
                if any(_has_more_than_one_exit(src_) for src_, _, _ in dst_in_edges):
                    # at least one source node has more than just the default exit. skip.
                    return False

            candidate = False
            if dst_addr in known_functions:
                # dst_addr cannot be the same as src_function.addr. Pass
                pass
            elif dst_addr in blockaddr_to_function:
                # it seems that we already know where this function should belong to. Pass.
                dst_func = blockaddr_to_function[dst_addr]
                if dst_func is src_function:
                    # they belong to the same function right now, but they'd better not
                    candidate = True
                    # treat it as a tail-call optimization
            else:
                # we don't know where it belongs to
                # treat it as a tail-call optimization
                candidate = True

            if candidate:
                regs = {self.project.arch.sp_offset}
                if hasattr(self.project.arch, 'bp_offset') and self.project.arch.bp_offset is not None:
                    regs.add(self.project.arch.bp_offset)
                sptracker = self.project.analyses.StackPointerTracker(src_function, regs, track_memory=self._sp_tracking_track_memory)
                sp_delta = sptracker.offset_after_block(src_addr, self.project.arch.sp_offset)
                if sp_delta == 0:
                    return True

        return False