docs: def step(self, stash='active', n=None, selector_func=None, step_func=None,
               successor_func=None, until=None, filter_func=None, **run_args):
          """
          Step a stash of states forward and categorize the successors appropriately.
  
          The parameters to this function allow you to control everything about the stepping and
          categorization process.
  
          :param stash:           The name of the stash to step (default: 'active')
          :param selector_func:   If provided, should be a function that takes a state and returns a
                                  boolean. If True, the state will be stepped. Otherwise, it will be
                                  kept as-is.
          :param step_func:       If provided, should be a function that takes a SimulationManager and
                                  returns a SimulationManager. Will be called with the SimulationManager
                                  at every step. Note that this function should not actually perform any
                                  stepping - it is meant to be a maintenance function called after each step.
          :param successor_func:  If provided, should be a function that takes a state and return its successors.
                                  Otherwise, project.factory.successors will be used.
          :param filter_func:     If provided, should be a function that takes a state and return the name
                                  of the stash, to which the state should be moved.
          :param until:           (DEPRECATED) If provided, should be a function that takes a SimulationManager and
                                  returns True or False. Stepping will terminate when it is True.
          :param n:               (DEPRECATED) The number of times to step (default: 1 if "until" is not provided)
  
          Additionally, you can pass in any of the following keyword args for project.factory.successors:
  
          :param jumpkind:        The jumpkind of the previous exit
          :param addr:            An address to execute at instead of the state's ip.
          :param stmt_whitelist:  A list of stmt indexes to which to confine execution.
          :param last_stmt:       A statement index at which to stop execution.
          :param thumb:           Whether the block should be lifted in ARM's THUMB mode.
          :param backup_state:    A state to read bytes from instead of using project memory.
          :param opt_level:       The VEX optimization level to use.
          :param insn_bytes:      A string of bytes to use for the block instead of the project.
          :param size:            The maximum size of the block, in bytes.
          :param num_inst:        The maximum number of instructions.
          :param traceflags:      traceflags to be passed to VEX. Default: 0
  
          :returns:           The simulation manager, for chaining.
          :rtype:             SimulationManager
          """
          l.info("Stepping %s of %s", stash, self)
          # 8<----------------- Compatibility layer -----------------
          if n is not None or until is not None:
              if once('simgr_step_n_until'):
                  print("\x1b[31;1mDeprecation warning: the use of `n` and `until` arguments is deprecated. "
                        "Consider using simgr.run() with the same arguments if you want to specify "
                        "a number of steps or an additional condition on when to stop the execution.\x1b[0m")
              return self.run(stash, n, until, selector_func=selector_func, step_func=step_func,
                              successor_func=successor_func, filter_func=filter_func, **run_args)
          # ------------------ Compatibility layer ---------------->8
          bucket = defaultdict(list)
  
          for state in self._fetch_states(stash=stash):
  
              goto = self.filter(state, filter_func=filter_func)
              if isinstance(goto, tuple):
                  goto, state = goto
  
              if goto not in (None, stash):
                  bucket[goto].append(state)
                  continue
  
              if not self.selector(state, selector_func=selector_func):
                  bucket[stash].append(state)
                  continue
  
              pre_errored = len(self._errored)
              successors = self.step_state(state, successor_func=successor_func, **run_args)
  
              # handle degenerate stepping cases here. desired behavior:
              # if a step produced only unsat states, always add them to the unsat stash since this usually indicates a bug
              # if a step produced sat states and save_unsat is False, drop the unsats
              # if a step produced no successors, period, add the original state to deadended
  
              # first check if anything happened besides unsat. that gates all this behavior
              if not any(v for k, v in successors.items() if k != 'unsat') and len(self._errored) == pre_errored:
                  # then check if there were some unsats
                  if successors.get('unsat', []):
                      # only unsats. current setup is acceptable.
                      pass
                  else:
                      # no unsats. we've deadended.
                      bucket['deadended'].append(state)
                      continue
              else:
                  # there were sat states. it's okay to drop the unsat ones if the user said so.
                  if not self._save_unsat:
                      successors.pop('unsat', None)
  
              for to_stash, successor_states in successors.items():
                  bucket[to_stash or stash].extend(successor_states)
  
          self._clear_states(stash=stash)
          for to_stash, states in bucket.items():
              self._store_states(to_stash or stash, states)
  
          if step_func is not None:
              return step_func(self)
          return self