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);
}