docs: def unroll_loops(self, max_loop_unrolling_times):
          """
          Unroll loops for each function. The resulting CFG may still contain loops due to recursion, function calls, etc.
  
          :param int max_loop_unrolling_times: The maximum iterations of unrolling.
          :return: None
          """
  
          if not isinstance(max_loop_unrolling_times, int) or \
                          max_loop_unrolling_times < 0:
              raise AngrCFGError('Max loop unrolling times must be set to an integer greater than or equal to 0 if ' +
                                 'loop unrolling is enabled.')
  
          def _unroll(graph, loop):
              """
              The loop callback method where loops are unrolled.
  
              :param networkx.DiGraph graph: The control flow graph.
              :param angr.analyses.loopfinder.Loop loop: The loop instance.
              :return: None
              """
  
              for back_edge in loop.continue_edges:
                  loop_body_addrs = {n.addr for n in loop.body_nodes}
                  src_blocknode = back_edge[0]  # type: angr.knowledge.codenode.BlockNode
                  dst_blocknode = back_edge[1]  # type: angr.knowledge.codenode.BlockNode
  
                  for src in self.get_all_nodes(src_blocknode.addr):
                      for dst in graph.successors(src):
                          if dst.addr != dst_blocknode.addr:
                              continue
  
                          # Duplicate the dst node
                          new_dst = dst.copy()
                          new_dst.looping_times = dst.looping_times + 1
                          if (new_dst not in graph and
                                  # If the new_dst is already in the graph, we don't want to keep unrolling
                                  # the this loop anymore since it may *create* a new loop. Of course we
                                  # will lose some edges in this way, but in general it is acceptable.
                                  new_dst.looping_times <= max_loop_unrolling_times
                              ):
                              # Log all successors of the dst node
                              dst_successors = graph.successors(dst)
                              # Add new_dst to the graph
                              edge_data = graph.get_edge_data(src, dst)
                              graph.add_edge(src, new_dst, **edge_data)
                              for ds in dst_successors:
                                  if ds.looping_times == 0 and ds.addr not in loop_body_addrs:
                                      edge_data = graph.get_edge_data(dst, ds)
                                      graph.add_edge(new_dst, ds, **edge_data)
  
                          graph.remove_edge(src, dst)
  
          self._detect_loops(loop_callback=_unroll)