Create a list of new VFG jobs for the successor state.

        :param VFGJob job:                 The VFGJob instance.
        :param SimState successor: The succeeding state.
        :param BlockID new_block_id:       Block ID for the new VFGJob
        :param new_call_stack:             The new callstack.
        :return:                           A list of newly created VFG jobs.
        :rtype:                            list

def _create_new_jobs(self, job, successor, new_block_id, new_call_stack):
        """
        Create a list of new VFG jobs for the successor state.

        :param VFGJob job:                 The VFGJob instance.
        :param SimState successor: The succeeding state.
        :param BlockID new_block_id:       Block ID for the new VFGJob
        :param new_call_stack:             The new callstack.
        :return:                           A list of newly created VFG jobs.
        :rtype:                            list
        """

        # TODO: basic block stack is probably useless

        jumpkind = successor.history.jumpkind
        stmt_idx = successor.scratch.stmt_idx
        ins_addr = successor.scratch.ins_addr
        # Make a copy of the state in case we use it later
        successor_state = successor.copy()
        successor_addr = successor_state.solver.eval(successor_state.ip)

        new_jobs = [ ]

        if jumpkind == "Ijk_FakeRet":
            assert job.is_call_jump

            # This is the default "fake" return successor generated at each call, if and only if the target function
            # returns.

            # if the call is skipped (for whatever reason, like we reached the interfunction tracing limit), we use
            # this FakeRet successor as the final state of the function. Otherwise we save the FakeRet state in case the
            # callee does not return normally, but don't process them right away.

            # Clear the useless values (like return addresses, parameters) on stack if needed
            if self._cfg is not None:
                current_function = self.kb.functions.function(job.call_target)
                if current_function is not None:
                    sp_difference = current_function.sp_delta
                else:
                    sp_difference = 0
                reg_sp_offset = successor_state.arch.sp_offset
                reg_sp_expr = successor_state.registers.load(reg_sp_offset) + sp_difference
                successor_state.registers.store(successor_state.arch.sp_offset, reg_sp_expr)

                # Clear the return value with a TOP
                top_si = successor_state.solver.TSI(successor_state.arch.bits)
                successor_state.registers.store(successor_state.arch.ret_offset, top_si)

            if job.call_skipped:

                # TODO: Make sure the return values make sense
                #if self.project.arch.name == 'X86':
                #    successor_state.regs.eax = successor_state.solver.BVS('ret_val', 32, min=0, max=0xffffffff, stride=1)

                new_job = VFGJob(successor_addr,
                                 successor_state,
                                 self._context_sensitivity_level,
                                 block_id=new_block_id,
                                 jumpkind='Ijk_Ret',
                                 call_stack=new_call_stack,
                                 src_block_id=job.block_id,
                                 src_exit_stmt_idx=stmt_idx,
                                 src_ins_addr=ins_addr,
                                 )

                new_jobs.append(new_job)
                assert isinstance(self._task_stack[-2], FunctionAnalysis)
                self._task_stack[-2].jobs.append(new_job)
                job.dbg_exit_status[successor] = "Pending"

            else:
                self._pending_returns[new_block_id] = PendingJob(new_block_id, successor_state, new_call_stack,
                                                                 job.block_id, stmt_idx, ins_addr)
                job.dbg_exit_status[successor] = "Pending"

        else:
            if sim_options.ABSTRACT_MEMORY in successor.options:
                if self._is_call_jumpkind(successor.history.jumpkind):
                    # If this is a call, we create a new stack address mapping
                    reg_sp_si = self._create_stack_region(successor_state, successor_addr)

                    # Save the new sp register
                    new_reg_sp_expr = successor_state.solver.ValueSet(successor_state.arch.bits,
                                                                       'global',
                                                                       0,
                                                                       reg_sp_si
                                                                       )
                    successor_state.regs.sp = new_reg_sp_expr

                elif successor.history.jumpkind == "Ijk_Ret":
                    # Remove the existing stack address mapping
                    # FIXME: Now we are assuming the sp is restored to its original value
                    reg_sp_expr = successor_state.regs.sp

                    if isinstance(reg_sp_expr._model_vsa, claripy.vsa.StridedInterval):
                        reg_sp_si = reg_sp_expr._model_vsa
                        reg_sp_val = reg_sp_si.min
                    elif isinstance(reg_sp_expr._model_vsa, claripy.vsa.ValueSet):
                        reg_sp_si = next(iter(reg_sp_expr._model_vsa.items()))[1]
                        reg_sp_val = reg_sp_si.min
                        # TODO: Finish it!

            new_job = VFGJob(successor_addr,
                             successor_state,
                             self._context_sensitivity_level,
                             block_id=new_block_id,
                             jumpkind=successor_state.history.jumpkind,
                             call_stack=new_call_stack,
                             src_block_id=job.block_id,
                             src_exit_stmt_idx=stmt_idx,
                             src_ins_addr=ins_addr,
                             )

            if successor.history.jumpkind == 'Ijk_Ret':
                # it's returning to the return site

                # save the state as a final state of the function that we are returning from
                if self._record_function_final_states:
                    # key of the function that we are returning from
                    source_function_key = FunctionKey.new(job.func_addr,
                                                          job.call_stack_suffix
                                                          )
                    self._save_function_final_state(source_function_key, job.func_addr, successor_state)

                # TODO: add an assertion that requires the returning target being the same as the return address we
                # TODO: stored before

                current_task = self._top_task
                if current_task.call_analysis is not None:
                    current_task.call_analysis.add_final_job(new_job)

                    job.dbg_exit_status[successor] = "Appended to the call analysis task"
                else:
                    job.dbg_exit_status[successor] = "Discarded (no call analysis task)"

            else:
                if self._is_call_jumpkind(successor.history.jumpkind):
                    # create a function analysis task
                    # TODO: the return address
                    task = FunctionAnalysis(new_job.addr, None)
                    self._task_stack.append(task)
                    # link it to the call analysis
                    job.call_task.register_function_analysis(task)

                else:
                    task = self._top_task

                # register the job to the function task
                task.jobs.append(new_job)
                # insert the new job into the new job array
                new_jobs.append(new_job)

                job.dbg_exit_status[successor] = "Appended"

        if not job.is_call_jump or jumpkind != "Ijk_FakeRet":
            new_target = (new_block_id, jumpkind)
        else:
            new_target = (new_block_id, "Ijk_FakeRet")  # This is the fake return!
        self._exit_targets[job.call_stack_suffix + (job.addr,)].append(new_target)

        return new_jobs