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.

        :param bool all_funcs_completed:    Ignore _completed_functions set and treat all functions as completed. This
                                            can be set to True after the entire CFG is built and _post_analysis() is
                                            called (at which point analysis on all functions must be completed).

def _analyze_function_features(self, all_funcs_completed=False):
        """
        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.

        :param bool all_funcs_completed:    Ignore _completed_functions set and treat all functions as completed. This
                                            can be set to True after the entire CFG is built and _post_analysis() is
                                            called (at which point analysis on all functions must be completed).
        """

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

        if self._updated_nonreturning_functions is not None:
            all_func_addrs = self._updated_nonreturning_functions

            # Convert addresses to objects
            all_functions = [ self.kb.functions.get_by_addr(f) for f in all_func_addrs
                              if self.kb.functions.contains_addr(f) ]

        else:
            all_functions = list(self.kb.functions.values())

        analyzed_functions = set()
        # short-hand
        functions = self.kb.functions  # type: angr.knowledge.FunctionManager

        while all_functions:
            func = all_functions.pop(-1)  # type: angr.knowledge.Function
            analyzed_functions.add(func.addr)

            if func.returning is not None:
                # It has been determined before. Skip it
                continue

            returning = self._determine_function_returning(func, all_funcs_completed=all_funcs_completed)

            if returning:
                func.returning = True
                changes['functions_return'].append(func)
            elif returning is False:
                func.returning = False
                changes['functions_do_not_return'].append(func)

            if returning is not None:
                # Add all callers of this function to all_functions list
                if func.addr in functions.callgraph:
                    callers = functions.callgraph.predecessors(func.addr)
                    for caller in callers:
                        if caller in analyzed_functions:
                            continue
                        if functions.contains_addr(caller):
                            all_functions.append(functions.get_by_addr(caller))

        return changes