Get a collection of successors out of the current job.

        :param CFGJob job:  The CFGJob instance.
        :return:            A collection of successors.
        :rtype:             list

def _get_successors(self, job):
        """
        Get a collection of successors out of the current job.

        :param CFGJob job:  The CFGJob instance.
        :return:            A collection of successors.
        :rtype:             list
        """

        addr = job.addr
        sim_successors = job.sim_successors
        cfg_node = job.cfg_node
        input_state = job.state
        func_addr = job.func_addr

        # check step limit
        if self._max_steps is not None:
            depth = cfg_node.depth
            if depth >= self._max_steps:
                return [ ]

        successors = [ ]
        is_indirect_jump = sim_successors.sort == 'IRSB' and self._is_indirect_jump(cfg_node, sim_successors)
        indirect_jump_resolved_by_resolvers = False

        if is_indirect_jump and self._resolve_indirect_jumps:
            # Try to resolve indirect jumps
            irsb = input_state.block().vex

            resolved, resolved_targets, ij = self._indirect_jump_encountered(addr, cfg_node, irsb, func_addr,
                                                                             stmt_idx=DEFAULT_STATEMENT)
            if resolved:
                successors = self._convert_indirect_jump_targets_to_states(job, resolved_targets)
                if ij:
                    self._indirect_jump_resolved(ij, ij.addr, None, resolved_targets)
            else:
                # Try to resolve this indirect jump using heavier approaches
                resolved_targets = self._process_one_indirect_jump(ij)
                successors = self._convert_indirect_jump_targets_to_states(job, resolved_targets)

            if successors:
                indirect_jump_resolved_by_resolvers = True
            else:
                # It's unresolved. Add it to the wait list (but apparently we don't have any better way to resolve it
                # right now).
                self._indirect_jumps_to_resolve.add(ij)

        if not successors:
            # Get all successors of this block
            successors = (sim_successors.flat_successors + sim_successors.unsat_successors) \
                if addr not in self._avoid_runs else []

        # Post-process successors
        successors, job.extra_info = self._post_process_successors(input_state, sim_successors, successors)

        all_successors = successors + sim_successors.unconstrained_successors

        # make sure FakeRets are at the last
        all_successors = [ suc for suc in all_successors if suc.history.jumpkind != 'Ijk_FakeRet' ] + \
                         [ suc for suc in all_successors if suc.history.jumpkind == 'Ijk_FakeRet' ]

        if self._keep_state:
            cfg_node.final_states = all_successors[::]

        if is_indirect_jump and not indirect_jump_resolved_by_resolvers:
            # For indirect jumps, filter successors that do not make sense
            successors = self._filter_insane_successors(successors)

        successors = self._try_resolving_indirect_jumps(sim_successors,
                                                        cfg_node,
                                                        func_addr,
                                                        successors,
                                                        job.exception_info,
                                                        self._block_artifacts)
        # Remove all successors whose IP is symbolic
        successors = [ s for s in successors if not s.ip.symbolic ]

        # Add additional edges supplied by the user
        successors = self._add_additional_edges(input_state, sim_successors, cfg_node, successors)

        # if base graph is used, add successors implied from the graph
        if self._base_graph:
            basegraph_successor_addrs = set()
            for src_, dst_ in self._base_graph.edges():
                if src_.addr == addr:
                    basegraph_successor_addrs.add(dst_.addr)
            successor_addrs = {s.solver.eval(s.ip) for s in successors}
            extra_successor_addrs = basegraph_successor_addrs - successor_addrs

            if all_successors:  # make sure we have a base state to use
                base_state = all_successors[0]  # TODO: for calls, we want to use the fake_ret state

                for s_addr in extra_successor_addrs:
                    # an extra target
                    successor_state = base_state.copy()
                    successor_state.ip = s_addr
                    successors.append(successor_state)
            else:
                if extra_successor_addrs:
                    l.error('CFGEmulated terminates at %#x although base graph provided more exits.', addr)

        if not successors:
            # There is no way out :-(
            # Log it first
            self._push_unresolvable_run(addr)

            if sim_successors.sort == 'SimProcedure' and isinstance(sim_successors.artifacts['procedure'],
                    SIM_PROCEDURES["stubs"]["PathTerminator"]):
                # If there is no valid exit in this branch and it's not
                # intentional (e.g. caused by a SimProcedure that does not
                # do_return) , we should make it return to its call-site. However,
                # we don't want to use its state anymore as it might be corrupted.
                # Just create an edge in the graph.
                return_target = job.call_stack.current_return_target
                if return_target is not None:
                    new_call_stack = job.call_stack_copy()
                    return_target_key = self._generate_block_id(
                        new_call_stack.stack_suffix(self.context_sensitivity_level),
                        return_target,
                        False
                    )  # You can never return to a syscall

                    if not cfg_node.instruction_addrs:
                        ret_ins_addr = None
                    else:
                        if self.project.arch.branch_delay_slot:
                            if len(cfg_node.instruction_addrs) > 1:
                                ret_ins_addr = cfg_node.instruction_addrs[-2]
                            else:
                                l.error('At %s: expecting more than one instruction. Only got one.', cfg_node)
                                ret_ins_addr = None
                        else:
                            ret_ins_addr = cfg_node.instruction_addrs[-1]

                    # Things might be a bit difficult here. _graph_add_edge() requires both nodes to exist, but here
                    # the return target node may not exist yet. If that's the case, we will put it into a "delayed edge
                    # list", and add this edge later when the return target CFGNode is created.
                    if return_target_key in self._nodes:
                        self._graph_add_edge(job.block_id, return_target_key, jumpkind='Ijk_Ret',
                                             stmt_id=DEFAULT_STATEMENT, ins_addr=ret_ins_addr)
                    else:
                        self._pending_edges[return_target_key].append((job.block_id, return_target_key,
                                                                       {
                                                                           'jumpkind': 'Ijk_Ret',
                                                                           'stmt_id': DEFAULT_STATEMENT,
                                                                           'ins_addr': ret_ins_addr,
                                                                        }
                                                                       )
                                                                      )

            else:
                # There are no successors, but we still want to update the function graph
                artifacts = job.sim_successors.artifacts
                if 'irsb' in artifacts and 'insn_addrs' in artifacts and artifacts['insn_addrs']:
                    the_irsb = artifacts['irsb']
                    insn_addrs = artifacts['insn_addrs']
                    self._handle_job_without_successors(job, the_irsb, insn_addrs)

        # TODO: replace it with a DDG-based function IO analysis
        # handle all actions
        if successors:
            self._handle_actions(successors[0],
                                 sim_successors,
                                 job.current_function,
                                 job.current_stack_pointer,
                                 set(),
                                 )

        return successors