docs: def make_functions(self):
          """
          Revisit the entire control flow graph, create Function instances accordingly, and correctly put blocks into
          each function.
  
          Although Function objects are crated during the CFG recovery, they are neither sound nor accurate. With a
          pre-constructed CFG, this method rebuilds all functions bearing the following rules:
  
              - A block may only belong to one function.
              - Small functions lying inside the startpoint and the endpoint of another function will be merged with the
                other function
              - Tail call optimizations are detected.
              - PLT stubs are aligned by 16.
  
          :return: None
          """
  
          tmp_functions = self.kb.functions.copy()
  
          for function in tmp_functions.values():
              function.mark_nonreturning_calls_endpoints()
  
          # Clear old functions dict
          self.kb.functions.clear()
  
          blockaddr_to_function = { }
          traversed_cfg_nodes = set()
  
          function_nodes = set()
  
          # Find nodes for beginnings of all functions
          for _, dst, data in self.graph.edges(data=True):
              jumpkind = data.get('jumpkind', "")
              if jumpkind == 'Ijk_Call' or jumpkind.startswith('Ijk_Sys'):
                  function_nodes.add(dst)
  
          entry_node = self.get_any_node(self._binary.entry)
          if entry_node is not None:
              function_nodes.add(entry_node)
  
          for n in self.graph.nodes():
              funcloc = self._loc_to_funcloc(n.addr)
              if funcloc in tmp_functions:
                  function_nodes.add(n)
  
          # traverse the graph starting from each node, not following call edges
          # it's important that we traverse all functions in order so that we have a greater chance to come across
          # rational functions before its irrational counterparts (e.g. due to failed jump table resolution)
  
          min_stage_2_progress = 50.0
          max_stage_2_progress = 90.0
          nodes_count = len(function_nodes)
          for i, fn in enumerate(function_nodes):
  
              if self._show_progressbar or self._progress_callback:
                  progress = min_stage_2_progress + (max_stage_2_progress - min_stage_2_progress) * (i * 1.0 / nodes_count)
                  self._update_progress(progress)
  
              self._graph_bfs_custom(self.graph, [ fn ], self._graph_traversal_handler, blockaddr_to_function,
                                     tmp_functions, traversed_cfg_nodes
                                     )
  
          # Don't forget those small function chunks that are not called by anything.
          # There might be references to them from data, or simply references that we cannot find via static analysis
  
          secondary_function_nodes = set()
          # add all function chunks ("functions" that are not called from anywhere)
          for func_addr in tmp_functions:
              node = self.get_any_node(func_addr)
              if node is None:
                  continue
              if node.addr not in blockaddr_to_function:
                  secondary_function_nodes.add(node)
  
          missing_cfg_nodes = set(self.graph.nodes()) - traversed_cfg_nodes
          missing_cfg_nodes = { node for node in missing_cfg_nodes if node.function_address is not None }
          if missing_cfg_nodes:
              l.debug('%d CFGNodes are missing in the first traversal.', len(missing_cfg_nodes))
              secondary_function_nodes |=  missing_cfg_nodes
  
          min_stage_3_progress = 90.0
          max_stage_3_progress = 99.9
  
          nodes_count = len(secondary_function_nodes)
          for i, fn in enumerate(secondary_function_nodes):
  
              if self._show_progressbar or self._progress_callback:
                  progress = min_stage_3_progress + (max_stage_3_progress - min_stage_3_progress) * (i * 1.0 / nodes_count)
                  self._update_progress(progress)
  
              self._graph_bfs_custom(self.graph, [fn], self._graph_traversal_handler, blockaddr_to_function,
                                     tmp_functions
                                     )
  
          to_remove = set()
  
          # remove empty functions
          for function in self.kb.functions.values():
              if function.startpoint is None:
                  to_remove.add(function.addr)
  
          for addr in to_remove:
              del self.kb.functions[addr]
  
          # Update CFGNode.function_address
          for node in self.model.nodes():
              if node.addr in blockaddr_to_function:
                  node.function_address = blockaddr_to_function[node.addr].addr