ExecutionStatus Debugger::runDebugger()

in lib/VM/Debugger/Debugger.cpp [156:324]


ExecutionStatus Debugger::runDebugger(
    Debugger::RunReason runReason,
    InterpreterState &state) {
  assert(!isDebugging_ && "can't run debugger while debugging is in progress");
  isDebugging_ = true;

  // We're going to derive a PauseReason to pass to the event observer. OptValue
  // is used to check our logic which is rather complicated.
  OptValue<PauseReason> pauseReason;

  // If the pause reason warrants it, this is set to be a valid breakpoint ID.
  BreakpointID breakpoint = fhd::kInvalidBreakpoint;

  if (runReason == RunReason::Exception) {
    // We hit an exception, report that we broke because of this.
    if (isUnwindingException_) {
      // We're currently unwinding an exception, so don't stop here
      // because we must have already reported the exception.
      isDebugging_ = false;
      return ExecutionStatus::EXCEPTION;
    }
    isUnwindingException_ = true;
    clearTempBreakpoints();
    pauseReason = PauseReason::Exception;
  } else if (runReason == RunReason::AsyncBreakImplicit) {
    if (curStepMode_.hasValue()) {
      // Avoid draining the queue or corrupting step state.
      isDebugging_ = false;
      return ExecutionStatus::RETURNED;
    }
    pauseReason = PauseReason::AsyncTrigger;
  } else if (runReason == RunReason::AsyncBreakExplicit) {
    // The user requested an async break, so we can clear stepping state
    // with the knowledge that the inspector isn't sending an immediate
    // continue.
    if (curStepMode_) {
      clearTempBreakpoints();
      curStepMode_ = llvh::None;
    }
    pauseReason = PauseReason::AsyncTrigger;
  } else {
    assert(runReason == RunReason::Opcode && "Unknown run reason");
    // First, check if we have to finish a step that's in progress.
    auto breakpointOpt = getBreakpointLocation(state.codeBlock, state.offset);
    if (breakpointOpt.hasValue() &&
        (breakpointOpt->hasStepBreakpoint() || breakpointOpt->onLoad)) {
      // We've hit a Step, which must mean we were stepping, or
      // pause-on-load if it's the first instruction of the global function.
      if (breakpointOpt->onLoad) {
        pauseReason = PauseReason::ScriptLoaded;
        clearTempBreakpoints();
      } else if (
          breakpointOpt->callStackDepths.count(0) ||
          breakpointOpt->callStackDepths.count(
              runtime_.getCurrentFrameOffset())) {
        // This is in fact a temp breakpoint we want to stop on right now.
        assert(curStepMode_ && "no step to finish");
        clearTempBreakpoints();
        auto locationOpt = getLocationForState(state);

        if (*curStepMode_ == StepMode::Into ||
            *curStepMode_ == StepMode::Over) {
          // If we're not stepping out, then we need to finish the step
          // in progress.
          // Otherwise, we just need to stop at the breakpoint site.
          while (!locationOpt.hasValue() || locationOpt->statement == 0 ||
                 sameStatementDifferentInstruction(state, preStepState_)) {
            // Move to the next source location.
            OpCode curCode = state.codeBlock->getOpCode(state.offset);

            if (curCode == OpCode::Ret) {
              // We're stepping out now.
              breakpointCaller();
              pauseOnAllCodeBlocks_ = true;
              curStepMode_ = StepMode::Out;
              isDebugging_ = false;
              return ExecutionStatus::RETURNED;
            }

            // These instructions won't recursively invoke the interpreter,
            // and we also can't easily determine where they will jump to,
            // so use single-step mode.
            if (shouldSingleStep(curCode)) {
              ExecutionStatus status = stepInstruction(state);
              if (status == ExecutionStatus::EXCEPTION) {
                isDebugging_ = false;
                return status;
              }
              locationOpt = getLocationForState(state);
              continue;
            }

            // Set a breakpoint at the next instruction and continue.
            breakAtPossibleNextInstructions(state);
            if (*curStepMode_ == StepMode::Into) {
              pauseOnAllCodeBlocks_ = true;
            }
            isDebugging_ = false;
            return ExecutionStatus::RETURNED;
          }
        }

        // Done stepping.
        curStepMode_ = llvh::None;
        pauseReason = PauseReason::StepFinish;
      } else {
        // We don't want to stop on this Step breakpoint.
        isDebugging_ = false;
        return ExecutionStatus::RETURNED;
      }
    } else {
      auto checkBreakpointCondition =
          [&](const std::string &condition) -> bool {
        if (condition.empty()) {
          // The empty condition is considered unset,
          // and we always pause on such breakpoints.
          return true;
        }
        EvalResultMetadata metadata;
        EvalArgs args;
        args.frameIdx = 0;
        // No handle here - we will only pass the value to toBoolean,
        // and no allocations should occur until then.
        HermesValue conditionResult =
            evalInFrame(args, condition, state, &metadata);
        NoAllocScope noAlloc(runtime_);
        if (metadata.isException) {
          // Ignore exceptions.
          // Cleanup is done by evalInFrame.
          return false;
        }
        noAlloc.release();
        return toBoolean(conditionResult);
      };

      // We've stopped on either a user breakpoint or a debugger statement.
      // Note: if we've stopped on both (breakpoint set on a debugger statement)
      // then we only report the breakpoint and move past it,
      // ignoring the debugger statement.
      if (breakpointOpt.hasValue()) {
        assert(
            breakpointOpt->user.hasValue() &&
            "must be stopped on a user breakpoint");
        const auto &condition =
            userBreakpoints_[*breakpointOpt->user].condition;
        if (checkBreakpointCondition(condition)) {
          pauseReason = PauseReason::Breakpoint;
          breakpoint = *(breakpointOpt->user);
        } else {
          isDebugging_ = false;
          return ExecutionStatus::RETURNED;
        }
      } else {
        pauseReason = PauseReason::DebuggerStatement;
      }

      // Stop stepping immediately.
      if (curStepMode_) {
        // If we're in a step, then the client still thinks we're debugging,
        // so just clear the status and clear the temp breakpoints.
        curStepMode_ = llvh::None;
        clearTempBreakpoints();
      }
    }
  }

  assert(pauseReason.hasValue() && "runDebugger failed to set PauseReason");
  return debuggerLoop(state, *pauseReason, breakpoint);
}