docs: def _handle_successor(self, job, successor, successors):
          """
          Returns a new CFGJob instance for further analysis, or None if there is no immediate state to perform the
          analysis on.
  
          :param CFGJob job: The current job.
          """
  
          state = successor
          all_successor_states = successors
          addr = job.addr
  
          # The PathWrapper instance to return
          pw = None
  
          job.successor_status[state] = ""
  
          new_state = state.copy()
          suc_jumpkind = state.history.jumpkind
          suc_exit_stmt_idx = state.scratch.exit_stmt_idx
          suc_exit_ins_addr = state.scratch.exit_ins_addr
  
          if suc_jumpkind in {'Ijk_EmWarn', 'Ijk_NoDecode', 'Ijk_MapFail', 'Ijk_NoRedir',
                              'Ijk_SigTRAP', 'Ijk_SigSEGV', 'Ijk_ClientReq'}:
              # Ignore SimExits that are of these jumpkinds
              job.successor_status[state] = "Skipped"
              return [ ]
  
          call_target = job.extra_info['call_target']
          if suc_jumpkind == "Ijk_FakeRet" and call_target is not None:
              # if the call points to a SimProcedure that doesn't return, we don't follow the fakeret anymore
              if self.project.is_hooked(call_target):
                  sim_proc = self.project._sim_procedures[call_target]
                  if sim_proc.NO_RET:
                      return [ ]
  
          # Get target address
          try:
              target_addr = state.solver.eval_one(state.ip)
          except (SimValueError, SimSolverModeError):
              # It cannot be concretized currently. Maybe we can handle it later, maybe it just cannot be concretized
              target_addr = None
              if suc_jumpkind == "Ijk_Ret":
                  target_addr = job.call_stack.current_return_target
                  if target_addr is not None:
                      new_state.ip = new_state.solver.BVV(target_addr, new_state.arch.bits)
  
          if target_addr is None:
              # Unlucky...
              return [ ]
  
          if state.thumb:
              # Make sure addresses are always odd. It is important to encode this information in the address for the
              # time being.
              target_addr |= 1
  
          # see if the target successor is in our whitelist
          if self._address_whitelist is not None:
              if target_addr not in self._address_whitelist:
                  l.debug("Successor %#x is not in the address whitelist. Skip.", target_addr)
                  return [ ]
  
          # see if this edge is in the base graph
          if self._base_graph is not None:
              # TODO: make it more efficient. the current implementation is half-assed and extremely slow
              for src_, dst_ in self._base_graph.edges():
                  if src_.addr == addr and dst_.addr == target_addr:
                      break
              else:
                  # not found
                  l.debug("Edge (%#x -> %#x) is not found in the base graph. Skip.", addr, target_addr)
                  return [ ]
  
          # Fix target_addr for syscalls
          if suc_jumpkind.startswith("Ijk_Sys"):
              syscall_proc = self.project.simos.syscall(new_state)
              if syscall_proc is not None:
                  target_addr = syscall_proc.addr
  
          self._pre_handle_successor_state(job.extra_info, suc_jumpkind, target_addr)
  
          if suc_jumpkind == "Ijk_FakeRet":
              if target_addr == job.extra_info['last_call_exit_target']:
                  l.debug("... skipping a fake return exit that has the same target with its call exit.")
                  job.successor_status[state] = "Skipped"
                  return [ ]
  
              if job.extra_info['skip_fakeret']:
                  l.debug('... skipping a fake return exit since the function it\'s calling doesn\'t return')
                  job.successor_status[state] = "Skipped - non-returning function 0x%x" % job.extra_info['call_target']
                  return [ ]
  
          # TODO: Make it optional
          if (suc_jumpkind == 'Ijk_Ret' and
                      self._call_depth is not None and
                      len(job.call_stack) <= 1
              ):
              # We cannot continue anymore since this is the end of the function where we started tracing
              l.debug('... reaching the end of the starting function, skip.')
              job.successor_status[state] = "Skipped - reaching the end of the starting function"
              return [ ]
  
          # Create the new call stack of target block
          new_call_stack = self._create_new_call_stack(addr, all_successor_states, job, target_addr,
                                                       suc_jumpkind)
          # Create the callstack suffix
          new_call_stack_suffix = new_call_stack.stack_suffix(self._context_sensitivity_level)
          # Tuple that will be used to index this exit
          new_tpl = self._generate_block_id(new_call_stack_suffix, target_addr, suc_jumpkind.startswith('Ijk_Sys'))
  
          # We might have changed the mode for this basic block
          # before. Make sure it is still running in 'fastpath' mode
          self._reset_state_mode(new_state, 'fastpath')
  
          pw = CFGJob(target_addr,
                      new_state,
                      self._context_sensitivity_level,
                      src_block_id=job.block_id,
                      src_exit_stmt_idx=suc_exit_stmt_idx,
                      src_ins_addr=suc_exit_ins_addr,
                      call_stack=new_call_stack,
                      jumpkind=suc_jumpkind,
                      )
          # Special case: If the binary has symbols and the target address is a function, but for some reason (e.g.,
          # a tail-call optimization) the CallStack's function address is still the old function address, we will have to
          # overwrite it here.
          if not self._is_call_jumpkind(pw.jumpkind):
              target_symbol = self.project.loader.find_symbol(target_addr)
              if target_symbol and target_symbol.is_function:
                  # Force update the function address
                  pw.func_addr = target_addr
  
          # Generate new exits
          if suc_jumpkind == "Ijk_Ret":
              # This is the real return exit
              job.successor_status[state] = "Appended"
  
          elif suc_jumpkind == "Ijk_FakeRet":
              # This is the default "fake" retn that generated at each
              # call. Save them first, but don't process them right
              # away
              # st = self.project._simos.prepare_call_state(new_state, initial_state=saved_state)
              st = new_state
              self._reset_state_mode(st, 'fastpath')
  
              pw = None # clear the job
              pe = PendingJob(job.func_addr,
                              job.extra_info['call_target'],
                              st,
                              job.block_id,
                              suc_exit_stmt_idx,
                              suc_exit_ins_addr,
                              new_call_stack
                              )
              self._pending_jobs[new_tpl] = pe
              self._register_analysis_job(pe.caller_func_addr, pe)
              job.successor_status[state] = "Pended"
  
          elif self._traced_addrs[new_call_stack_suffix][target_addr] >= 1 and suc_jumpkind == "Ijk_Ret":
              # This is a corner case for the f****** ARM instruction
              # like
              # BLEQ <address>
              # If we have analyzed the boring exit before returning from that called address, we will lose the link
              # between the last block of the function being called and the basic block it returns to. We cannot
              # reanalyze the basic block as we are not flow-sensitive, but we can still record the connection and make
              # for it afterwards.
              pass
  
          else:
              job.successor_status[state] = "Appended"
  
          if job.extra_info['is_call_jump'] and job.extra_info['call_target'] in self._non_returning_functions:
              job.extra_info['skip_fakeret'] = True
  
          if not pw:
              return [ ]
  
          if self._base_graph is not None:
              # remove all existing jobs that has the same block ID
              if next((en for en in self.jobs if en.block_id == pw.block_id), None):
                  # TODO: this is very hackish. Reimplement this logic later
                  self._job_info_queue = [entry for entry in self._job_info_queue if entry.job.block_id != pw.block_id]
  
          # register the job
          self._register_analysis_job(pw.func_addr, pw)
  
          return [ pw ]