def _analyze_function_features(self):
        """
        For each function in the function_manager, try to determine if it returns or not. A function does not return if
        it calls another function that is known to be not returning, and this function does not have other exits.

        We might as well analyze other features of functions in the future.
        """

        changes = {
            'functions_return': [],
            'functions_do_not_return': []
        }

        for func in self.kb.functions.values():
            if func.returning is not None:
                # It has been determined before. Skip it
                continue

            # If there is at least one endpoint, then this function is definitely returning
            if func.endpoints:
                changes['functions_return'].append(func)
                func.returning = True
                continue

            # This function does not have endpoints. It's either because it does not return, or we haven't analyzed all
            # blocks of it.

            # Let's first see if it's a known SimProcedure that does not return
            if self.project.is_hooked(func.addr):
                hooker = self.project.hooked_by(func.addr)
                if hasattr(hooker, 'NO_RET') and hooker.NO_RET:
                    func.returning = False
                    changes['functions_do_not_return'].append(func)
                    continue

            tmp_graph = networkx.DiGraph(func.graph)
            # Remove all fakeret edges from a non-returning function
            edges_to_remove = [ ]
            for src, dst, data in tmp_graph.edges_iter(data=True):
                if data['type'] == 'fake_return':
                    edges = [ edge for edge in func.transition_graph.edges(
                                                nbunch=[src], data=True
                                                ) if edge[2]['type'] != 'fake_return'
                              ]
                    if not edges:
                        # We don't know which function it's supposed to call
                        # skip
                        continue
                    target_addr = edges[0][1].addr
                    target_func = self.kb.functions.function(addr=target_addr)
                    if target_func.returning is False:
                        edges_to_remove.append((src, dst))

            for src, dst in edges_to_remove:
                tmp_graph.remove_edge(src, dst)

            # We check all its current nodes in transition graph whose out degree is 0 (call them
            # temporary endpoints)
            temporary_local_endpoints = [a for a in tmp_graph.nodes()
                                         if tmp_graph.out_degree(a) == 0]

            if not temporary_local_endpoints:
                # It might be empty if our transition graph is fucked up (for example, the freaking
                # SimProcedureContinuation can be used in any SimProcedure and almost always creates loops in its
                # transition graph). Just ignore it.
                continue

            all_endpoints_returning = []
            temporary_endpoints = []

            for local_endpoint in temporary_local_endpoints:

                if local_endpoint in func.transition_graph.nodes():
                    out_edges = func.transition_graph.out_edges([local_endpoint], data=True)

                    if not out_edges:
                        temporary_endpoints.append(local_endpoint)

                    else:
                        for src, dst, data in out_edges:
                            t = data['type']

                            if t != 'fake_return':
                                temporary_endpoints.append(dst)

            for endpoint in temporary_endpoints:
                if func.nodes in endpoint:
                    # Somehow analysis terminated here (e.g. an unsupported instruction, or it doesn't generate an exit)

                    if isinstance(endpoint, Function):
                        all_endpoints_returning.append(endpoint.returning)

                    else:
                        successors = [ dst for _, dst in func.transition_graph.out_edges(endpoint) ]
                        all_noret = True
                        for suc in successors:
                            if isinstance(suc, Function):
                                if suc.returning is not False:
                                    all_noret = False
                            else:
                                n = self.get_any_node(endpoint.addr, is_syscall=func.is_syscall)
                                if n:
                                    # It might be a SimProcedure or a syscall, or even a normal block
                                    if n.no_ret is not True:
                                        all_noret = False

                        if all_noret:
                            all_endpoints_returning.append(True)
                        else:
                            all_endpoints_returning.append(None)

                else:
                    # This block is not a member of the current function
                    call_target = endpoint

                    call_target_func = self.kb.functions.function(call_target)
                    if call_target_func is None:
                        all_endpoints_returning.append(None)
                        continue

                    all_endpoints_returning.append(call_target_func.returning)

            if all([i is False for i in all_endpoints_returning]):
                # All target functions that this function calls is not returning
                func.returning = False
                changes['functions_do_not_return'].append(func)

        return changes
dataset/ETHPy150Open angr/angr/angr/analyses/cfg_accurate.py/CFGAccurate._analyze_function_features/swapped operands of `in`