/src/hermes/lib/VM/Debugger/Debugger.cpp
Line  | Count  | Source (jump to first uncovered line)  | 
1  |  | /*  | 
2  |  |  * Copyright (c) Meta Platforms, Inc. and affiliates.  | 
3  |  |  *  | 
4  |  |  * This source code is licensed under the MIT license found in the  | 
5  |  |  * LICENSE file in the root directory of this source tree.  | 
6  |  |  */  | 
7  |  |  | 
8  |  | #ifdef HERMES_ENABLE_DEBUGGER  | 
9  |  |  | 
10  |  | #include "hermes/VM/Debugger/Debugger.h"  | 
11  |  |  | 
12  |  | #include "hermes/Inst/InstDecode.h"  | 
13  |  | #include "hermes/Support/UTF8.h"  | 
14  |  | #include "hermes/VM/Callable.h"  | 
15  |  | #include "hermes/VM/CodeBlock.h"  | 
16  |  | #include "hermes/VM/JSError.h"  | 
17  |  | #include "hermes/VM/JSLib.h"  | 
18  |  | #include "hermes/VM/Operations.h"  | 
19  |  | #include "hermes/VM/Profiler/SamplingProfiler.h"  | 
20  |  | #include "hermes/VM/Runtime.h"  | 
21  |  | #include "hermes/VM/RuntimeModule.h"  | 
22  |  | #include "hermes/VM/StackFrame-inline.h"  | 
23  |  | #include "hermes/VM/StringView.h"  | 
24  |  | #pragma GCC diagnostic push  | 
25  |  |  | 
26  |  | #ifdef HERMES_COMPILER_SUPPORTS_WSHORTEN_64_TO_32  | 
27  |  | #pragma GCC diagnostic ignored "-Wshorten-64-to-32"  | 
28  |  | #endif  | 
29  |  | #ifdef HERMES_ENABLE_DEBUGGER  | 
30  |  |  | 
31  |  | namespace hermes { | 
32  |  | namespace vm { | 
33  |  |  | 
34  |  | using namespace hermes::inst;  | 
35  |  | namespace fhd = ::facebook::hermes::debugger;  | 
36  |  |  | 
37  |  | // These instructions won't recursively invoke the interpreter,  | 
38  |  | // and we also can't easily determine where they will jump to.  | 
39  | 0  | static inline bool shouldSingleStep(OpCode opCode) { | 
40  | 0  |   return opCode == OpCode::Throw || opCode == OpCode::SwitchImm;  | 
41  | 0  | }  | 
42  |  |  | 
43  |  | static StringView getFunctionName(  | 
44  |  |     Runtime &runtime,  | 
45  | 0  |     const CodeBlock *codeBlock) { | 
46  | 0  |   auto functionName = codeBlock->getNameMayAllocate();  | 
47  | 0  |   if (functionName == Predefined::getSymbolID(Predefined::emptyString)) { | 
48  | 0  |     functionName = Predefined::getSymbolID(Predefined::anonymous);  | 
49  | 0  |   }  | 
50  | 0  |   return runtime.getIdentifierTable().getStringView(runtime, functionName);  | 
51  | 0  | }  | 
52  |  |  | 
53  |  | static std::string getFileNameAsUTF8(  | 
54  |  |     Runtime &runtime,  | 
55  |  |     RuntimeModule *runtimeModule,  | 
56  | 0  |     uint32_t filenameId) { | 
57  | 0  |   const auto *debugInfo = runtimeModule->getBytecode()->getDebugInfo();  | 
58  | 0  |   return debugInfo->getFilenameByID(filenameId);  | 
59  | 0  | }  | 
60  |  |  | 
61  |  | /// \returns the IP in \p cb, which is the given \p frame (0 being the top most  | 
62  |  | /// frame). This is either the current program IP, or the return IP following a  | 
63  |  | /// call instruction.  | 
64  |  | static unsigned  | 
65  | 0  | getIPOffsetInBlock(Runtime &runtime, const CodeBlock *cb, uint32_t frame) { | 
66  | 0  |   if (frame == 0) { | 
67  |  |     // The current IP in cb is the current program IP if this is top most frame.  | 
68  | 0  |     return cb->getOffsetOf(runtime.getCurrentIP());  | 
69  | 0  |   }  | 
70  |  |  | 
71  |  |   // Otherwise, IP for frame N is stored in frame N - 1.  | 
72  | 0  |   auto prevFrameInfo = runtime.stackFrameInfoByIndex(frame - 1);  | 
73  | 0  |   return cb->getOffsetOf(prevFrameInfo->frame->getSavedIP());  | 
74  | 0  | }  | 
75  |  |  | 
76  |  | /// Used when accessing the scope descriptor information for a particular  | 
77  |  | /// bytecode.  | 
78  |  | struct ScopeRegAndDescriptorChain { | 
79  |  |   /// Which register contains the Environment.  | 
80  |  |   unsigned reg;  | 
81  |  |  | 
82  |  |   /// All scope descriptor chain.  | 
83  |  |   llvh::SmallVector<hbc::DebugScopeDescriptor, 4> scopeDescs;  | 
84  |  | };  | 
85  |  |  | 
86  |  | /// \return a pair with its first element being the number of register that  | 
87  |  | /// holds the Environment at the runtime's current IP, and its second, the scope  | 
88  |  | /// chain containing the block and all its lexical parents, including the global  | 
89  |  | /// scope. \return none if the scope chain is unavailable.  | 
90  |  | static llvh::Optional<ScopeRegAndDescriptorChain>  | 
91  | 0  | scopeDescChainForBlock(Runtime &runtime, const CodeBlock *cb, uint32_t frame) { | 
92  | 0  |   OptValue<hbc::DebugSourceLocation> locationOpt =  | 
93  | 0  |       cb->getSourceLocation(getIPOffsetInBlock(runtime, cb, frame));  | 
94  |  | 
  | 
95  | 0  |   if (!locationOpt) { | 
96  | 0  |     return llvh::None;  | 
97  | 0  |   }  | 
98  |  |  | 
99  | 0  |   unsigned envReg = locationOpt->envReg;  | 
100  | 0  |   if (envReg == hbc::DebugSourceLocation::NO_REG) { | 
101  |  |     // The debug information doesn't know where the environment is stored.  | 
102  | 0  |     return llvh::None;  | 
103  | 0  |   }  | 
104  |  |  | 
105  | 0  |   ScopeRegAndDescriptorChain ret;  | 
106  | 0  |   ret.reg = envReg;  | 
107  | 0  |   RuntimeModule *runtimeModule = cb->getRuntimeModule();  | 
108  | 0  |   const hbc::BCProvider *bytecode = runtimeModule->getBytecode();  | 
109  | 0  |   const hbc::DebugInfo *debugInfo = bytecode->getDebugInfo();  | 
110  |  | 
  | 
111  | 0  |   OptValue<unsigned> currentScopeDescOffset = locationOpt->scopeAddress;  | 
112  | 0  |   while (currentScopeDescOffset) { | 
113  | 0  |     ret.scopeDescs.push_back(  | 
114  | 0  |         debugInfo->getScopeDescriptor(*currentScopeDescOffset));  | 
115  |  | 
  | 
116  | 0  |     currentScopeDescOffset = ret.scopeDescs.back().parentOffset;  | 
117  | 0  |   }  | 
118  |  | 
  | 
119  | 0  |   return ret;  | 
120  | 0  | }  | 
121  |  |  | 
122  |  | /// \return the first (inner-most) scope descriptor in \p frame.  | 
123  |  | static OptValue<uint32_t> getScopeDescIndexForFrame(  | 
124  |  |     const llvh::SmallVectorImpl<hbc::DebugScopeDescriptor> &scopeDescs,  | 
125  | 0  |     uint32_t frame) { | 
126  |  |   // newFrame is a flag indicating that the next scope descriptor belongs to a  | 
127  |  |   // new frame in the call stack. The default value (true) represents the fact  | 
128  |  |   // that the top most scope should always be included in the result.  | 
129  | 0  |   bool newFrame = true;  | 
130  |  |  | 
131  |  |   // counts the number of new frames see in the scope descriptor chain.  | 
132  | 0  |   uint32_t numSeenFrames = 0;  | 
133  | 0  |   for (uint32_t i = 0, end = scopeDescs.size(); i < end; ++i) { | 
134  | 0  |     const hbc::DebugScopeDescriptor &currScopeDesc = scopeDescs[i];  | 
135  | 0  |     if (newFrame) { | 
136  |  |       // currScopeDesc is the top-most scope descriptor in a new frame. The  | 
137  |  |       // search is over if numSeenFrames equals frame.  | 
138  | 0  |       if (numSeenFrames == frame) { | 
139  | 0  |         return i;  | 
140  | 0  |       }  | 
141  | 0  |       ++numSeenFrames;  | 
142  | 0  |     }  | 
143  |  |     // A new frame is found if currScopeDesc is not an inner scope (i.e., it is  | 
144  |  |     // the first scope in its function).  | 
145  | 0  |     newFrame = !currScopeDesc.flags.isInnerScope;  | 
146  | 0  |   }  | 
147  |  |  | 
148  | 0  |   return llvh::None;  | 
149  | 0  | }  | 
150  |  |  | 
151  | 0  | void Debugger::triggerAsyncPause(AsyncPauseKind kind) { | 
152  | 0  |   runtime_.triggerDebuggerAsyncBreak(kind);  | 
153  | 0  | }  | 
154  |  |  | 
155  |  | llvh::Optional<uint32_t> Debugger::findJumpTarget(  | 
156  |  |     CodeBlock *block,  | 
157  | 0  |     uint32_t offset) { | 
158  | 0  |   const Inst *ip = block->getOffsetPtr(offset);  | 
159  |  | 
  | 
160  | 0  | #define DEFINE_JUMP_LONG_VARIANT(name, nameLong) \  | 
161  | 0  |   case OpCode::name: {                           \ | 
162  | 0  |     return offset + ip->i##name.op1;             \  | 
163  | 0  |   }                                              \  | 
164  | 0  |   case OpCode::nameLong: {                       \ | 
165  | 0  |     return offset + ip->i##nameLong.op1;         \  | 
166  | 0  |   }  | 
167  |  | 
  | 
168  | 0  |   switch (ip->opCode) { | 
169  | 0  | #include "hermes/BCGen/HBC/BytecodeList.def"  | 
170  | 0  |     default:  | 
171  | 0  |       return llvh::None;  | 
172  | 0  |   }  | 
173  | 0  | #undef DEFINE_JUMP_LONG_VARIANT  | 
174  | 0  | }  | 
175  |  |  | 
176  | 0  | void Debugger::breakAtPossibleNextInstructions(const InterpreterState &state) { | 
177  | 0  |   auto nextOffset =  | 
178  | 0  |       state.offset + getInstSize(getRealOpCode(state.codeBlock, state.offset));  | 
179  |  |   // Set a breakpoint at the next instruction in the code block if this is not  | 
180  |  |   // the last instruction.  | 
181  | 0  |   if (nextOffset < state.codeBlock->getOpcodeArray().size()) { | 
182  | 0  |     setStepBreakpoint(  | 
183  | 0  |         state.codeBlock, nextOffset, runtime_.getCurrentFrameOffset());  | 
184  | 0  |   }  | 
185  |  |   // If the instruction is a jump, set a break point at the possible  | 
186  |  |   // jump target; otherwise, only break at the next instruction.  | 
187  |  |   // This instruction could jump to itself, so this step should be after the  | 
188  |  |   // previous step (otherwise the Jmp will have been overwritten by a Debugger  | 
189  |  |   // inst, and we won't be able to find the target).  | 
190  |  |   //  | 
191  |  |   // Since we've already set a breakpoint on the next instruction, we can  | 
192  |  |   // skip the case where that is also the jump target.  | 
193  | 0  |   auto jumpTarget = findJumpTarget(state.codeBlock, state.offset);  | 
194  | 0  |   if (jumpTarget.hasValue() && jumpTarget.getValue() != nextOffset) { | 
195  | 0  |     setStepBreakpoint(  | 
196  | 0  |         state.codeBlock,  | 
197  | 0  |         jumpTarget.getValue(),  | 
198  | 0  |         runtime_.getCurrentFrameOffset());  | 
199  | 0  |   }  | 
200  | 0  | }  | 
201  |  |  | 
202  | 0  | inst::OpCode Debugger::getRealOpCode(CodeBlock *block, uint32_t offset) const { | 
203  | 0  |   auto breakpointOpt = getBreakpointLocation(block, offset);  | 
204  | 0  |   if (breakpointOpt) { | 
205  | 0  |     const auto *inst =  | 
206  | 0  |         reinterpret_cast<const inst::Inst *>(&(breakpointOpt->opCode));  | 
207  | 0  |     return inst->opCode;  | 
208  | 0  |   }  | 
209  |  |  | 
210  | 0  |   auto opcodes = block->getOpcodeArray();  | 
211  | 0  |   assert(offset < opcodes.size() && "opCode offset out of bounds");  | 
212  | 0  |   const auto *inst = reinterpret_cast<const inst::Inst *>(&opcodes[offset]);  | 
213  | 0  |   return inst->opCode;  | 
214  | 0  | }  | 
215  |  |  | 
216  |  | ExecutionStatus Debugger::runDebugger(  | 
217  |  |     Debugger::RunReason runReason,  | 
218  | 0  |     InterpreterState &state) { | 
219  | 0  |   assert(!isDebugging_ && "can't run debugger while debugging is in progress");  | 
220  | 0  |   isDebugging_ = true;  | 
221  |  |  | 
222  |  |   // We're going to derive a PauseReason to pass to the event observer. OptValue  | 
223  |  |   // is used to check our logic which is rather complicated.  | 
224  | 0  |   OptValue<PauseReason> pauseReason;  | 
225  |  |  | 
226  |  |   // If the pause reason warrants it, this is set to be a valid breakpoint ID.  | 
227  | 0  |   BreakpointID breakpoint = fhd::kInvalidBreakpoint;  | 
228  |  | 
  | 
229  | 0  |   if (runReason == RunReason::Exception) { | 
230  |  |     // We hit an exception, report that we broke because of this.  | 
231  | 0  |     if (isUnwindingException_) { | 
232  |  |       // We're currently unwinding an exception, so don't stop here  | 
233  |  |       // because we must have already reported the exception.  | 
234  | 0  |       isDebugging_ = false;  | 
235  | 0  |       return ExecutionStatus::EXCEPTION;  | 
236  | 0  |     }  | 
237  | 0  |     isUnwindingException_ = true;  | 
238  | 0  |     clearTempBreakpoints();  | 
239  | 0  |     pauseReason = PauseReason::Exception;  | 
240  | 0  |   } else if (runReason == RunReason::AsyncBreakImplicit) { | 
241  | 0  |     if (curStepMode_.hasValue()) { | 
242  |  |       // Avoid draining the queue or corrupting step state.  | 
243  | 0  |       isDebugging_ = false;  | 
244  | 0  |       return ExecutionStatus::RETURNED;  | 
245  | 0  |     }  | 
246  | 0  |     pauseReason = PauseReason::AsyncTriggerImplicit;  | 
247  | 0  |   } else if (runReason == RunReason::AsyncBreakExplicit) { | 
248  |  |     // The user requested an async break, so we can clear stepping state  | 
249  |  |     // with the knowledge that the inspector isn't sending an immediate  | 
250  |  |     // continue.  | 
251  | 0  |     if (curStepMode_) { | 
252  | 0  |       clearTempBreakpoints();  | 
253  | 0  |       curStepMode_ = llvh::None;  | 
254  | 0  |     }  | 
255  | 0  |     pauseReason = PauseReason::AsyncTriggerExplicit;  | 
256  | 0  |   } else { | 
257  | 0  |     assert(runReason == RunReason::Opcode && "Unknown run reason");  | 
258  |  |  | 
259  |  |     // Whether we breakpoint on all CodeBlocks, or breakpoint caller, they'll  | 
260  |  |     // eventually hit the installed Debugger OpCode and get here. We need to  | 
261  |  |     // restore any breakpoint that we delayed restoring in  | 
262  |  |     // processInstUnderDebuggerOpCode().  | 
263  | 0  |     if (restoreBreakpointIfAny()) { | 
264  |  |       // And if we do get here and restored a breakpoint, it means that we're  | 
265  |  |       // stopping here because of the Restoration breakpoint we added from  | 
266  |  |       // pauseOnAllCodeBlocksToRestoreBreakpoint_ or breakpointCaller(). Clear  | 
267  |  |       // them out because we're not reliant on them to handle any stepping.  | 
268  | 0  |       clearRestorationBreakpoints();  | 
269  |  |  | 
270  |  |       // If after clearing Restoration breakpoints, there is no longer a  | 
271  |  |       // breakpoint at the current location, then that means there isn't any  | 
272  |  |       // user or temp breakpoint at this location. If the instruction is also  | 
273  |  |       // not an actual debugger statement, then we can just exit out of the  | 
274  |  |       // debugger loop.  | 
275  | 0  |       auto breakpointOpt = getBreakpointLocation(state.codeBlock, state.offset);  | 
276  | 0  |       OpCode curCode = getRealOpCode(state.codeBlock, state.offset);  | 
277  | 0  |       if (!breakpointOpt.hasValue() && curCode != OpCode::Debugger) { | 
278  | 0  |         isDebugging_ = false;  | 
279  | 0  |         return ExecutionStatus::RETURNED;  | 
280  | 0  |       }  | 
281  | 0  |     }  | 
282  |  |  | 
283  |  |     // First, check if we have to finish a step that's in progress.  | 
284  | 0  |     auto breakpointOpt = getBreakpointLocation(state.codeBlock, state.offset);  | 
285  | 0  |     if (breakpointOpt.hasValue() &&  | 
286  | 0  |         (breakpointOpt->hasStepBreakpoint || breakpointOpt->onLoad)) { | 
287  |  |       // We've hit a Step, which must mean we were stepping, or  | 
288  |  |       // pause-on-load if it's the first instruction of the global function.  | 
289  | 0  |       if (breakpointOpt->onLoad) { | 
290  | 0  |         pauseReason = PauseReason::ScriptLoaded;  | 
291  | 0  |         clearTempBreakpoints();  | 
292  | 0  |       } else if (  | 
293  | 0  |           breakpointOpt->callStackDepths.count(0) ||  | 
294  | 0  |           breakpointOpt->callStackDepths.count(  | 
295  | 0  |               runtime_.getCurrentFrameOffset())) { | 
296  |  |         // This is in fact a temp breakpoint we want to stop on right now.  | 
297  | 0  |         assert(curStepMode_ && "no step to finish");  | 
298  | 0  |         clearTempBreakpoints();  | 
299  | 0  |         auto locationOpt = getLocationForState(state);  | 
300  |  | 
  | 
301  | 0  |         if (*curStepMode_ == StepMode::Into ||  | 
302  | 0  |             *curStepMode_ == StepMode::Over) { | 
303  |  |           // If we're not stepping out, then we need to finish the step  | 
304  |  |           // in progress.  | 
305  |  |           // Otherwise, we just need to stop at the breakpoint site.  | 
306  | 0  |           while (!locationOpt.hasValue() || locationOpt->statement == 0 ||  | 
307  | 0  |                  sameStatementDifferentInstruction(state, preStepState_)) { | 
308  |  |             // Move to the next source location.  | 
309  | 0  |             OpCode curCode = getRealOpCode(state.codeBlock, state.offset);  | 
310  |  | 
  | 
311  | 0  |             if (curCode == OpCode::Ret) { | 
312  |  |               // We're stepping out now.  | 
313  | 0  |               breakpointCaller(/*forRestorationBreakpoint*/ false);  | 
314  | 0  |               pauseOnAllCodeBlocks_ = true;  | 
315  | 0  |               curStepMode_ = StepMode::Out;  | 
316  | 0  |               isDebugging_ = false;  | 
317  | 0  |               return ExecutionStatus::RETURNED;  | 
318  | 0  |             }  | 
319  |  |  | 
320  |  |             // These instructions won't recursively invoke the interpreter,  | 
321  |  |             // and we also can't easily determine where they will jump to,  | 
322  |  |             // so use single-step mode.  | 
323  | 0  |             if (shouldSingleStep(curCode)) { | 
324  | 0  |               ExecutionStatus status = stepInstruction(state);  | 
325  | 0  |               if (status == ExecutionStatus::EXCEPTION) { | 
326  | 0  |                 breakpointExceptionHandler(state);  | 
327  | 0  |                 isDebugging_ = false;  | 
328  | 0  |                 return status;  | 
329  | 0  |               }  | 
330  | 0  |               locationOpt = getLocationForState(state);  | 
331  | 0  |               continue;  | 
332  | 0  |             }  | 
333  |  |  | 
334  |  |             // Set a breakpoint at the next instruction and continue.  | 
335  | 0  |             breakAtPossibleNextInstructions(state);  | 
336  | 0  |             if (*curStepMode_ == StepMode::Into) { | 
337  | 0  |               pauseOnAllCodeBlocks_ = true;  | 
338  | 0  |             }  | 
339  | 0  |             isDebugging_ = false;  | 
340  | 0  |             return ExecutionStatus::RETURNED;  | 
341  | 0  |           }  | 
342  | 0  |         }  | 
343  |  |  | 
344  |  |         // Done stepping.  | 
345  | 0  |         curStepMode_ = llvh::None;  | 
346  | 0  |         pauseReason = PauseReason::StepFinish;  | 
347  | 0  |       } else { | 
348  |  |         // We don't want to stop on this Step breakpoint.  | 
349  | 0  |         isDebugging_ = false;  | 
350  | 0  |         return ExecutionStatus::RETURNED;  | 
351  | 0  |       }  | 
352  | 0  |     } else { | 
353  | 0  |       auto checkBreakpointCondition =  | 
354  | 0  |           [&](const std::string &condition) -> bool { | 
355  | 0  |         if (condition.empty()) { | 
356  |  |           // The empty condition is considered unset,  | 
357  |  |           // and we always pause on such breakpoints.  | 
358  | 0  |           return true;  | 
359  | 0  |         }  | 
360  | 0  |         EvalResultMetadata metadata;  | 
361  | 0  |         EvalArgs args;  | 
362  | 0  |         args.frameIdx = 0;  | 
363  |  |         // No handle here - we will only pass the value to toBoolean,  | 
364  |  |         // and no allocations should occur until then.  | 
365  | 0  |         HermesValue conditionResult =  | 
366  | 0  |             evalInFrame(args, condition, state, &metadata);  | 
367  | 0  |         NoAllocScope noAlloc(runtime_);  | 
368  | 0  |         if (metadata.isException) { | 
369  |  |           // Ignore exceptions.  | 
370  |  |           // Cleanup is done by evalInFrame.  | 
371  | 0  |           return false;  | 
372  | 0  |         }  | 
373  | 0  |         noAlloc.release();  | 
374  | 0  |         return toBoolean(conditionResult);  | 
375  | 0  |       };  | 
376  |  |  | 
377  |  |       // We've stopped on either a user breakpoint or a debugger statement.  | 
378  |  |       // Note: if we've stopped on both (breakpoint set on a debugger statement)  | 
379  |  |       // then we only report the breakpoint and move past it,  | 
380  |  |       // ignoring the debugger statement.  | 
381  | 0  |       if (breakpointOpt.hasValue()) { | 
382  | 0  |         assert(  | 
383  | 0  |             breakpointOpt->user.hasValue() &&  | 
384  | 0  |             "must be stopped on a user breakpoint");  | 
385  | 0  |         const auto &condition =  | 
386  | 0  |             userBreakpoints_[*breakpointOpt->user].condition;  | 
387  | 0  |         if (checkBreakpointCondition(condition)) { | 
388  | 0  |           pauseReason = PauseReason::Breakpoint;  | 
389  | 0  |           breakpoint = *(breakpointOpt->user);  | 
390  | 0  |         } else { | 
391  | 0  |           isDebugging_ = false;  | 
392  | 0  |           return ExecutionStatus::RETURNED;  | 
393  | 0  |         }  | 
394  | 0  |       } else { | 
395  | 0  |         pauseReason = PauseReason::DebuggerStatement;  | 
396  | 0  |       }  | 
397  |  |  | 
398  |  |       // Stop stepping immediately.  | 
399  | 0  |       if (curStepMode_) { | 
400  |  |         // If we're in a step, then the client still thinks we're debugging,  | 
401  |  |         // so just clear the status and clear the temp breakpoints.  | 
402  | 0  |         curStepMode_ = llvh::None;  | 
403  | 0  |         clearTempBreakpoints();  | 
404  | 0  |       }  | 
405  | 0  |     }  | 
406  | 0  |   }  | 
407  |  |  | 
408  | 0  |   assert(pauseReason.hasValue() && "runDebugger failed to set PauseReason");  | 
409  | 0  |   return debuggerLoop(state, *pauseReason, breakpoint);  | 
410  | 0  | }  | 
411  |  |  | 
412  |  | ExecutionStatus Debugger::debuggerLoop(  | 
413  |  |     InterpreterState &state,  | 
414  |  |     PauseReason pauseReason,  | 
415  | 0  |     BreakpointID breakpoint) { | 
416  | 0  |   const InterpreterState startState = state;  | 
417  | 0  |   const bool startException = pauseReason == PauseReason::Exception;  | 
418  | 0  |   EvalResultMetadata evalResultMetadata;  | 
419  | 0  |   CallResult<InterpreterState> result{ExecutionStatus::EXCEPTION}; | 
420  | 0  |   GCScope gcScope{runtime_}; | 
421  | 0  |   MutableHandle<> evalResult{runtime_}; | 
422  |  |   // Keep the evalResult alive, even if all other handles are flushed.  | 
423  | 0  |   static constexpr unsigned KEEP_HANDLES = 1;  | 
424  | 0  | #if HERMESVM_SAMPLING_PROFILER_AVAILABLE  | 
425  | 0  |   SuspendSamplingProfilerRAII ssp{runtime_, "debugger"}; | 
426  | 0  | #endif // HERMESVM_SAMPLING_PROFILER_AVAILABLE  | 
427  | 0  |   while (true) { | 
428  | 0  |     GCScopeMarkerRAII marker{runtime_}; | 
429  | 0  |     auto command = getNextCommand(  | 
430  | 0  |         state, pauseReason, *evalResult, evalResultMetadata, breakpoint);  | 
431  | 0  |     evalResult.clear();  | 
432  | 0  |     switch (command.type) { | 
433  | 0  |       case DebugCommandType::NONE:  | 
434  | 0  |         break;  | 
435  | 0  |       case DebugCommandType::CONTINUE:  | 
436  | 0  |         isDebugging_ = false;  | 
437  | 0  |         curStepMode_ = llvh::None;  | 
438  | 0  |         return ExecutionStatus::RETURNED;  | 
439  | 0  |       case DebugCommandType::EVAL:  | 
440  | 0  |         evalResult = evalInFrame(  | 
441  | 0  |             command.evalArgs, command.text, startState, &evalResultMetadata);  | 
442  | 0  |         pauseReason = PauseReason::EvalComplete;  | 
443  | 0  |         break;  | 
444  | 0  |       case DebugCommandType::STEP: { | 
445  |  |         // If we pause again in this function, it will be due to a step.  | 
446  | 0  |         pauseReason = PauseReason::StepFinish;  | 
447  | 0  |         const StepMode stepMode = command.stepArgs.mode;  | 
448  |  |         // We should only be able to step from instructions with recorded  | 
449  |  |         // locations.  | 
450  | 0  |         const auto startLocationOpt = getLocationForState(state);  | 
451  | 0  |         (void)startLocationOpt;  | 
452  | 0  |         assert(  | 
453  | 0  |             startLocationOpt.hasValue() &&  | 
454  | 0  |             "starting step from a location without debug info");  | 
455  | 0  |         preStepState_ = state;  | 
456  | 0  |         if (stepMode == StepMode::Into || stepMode == StepMode::Over) { | 
457  | 0  |           if (startException) { | 
458  |  |             // Paused because of a throw or we're about to throw.  | 
459  |  |             // Breakpoint the handler if it's there, and continue.  | 
460  | 0  |             breakpointExceptionHandler(state);  | 
461  | 0  |             isDebugging_ = false;  | 
462  | 0  |             curStepMode_ = stepMode;  | 
463  | 0  |             return ExecutionStatus::RETURNED;  | 
464  | 0  |           }  | 
465  | 0  |           while (true) { | 
466  |  |             // NOTE: this loop doesn't actually allocate any handles presently,  | 
467  |  |             // but it could, and clearing all handles is really cheap.  | 
468  | 0  |             gcScope.flushToSmallCount(KEEP_HANDLES);  | 
469  | 0  |             OpCode curCode = getRealOpCode(state.codeBlock, state.offset);  | 
470  |  | 
  | 
471  | 0  |             if (curCode == OpCode::Ret) { | 
472  | 0  |               breakpointCaller(/*forRestorationBreakpoint*/ false);  | 
473  | 0  |               pauseOnAllCodeBlocks_ = true;  | 
474  | 0  |               isDebugging_ = false;  | 
475  |  |               // Equivalent to a step out.  | 
476  | 0  |               curStepMode_ = StepMode::Out;  | 
477  | 0  |               return ExecutionStatus::RETURNED;  | 
478  | 0  |             }  | 
479  |  |  | 
480  |  |             // These instructions won't recursively invoke the interpreter,  | 
481  |  |             // and we also can't easily determine where they will jump to,  | 
482  |  |             // so use single-step mode.  | 
483  | 0  |             if (shouldSingleStep(curCode)) { | 
484  | 0  |               ExecutionStatus status = stepInstruction(state);  | 
485  | 0  |               if (status == ExecutionStatus::EXCEPTION) { | 
486  | 0  |                 breakpointExceptionHandler(state);  | 
487  | 0  |                 isDebugging_ = false;  | 
488  | 0  |                 curStepMode_ = stepMode;  | 
489  | 0  |                 return status;  | 
490  | 0  |               }  | 
491  | 0  |               auto locationOpt = getLocationForState(state);  | 
492  | 0  |               if (locationOpt.hasValue() && locationOpt->statement != 0 &&  | 
493  | 0  |                   !sameStatementDifferentInstruction(state, preStepState_)) { | 
494  |  |                 // We've moved on from the statement that was executing.  | 
495  | 0  |                 break;  | 
496  | 0  |               }  | 
497  | 0  |               continue;  | 
498  | 0  |             }  | 
499  |  |  | 
500  |  |             // Set a breakpoint at the next instruction and continue.  | 
501  |  |             // If there is a user installed breakpoint, we need to temporarily  | 
502  |  |             // uninstall the breakpoint so that we can get the correct  | 
503  |  |             // offset for the next instruction.  | 
504  | 0  |             auto breakpointOpt =  | 
505  | 0  |                 getBreakpointLocation(state.codeBlock, state.offset);  | 
506  | 0  |             if (breakpointOpt) { | 
507  | 0  |               uninstallBreakpoint(  | 
508  | 0  |                   state.codeBlock, state.offset, breakpointOpt->opCode);  | 
509  | 0  |             }  | 
510  | 0  |             breakAtPossibleNextInstructions(state);  | 
511  | 0  |             if (breakpointOpt) { | 
512  | 0  |               state.codeBlock->installBreakpointAtOffset(state.offset);  | 
513  | 0  |             }  | 
514  | 0  |             if (stepMode == StepMode::Into) { | 
515  |  |               // Stepping in could enter another code block,  | 
516  |  |               // so handle that by breakpointing all code blocks.  | 
517  | 0  |               pauseOnAllCodeBlocks_ = true;  | 
518  | 0  |             }  | 
519  | 0  |             isDebugging_ = false;  | 
520  | 0  |             curStepMode_ = stepMode;  | 
521  | 0  |             return ExecutionStatus::RETURNED;  | 
522  | 0  |           }  | 
523  | 0  |         } else { | 
524  | 0  |           ExecutionStatus status;  | 
525  | 0  |           if (startException) { | 
526  | 0  |             breakpointExceptionHandler(state);  | 
527  | 0  |             status = ExecutionStatus::EXCEPTION;  | 
528  | 0  |           } else { | 
529  | 0  |             breakpointCaller(/*forRestorationBreakpoint*/ false);  | 
530  | 0  |             status = ExecutionStatus::RETURNED;  | 
531  | 0  |           }  | 
532  |  |           // Stepping out of here is the same as continuing.  | 
533  | 0  |           isDebugging_ = false;  | 
534  | 0  |           curStepMode_ = StepMode::Out;  | 
535  | 0  |           return status;  | 
536  | 0  |         }  | 
537  | 0  |         break;  | 
538  | 0  |       }  | 
539  | 0  |     }  | 
540  | 0  |   }  | 
541  | 0  | }  | 
542  |  |  | 
543  | 356  | void Debugger::willExecuteModule(RuntimeModule *module, CodeBlock *codeBlock) { | 
544  |  |   // This function should only be called on the main RuntimeModule and not on  | 
545  |  |   // any "child" RuntimeModules it may create through lazy compilation.  | 
546  | 356  |   assert(  | 
547  | 356  |       module == module->getLazyRootModule() &&  | 
548  | 356  |       "Expected to only run on lazy root module");  | 
549  |  |  | 
550  | 356  |   if (!getShouldPauseOnScriptLoad())  | 
551  | 356  |     return;  | 
552  |  |   // We want to pause on the first instruction of this module.  | 
553  |  |   // Add a breakpoint on the first opcode of its global function.  | 
554  | 0  |   auto globalFunctionIndex = module->getBytecode()->getGlobalFunctionIndex();  | 
555  | 0  |   auto globalCode = module->getCodeBlockMayAllocate(globalFunctionIndex);  | 
556  | 0  |   setOnLoadBreakpoint(globalCode, 0);  | 
557  | 0  | }  | 
558  |  |  | 
559  | 597  | void Debugger::willUnloadModule(RuntimeModule *module) { | 
560  | 597  |   if (tempBreakpoints_.size() == 0 && restorationBreakpoints_.size() == 0 &&  | 
561  | 597  |       userBreakpoints_.size() == 0) { | 
562  | 597  |     return;  | 
563  | 597  |   }  | 
564  |  |  | 
565  | 0  |   llvh::DenseSet<CodeBlock *> unloadingBlocks;  | 
566  | 0  |   for (auto *block : module->getFunctionMap()) { | 
567  | 0  |     if (block) { | 
568  | 0  |       unloadingBlocks.insert(block);  | 
569  | 0  |     }  | 
570  | 0  |   }  | 
571  |  | 
  | 
572  | 0  |   for (auto &bp : userBreakpoints_) { | 
573  | 0  |     if (unloadingBlocks.count(bp.second.codeBlock)) { | 
574  | 0  |       unresolveBreakpointLocation(bp.second);  | 
575  | 0  |     }  | 
576  | 0  |   }  | 
577  |  | 
  | 
578  | 0  |   auto cleanNonUserBreakpoint = [&](Breakpoint &bp) { | 
579  | 0  |     if (!unloadingBlocks.count(bp.codeBlock))  | 
580  | 0  |       return false;  | 
581  |  |  | 
582  | 0  |     auto *ptr = bp.codeBlock->getOffsetPtr(bp.offset);  | 
583  | 0  |     auto it = breakpointLocations_.find(ptr);  | 
584  | 0  |     if (it != breakpointLocations_.end()) { | 
585  | 0  |       auto &location = it->second;  | 
586  | 0  |       assert(!location.user.hasValue() && "Unexpected user breakpoint");  | 
587  | 0  |       uninstallBreakpoint(bp.codeBlock, bp.offset, location.opCode);  | 
588  | 0  |       breakpointLocations_.erase(it);  | 
589  | 0  |     }  | 
590  | 0  |     return true;  | 
591  | 0  |   };  | 
592  |  | 
  | 
593  | 0  |   tempBreakpoints_.erase(  | 
594  | 0  |       std::remove_if(  | 
595  | 0  |           tempBreakpoints_.begin(),  | 
596  | 0  |           tempBreakpoints_.end(),  | 
597  | 0  |           cleanNonUserBreakpoint),  | 
598  | 0  |       tempBreakpoints_.end());  | 
599  |  | 
  | 
600  | 0  |   restorationBreakpoints_.erase(  | 
601  | 0  |       std::remove_if(  | 
602  | 0  |           restorationBreakpoints_.begin(),  | 
603  | 0  |           restorationBreakpoints_.end(),  | 
604  | 0  |           cleanNonUserBreakpoint),  | 
605  | 0  |       restorationBreakpoints_.end());  | 
606  | 0  | }  | 
607  |  |  | 
608  | 0  | void Debugger::resolveBreakpoints(CodeBlock *codeBlock) { | 
609  | 0  |   for (auto &it : userBreakpoints_) { | 
610  | 0  |     auto &breakpoint = it.second;  | 
611  | 0  |     if (!breakpoint.isResolved()) { | 
612  | 0  |       resolveBreakpointLocation(breakpoint);  | 
613  | 0  |       if (breakpoint.isResolved() && breakpoint.enabled) { | 
614  | 0  |         setUserBreakpoint(breakpoint.codeBlock, breakpoint.offset, it.first);  | 
615  | 0  |         if (breakpointResolvedCallback_) { | 
616  | 0  |           breakpointResolvedCallback_(it.first);  | 
617  | 0  |         }  | 
618  | 0  |       }  | 
619  | 0  |     }  | 
620  | 0  |   }  | 
621  | 0  | }  | 
622  |  |  | 
623  |  | auto Debugger::getCallFrameInfo(const CodeBlock *codeBlock, uint32_t ipOffset)  | 
624  | 0  |     const -> CallFrameInfo { | 
625  | 0  |   GCScopeMarkerRAII marker{runtime_}; | 
626  | 0  |   CallFrameInfo frameInfo;  | 
627  | 0  |   if (!codeBlock) { | 
628  | 0  |     frameInfo.functionName = "(native)";  | 
629  | 0  |   } else { | 
630  |  |     // The caller doesn't expect that this function is allocating new handles,  | 
631  |  |     // so make sure we aren't.  | 
632  | 0  |     GCScopeMarkerRAII gcMarker{runtime_}; | 
633  |  | 
  | 
634  | 0  |     llvh::SmallVector<char16_t, 64> storage;  | 
635  | 0  |     UTF16Ref functionName =  | 
636  | 0  |         getFunctionName(runtime_, codeBlock).getUTF16Ref(storage);  | 
637  | 0  |     convertUTF16ToUTF8WithReplacements(frameInfo.functionName, functionName);  | 
638  | 0  |     auto locationOpt = codeBlock->getSourceLocation(ipOffset);  | 
639  | 0  |     if (locationOpt) { | 
640  | 0  |       frameInfo.location.line = locationOpt->line;  | 
641  | 0  |       frameInfo.location.column = locationOpt->column;  | 
642  | 0  |       frameInfo.location.fileId = resolveScriptId(  | 
643  | 0  |           codeBlock->getRuntimeModule(), locationOpt->filenameId);  | 
644  | 0  |       frameInfo.location.fileName = getFileNameAsUTF8(  | 
645  | 0  |           runtime_, codeBlock->getRuntimeModule(), locationOpt->filenameId);  | 
646  | 0  |     }  | 
647  | 0  |   }  | 
648  | 0  |   return frameInfo;  | 
649  | 0  | }  | 
650  |  |  | 
651  | 0  | auto Debugger::getStackTrace() const -> StackTrace { | 
652  |  |   // It's ok for the frame to be a native frame (i.e. null CodeBlock and null  | 
653  |  |   // IP), but there must be a frame.  | 
654  | 0  |   assert(  | 
655  | 0  |       runtime_.getCurrentFrame() &&  | 
656  | 0  |       "Must have at least one stack frame to call this function");  | 
657  | 0  |   using fhd::CallFrameInfo;  | 
658  | 0  |   GCScopeMarkerRAII marker{runtime_}; | 
659  | 0  |   MutableHandle<> displayName{runtime_}; | 
660  | 0  |   MutableHandle<JSObject> propObj{runtime_}; | 
661  | 0  |   std::vector<CallFrameInfo> frames;  | 
662  |  |   // Note that we are iterating backwards from the top.  | 
663  |  |   // Also note that each frame saves its caller's code block and IP (the  | 
664  |  |   // SavedCodeBlock and SavedIP). We obtain the current code location by getting  | 
665  |  |   // the Callee CodeBlock of the top frame.  | 
666  | 0  |   const CodeBlock *codeBlock =  | 
667  | 0  |       runtime_.getCurrentFrame()->getCalleeCodeBlock(runtime_);  | 
668  | 0  |   const inst::Inst *ip = runtime_.getCurrentIP();  | 
669  | 0  |   GCScopeMarkerRAII marker2{runtime_}; | 
670  | 0  |   for (auto cf : runtime_.getStackFrames()) { | 
671  | 0  |     marker2.flush();  | 
672  | 0  |     uint32_t ipOffset = (codeBlock && ip) ? codeBlock->getOffsetOf(ip) : 0;  | 
673  | 0  |     CallFrameInfo frameInfo = getCallFrameInfo(codeBlock, ipOffset);  | 
674  | 0  |     if (auto callableHandle = Handle<Callable>::dyn_vmcast(  | 
675  | 0  |             Handle<>(&cf.getCalleeClosureOrCBRef()))) { | 
676  | 0  |       NamedPropertyDescriptor desc;  | 
677  | 0  |       propObj = JSObject::getNamedDescriptorPredefined(  | 
678  | 0  |           callableHandle, runtime_, Predefined::displayName, desc);  | 
679  | 0  |       if (propObj) { | 
680  | 0  |         auto displayNameRes = JSObject::getNamedSlotValue(  | 
681  | 0  |             createPseudoHandle(*propObj), runtime_, desc);  | 
682  | 0  |         if (LLVM_UNLIKELY(displayNameRes == ExecutionStatus::EXCEPTION)) { | 
683  | 0  |           displayName = HermesValue::encodeUndefinedValue();  | 
684  | 0  |         } else { | 
685  | 0  |           displayName = std::move(*displayNameRes);  | 
686  | 0  |           if (displayName->isString()) { | 
687  | 0  |             llvh::SmallVector<char16_t, 64> storage;  | 
688  | 0  |             displayName->getString()->appendUTF16String(storage);  | 
689  | 0  |             convertUTF16ToUTF8WithReplacements(frameInfo.functionName, storage);  | 
690  | 0  |           }  | 
691  | 0  |         }  | 
692  | 0  |       }  | 
693  | 0  |     }  | 
694  | 0  |     frames.push_back(frameInfo);  | 
695  |  | 
  | 
696  | 0  |     codeBlock = cf.getSavedCodeBlock();  | 
697  | 0  |     ip = cf.getSavedIP();  | 
698  | 0  |     if (!codeBlock && ip) { | 
699  |  |       // If we have a saved IP but no saved code block, this was a bound call.  | 
700  |  |       // Go up one frame and get the callee code block but use the current  | 
701  |  |       // frame's saved IP.  | 
702  | 0  |       StackFramePtr prev = cf->getPreviousFrame();  | 
703  | 0  |       assert(prev && "bound function calls must have a caller");  | 
704  | 0  |       if (CodeBlock *parentCB = prev->getCalleeCodeBlock(runtime_)) { | 
705  | 0  |         codeBlock = parentCB;  | 
706  | 0  |       }  | 
707  | 0  |     }  | 
708  | 0  |   }  | 
709  | 0  |   return StackTrace(std::move(frames));  | 
710  | 0  | }  | 
711  |  |  | 
712  | 0  | auto Debugger::createBreakpoint(const SourceLocation &loc) -> BreakpointID { | 
713  | 0  |   using fhd::kInvalidBreakpoint;  | 
714  |  | 
  | 
715  | 0  |   OptValue<hbc::DebugSearchResult> locationOpt{llvh::None}; | 
716  |  | 
  | 
717  | 0  |   Breakpoint breakpoint{}; | 
718  | 0  |   breakpoint.requestedLocation = loc;  | 
719  |  |   // Breakpoints are enabled by default.  | 
720  | 0  |   breakpoint.enabled = true;  | 
721  | 0  |   bool resolved = resolveBreakpointLocation(breakpoint);  | 
722  |  | 
  | 
723  | 0  |   BreakpointID breakpointId;  | 
724  | 0  |   if (resolved) { | 
725  | 0  |     auto breakpointLoc =  | 
726  | 0  |         getBreakpointLocation(breakpoint.codeBlock, breakpoint.offset);  | 
727  | 0  |     if (breakpointLoc.hasValue() && breakpointLoc->user) { | 
728  |  |       // Don't set duplicate user breakpoint.  | 
729  | 0  |       return kInvalidBreakpoint;  | 
730  | 0  |     }  | 
731  |  |  | 
732  | 0  |     breakpointId = nextBreakpointId_++;  | 
733  | 0  |     setUserBreakpoint(breakpoint.codeBlock, breakpoint.offset, breakpointId);  | 
734  | 0  |   } else { | 
735  | 0  |     breakpointId = nextBreakpointId_++;  | 
736  | 0  |   }  | 
737  |  |  | 
738  | 0  |   userBreakpoints_[breakpointId] = std::move(breakpoint);  | 
739  |  | 
  | 
740  | 0  |   return breakpointId;  | 
741  | 0  | }  | 
742  |  |  | 
743  | 0  | void Debugger::setBreakpointCondition(BreakpointID id, std::string condition) { | 
744  | 0  |   auto it = userBreakpoints_.find(id);  | 
745  |  | 
  | 
746  | 0  |   if (it == userBreakpoints_.end()) { | 
747  | 0  |     return;  | 
748  | 0  |   }  | 
749  |  |  | 
750  | 0  |   auto &breakpoint = it->second;  | 
751  | 0  |   breakpoint.condition = std::move(condition);  | 
752  | 0  | }  | 
753  |  |  | 
754  | 0  | void Debugger::deleteBreakpoint(BreakpointID id) { | 
755  | 0  |   auto it = userBreakpoints_.find(id);  | 
756  |  | 
  | 
757  | 0  |   if (it == userBreakpoints_.end()) { | 
758  | 0  |     return;  | 
759  | 0  |   }  | 
760  |  |  | 
761  | 0  |   auto &breakpoint = it->second;  | 
762  | 0  |   if (breakpoint.enabled && breakpoint.isResolved()) { | 
763  | 0  |     unsetUserBreakpoint(breakpoint);  | 
764  | 0  |   }  | 
765  | 0  |   userBreakpoints_.erase(it);  | 
766  | 0  | }  | 
767  |  |  | 
768  | 0  | void Debugger::deleteAllBreakpoints() { | 
769  | 0  |   for (auto &it : userBreakpoints_) { | 
770  | 0  |     auto &breakpoint = it.second;  | 
771  | 0  |     if (breakpoint.enabled && breakpoint.isResolved()) { | 
772  | 0  |       unsetUserBreakpoint(breakpoint);  | 
773  | 0  |     }  | 
774  | 0  |   }  | 
775  | 0  |   userBreakpoints_.clear();  | 
776  | 0  | }  | 
777  |  |  | 
778  | 0  | void Debugger::setBreakpointEnabled(BreakpointID id, bool enable) { | 
779  | 0  |   auto it = userBreakpoints_.find(id);  | 
780  |  | 
  | 
781  | 0  |   if (it == userBreakpoints_.end()) { | 
782  | 0  |     return;  | 
783  | 0  |   }  | 
784  |  |  | 
785  | 0  |   auto &breakpoint = it->second;  | 
786  | 0  |   if (enable && !breakpoint.enabled) { | 
787  | 0  |     breakpoint.enabled = true;  | 
788  | 0  |     if (breakpoint.isResolved()) { | 
789  | 0  |       setUserBreakpoint(breakpoint.codeBlock, breakpoint.offset, id);  | 
790  | 0  |     }  | 
791  | 0  |   } else if (!enable && breakpoint.enabled) { | 
792  | 0  |     breakpoint.enabled = false;  | 
793  | 0  |     if (breakpoint.isResolved()) { | 
794  | 0  |       unsetUserBreakpoint(breakpoint);  | 
795  | 0  |     }  | 
796  | 0  |   }  | 
797  | 0  | }  | 
798  |  |  | 
799  |  | llvh::Optional<const Debugger::BreakpointLocation>  | 
800  | 0  | Debugger::getBreakpointLocation(CodeBlock *codeBlock, uint32_t offset) const { | 
801  | 0  |   return getBreakpointLocation(codeBlock->getOffsetPtr(offset));  | 
802  | 0  | }  | 
803  |  |  | 
804  |  | auto Debugger::installBreakpoint(CodeBlock *codeBlock, uint32_t offset)  | 
805  | 0  |     -> BreakpointLocation & { | 
806  | 0  |   auto opcodes = codeBlock->getOpcodeArray();  | 
807  | 0  |   assert(offset < opcodes.size() && "invalid offset to set breakpoint");  | 
808  | 0  |   auto &location =  | 
809  | 0  |       breakpointLocations_  | 
810  | 0  |           .try_emplace(codeBlock->getOffsetPtr(offset), opcodes[offset])  | 
811  | 0  |           .first->second;  | 
812  | 0  |   if (location.count() == 0) { | 
813  |  |     // count used to be 0, so patch this in now that the count > 0.  | 
814  | 0  |     codeBlock->installBreakpointAtOffset(offset);  | 
815  | 0  |   }  | 
816  | 0  |   return location;  | 
817  | 0  | }  | 
818  |  |  | 
819  |  | void Debugger::uninstallBreakpoint(  | 
820  |  |     CodeBlock *codeBlock,  | 
821  |  |     uint32_t offset,  | 
822  | 0  |     hbc::opcode_atom_t opCode) { | 
823  |  |   // Check to see if we had temporarily kept the breakpoint uninstalled. If we  | 
824  |  |   // already did, and it's to be removed, then we don't need to restore it  | 
825  |  |   // anymore.  | 
826  | 0  |   if (breakpointToRestore_.first == codeBlock &&  | 
827  | 0  |       breakpointToRestore_.second == offset) { | 
828  | 0  |     breakpointToRestore_ = {nullptr, 0}; | 
829  | 0  |   } else { | 
830  | 0  |     codeBlock->uninstallBreakpointAtOffset(offset, opCode);  | 
831  | 0  |   }  | 
832  | 0  | }  | 
833  |  |  | 
834  |  | void Debugger::setUserBreakpoint(  | 
835  |  |     CodeBlock *codeBlock,  | 
836  |  |     uint32_t offset,  | 
837  | 0  |     BreakpointID id) { | 
838  | 0  |   BreakpointLocation &location = installBreakpoint(codeBlock, offset);  | 
839  | 0  |   location.user = id;  | 
840  | 0  | }  | 
841  |  |  | 
842  |  | void Debugger::doSetNonUserBreakpoint(  | 
843  |  |     CodeBlock *codeBlock,  | 
844  |  |     uint32_t offset,  | 
845  |  |     uint32_t callStackDepth,  | 
846  | 0  |     bool isStepBreakpoint) { | 
847  | 0  |   BreakpointLocation &location = installBreakpoint(codeBlock, offset);  | 
848  | 0  |   std::vector<Breakpoint> &breakpoints =  | 
849  | 0  |       isStepBreakpoint ? tempBreakpoints_ : restorationBreakpoints_;  | 
850  | 0  |   if (location.callStackDepths.count(callStackDepth) == 0) { | 
851  | 0  |     location.callStackDepths.insert(callStackDepth);  | 
852  | 0  |   }  | 
853  |  | 
  | 
854  | 0  |   if ((isStepBreakpoint && !location.hasStepBreakpoint) ||  | 
855  | 0  |       (!isStepBreakpoint && !location.hasRestorationBreakpoint)) { | 
856  |  |     // Leave the resolved location empty for now,  | 
857  |  |     // let the caller fill it in lazily.  | 
858  | 0  |     Breakpoint breakpoint{}; | 
859  | 0  |     breakpoint.codeBlock = codeBlock;  | 
860  | 0  |     breakpoint.offset = offset;  | 
861  | 0  |     breakpoint.enabled = true;  | 
862  | 0  |     breakpoints.push_back(breakpoint);  | 
863  | 0  |   }  | 
864  |  | 
  | 
865  | 0  |   if (isStepBreakpoint) { | 
866  | 0  |     location.hasStepBreakpoint = true;  | 
867  | 0  |   } else { | 
868  | 0  |     location.hasRestorationBreakpoint = true;  | 
869  | 0  |   }  | 
870  | 0  | }  | 
871  |  |  | 
872  |  | void Debugger::setStepBreakpoint(  | 
873  |  |     CodeBlock *codeBlock,  | 
874  |  |     uint32_t offset,  | 
875  | 0  |     uint32_t callStackDepth) { | 
876  | 0  |   doSetNonUserBreakpoint(  | 
877  | 0  |       codeBlock, offset, callStackDepth, /*isStepBreakpoint*/ true);  | 
878  | 0  | }  | 
879  |  |  | 
880  | 0  | void Debugger::setOnLoadBreakpoint(CodeBlock *codeBlock, uint32_t offset) { | 
881  | 0  |   BreakpointLocation &location = installBreakpoint(codeBlock, offset);  | 
882  |  |   // Leave the resolved location empty for now,  | 
883  |  |   // let the caller fill it in lazily.  | 
884  | 0  |   Breakpoint breakpoint{}; | 
885  | 0  |   breakpoint.codeBlock = codeBlock;  | 
886  | 0  |   breakpoint.offset = offset;  | 
887  | 0  |   breakpoint.enabled = true;  | 
888  | 0  |   assert(!location.onLoad && "can't set duplicate on-load breakpoint");  | 
889  | 0  |   location.onLoad = true;  | 
890  | 0  |   tempBreakpoints_.push_back(breakpoint);  | 
891  | 0  |   assert(location.count() && "invalid count following set breakpoint");  | 
892  | 0  | }  | 
893  |  |  | 
894  | 0  | void Debugger::unsetUserBreakpoint(const Breakpoint &breakpoint) { | 
895  | 0  |   CodeBlock *codeBlock = breakpoint.codeBlock;  | 
896  | 0  |   uint32_t offset = breakpoint.offset;  | 
897  |  | 
  | 
898  | 0  |   auto opcodes = codeBlock->getOpcodeArray();  | 
899  | 0  |   (void)opcodes;  | 
900  | 0  |   assert(offset < opcodes.size() && "invalid offset to set breakpoint");  | 
901  |  |  | 
902  | 0  |   const Inst *offsetPtr = codeBlock->getOffsetPtr(offset);  | 
903  |  | 
  | 
904  | 0  |   auto locIt = breakpointLocations_.find(offsetPtr);  | 
905  | 0  |   assert(  | 
906  | 0  |       locIt != breakpointLocations_.end() &&  | 
907  | 0  |       "can't unset a non-existent breakpoint");  | 
908  |  |  | 
909  | 0  |   auto &location = locIt->second;  | 
910  |  | 
  | 
911  | 0  |   assert(location.user && "no user breakpoints to unset");  | 
912  | 0  |   location.user = llvh::None;  | 
913  | 0  |   if (location.count() == 0) { | 
914  |  |     // No more reason to keep this location around.  | 
915  |  |     // Unpatch it from the opcode stream and delete it from the map.  | 
916  | 0  |     uninstallBreakpoint(codeBlock, offset, location.opCode);  | 
917  | 0  |     breakpointLocations_.erase(offsetPtr);  | 
918  | 0  |   }  | 
919  | 0  | }  | 
920  |  |  | 
921  | 0  | void Debugger::setEntryBreakpointForCodeBlock(CodeBlock *codeBlock) { | 
922  | 0  |   assert(!codeBlock->isLazy() && "can't set breakpoint on a lazy codeblock");  | 
923  | 0  |   assert(  | 
924  | 0  |       (pauseOnAllCodeBlocks_ || pauseOnAllCodeBlocksToRestoreBreakpoint_) &&  | 
925  | 0  |       "can't set temp breakpoint while not stepping");  | 
926  | 0  |   if (pauseOnAllCodeBlocks_) { | 
927  | 0  |     setStepBreakpoint(codeBlock, 0, 0);  | 
928  | 0  |   }  | 
929  | 0  |   if (pauseOnAllCodeBlocksToRestoreBreakpoint_) { | 
930  | 0  |     setRestorationBreakpoint(codeBlock, 0, 0);  | 
931  | 0  |   }  | 
932  | 0  | }  | 
933  |  |  | 
934  | 0  | void Debugger::breakpointCaller(bool forRestorationBreakpoint) { | 
935  | 0  |   auto callFrames = runtime_.getStackFrames();  | 
936  |  | 
  | 
937  | 0  |   assert(callFrames.begin() != callFrames.end() && "empty call stack");  | 
938  |  |  | 
939  |  |   // Go through the callStack backwards to find the first place we can break.  | 
940  | 0  |   auto frameIt = callFrames.begin();  | 
941  | 0  |   const Inst *ip = nullptr;  | 
942  | 0  |   for (; frameIt != callFrames.end(); ++frameIt) { | 
943  | 0  |     ip = frameIt->getSavedIP();  | 
944  | 0  |     if (ip) { | 
945  | 0  |       break;  | 
946  | 0  |     }  | 
947  | 0  |   }  | 
948  | 0  |   if (!ip) { | 
949  | 0  |     return;  | 
950  | 0  |   }  | 
951  |  |   // If the ip was saved in the stack frame, the caller is the function  | 
952  |  |   // that we want to return to. The code block might not be saved in this  | 
953  |  |   // frame, so we need to find that in the frame below.  | 
954  | 0  |   do { | 
955  | 0  |     frameIt++;  | 
956  | 0  |     assert(  | 
957  | 0  |         frameIt != callFrames.end() &&  | 
958  | 0  |         "The frame that has saved ip cannot be the bottom frame");  | 
959  | 0  |   } while (!frameIt->getCalleeCodeBlock(runtime_));  | 
960  |  |   // In the frame below, the 'calleeClosureORCB' register contains  | 
961  |  |   // the code block we need.  | 
962  | 0  |   CodeBlock *codeBlock = frameIt->getCalleeCodeBlock(runtime_);  | 
963  | 0  |   assert(codeBlock && "The code block must exist since we have ip");  | 
964  |  |   // Track the call stack depth that the breakpoint would be set on.  | 
965  |  |  | 
966  | 0  |   uint32_t offset = codeBlock->getOffsetOf(ip);  | 
967  | 0  |   uint32_t newOffset = offset + getInstSize(getRealOpCode(codeBlock, offset));  | 
968  |  | 
  | 
969  | 0  |   if (forRestorationBreakpoint) { | 
970  | 0  |     setRestorationBreakpoint(  | 
971  | 0  |         codeBlock, newOffset, runtime_.calcFrameOffset(frameIt));  | 
972  | 0  |   } else { | 
973  | 0  |     setStepBreakpoint(codeBlock, newOffset, runtime_.calcFrameOffset(frameIt));  | 
974  | 0  |   }  | 
975  | 0  | }  | 
976  |  |  | 
977  | 0  | void Debugger::breakpointExceptionHandler(const InterpreterState &state) { | 
978  | 0  |   auto target = findCatchTarget(state);  | 
979  | 0  |   if (!target) { | 
980  | 0  |     return;  | 
981  | 0  |   }  | 
982  | 0  |   auto *codeBlock = target->first.codeBlock;  | 
983  | 0  |   auto offset = target->first.offset;  | 
984  | 0  |   setStepBreakpoint(codeBlock, offset, target->second);  | 
985  | 0  | }  | 
986  |  |  | 
987  | 0  | void Debugger::doClearNonUserBreakpoints(bool isStepBreakpoint) { | 
988  | 0  |   llvh::SmallVector<const Inst *, 4> toErase{}; | 
989  |  | 
  | 
990  | 0  |   std::vector<Breakpoint> &breakpointsToClear =  | 
991  | 0  |       isStepBreakpoint ? tempBreakpoints_ : restorationBreakpoints_;  | 
992  |  | 
  | 
993  | 0  |   for (const auto &breakpoint : breakpointsToClear) { | 
994  | 0  |     auto *codeBlock = breakpoint.codeBlock;  | 
995  | 0  |     auto offset = breakpoint.offset;  | 
996  | 0  |     const Inst *inst = codeBlock->getOffsetPtr(offset);  | 
997  | 0  |     auto it = breakpointLocations_.find(inst);  | 
998  | 0  |     if (it == breakpointLocations_.end()) { | 
999  | 0  |       continue;  | 
1000  | 0  |     }  | 
1001  | 0  |     auto &location = it->second;  | 
1002  |  | 
  | 
1003  | 0  |     if (isStepBreakpoint) { | 
1004  | 0  |       location.hasStepBreakpoint = false;  | 
1005  | 0  |       if (location.hasRestorationBreakpoint) { | 
1006  | 0  |         continue;  | 
1007  | 0  |       }  | 
1008  | 0  |     } else { | 
1009  | 0  |       location.hasRestorationBreakpoint = false;  | 
1010  | 0  |       if (location.hasStepBreakpoint) { | 
1011  | 0  |         continue;  | 
1012  | 0  |       }  | 
1013  | 0  |     }  | 
1014  |  |  | 
1015  | 0  |     if (location.count()) { | 
1016  | 0  |       location.callStackDepths.clear();  | 
1017  | 0  |       location.onLoad = false;  | 
1018  | 0  |       if (location.count() == 0) { | 
1019  | 0  |         uninstallBreakpoint(codeBlock, offset, location.opCode);  | 
1020  | 0  |         toErase.push_back(inst);  | 
1021  | 0  |       }  | 
1022  | 0  |     }  | 
1023  | 0  |   }  | 
1024  | 0  |   for (const Inst *inst : toErase) { | 
1025  | 0  |     breakpointLocations_.erase(inst);  | 
1026  | 0  |   }  | 
1027  | 0  |   breakpointsToClear.clear();  | 
1028  | 0  | }  | 
1029  |  |  | 
1030  | 0  | void Debugger::clearTempBreakpoints() { | 
1031  | 0  |   doClearNonUserBreakpoints(/*isStepBreakpoint*/ true);  | 
1032  | 0  |   pauseOnAllCodeBlocks_ = false;  | 
1033  | 0  | }  | 
1034  |  |  | 
1035  |  | void Debugger::setRestorationBreakpoint(  | 
1036  |  |     CodeBlock *codeBlock,  | 
1037  |  |     uint32_t offset,  | 
1038  | 0  |     uint32_t callStackDepth) { | 
1039  | 0  |   doSetNonUserBreakpoint(  | 
1040  | 0  |       codeBlock, offset, callStackDepth, /*isStepBreakpoint*/ false);  | 
1041  | 0  | }  | 
1042  |  |  | 
1043  | 0  | bool Debugger::restoreBreakpointIfAny() { | 
1044  | 0  |   if (breakpointToRestore_.first != nullptr) { | 
1045  | 0  |     breakpointToRestore_.first->installBreakpointAtOffset(  | 
1046  | 0  |         breakpointToRestore_.second);  | 
1047  | 0  |     breakpointToRestore_ = {nullptr, 0}; | 
1048  | 0  |     return true;  | 
1049  | 0  |   }  | 
1050  | 0  |   return false;  | 
1051  | 0  | }  | 
1052  |  |  | 
1053  | 0  | void Debugger::clearRestorationBreakpoints() { | 
1054  | 0  |   doClearNonUserBreakpoints(/*isStepBreakpoint*/ false);  | 
1055  | 0  |   pauseOnAllCodeBlocksToRestoreBreakpoint_ = false;  | 
1056  | 0  | }  | 
1057  |  |  | 
1058  | 0  | ExecutionStatus Debugger::stepInstruction(InterpreterState &state) { | 
1059  | 0  |   auto *codeBlock = state.codeBlock;  | 
1060  | 0  |   uint32_t offset = state.offset;  | 
1061  | 0  |   assert(  | 
1062  | 0  |       getRealOpCode(codeBlock, offset) != OpCode::Ret &&  | 
1063  | 0  |       "can't stepInstruction in Ret, use step-out semantics instead");  | 
1064  | 0  |   assert(  | 
1065  | 0  |       shouldSingleStep(getRealOpCode(codeBlock, offset)) &&  | 
1066  | 0  |       "can't stepInstruction through Call, use step-in semantics instead");  | 
1067  | 0  |   auto locationOpt = getBreakpointLocation(codeBlock, offset);  | 
1068  | 0  |   ExecutionStatus status;  | 
1069  | 0  |   InterpreterState newState{state}; | 
1070  | 0  |   if (locationOpt.hasValue()) { | 
1071  |  |     // Temporarily uninstall the breakpoint so we can run the real instruction.  | 
1072  | 0  |     uninstallBreakpoint(codeBlock, offset, locationOpt->opCode);  | 
1073  | 0  |     status = runtime_.stepFunction(newState);  | 
1074  | 0  |     codeBlock->installBreakpointAtOffset(offset);  | 
1075  | 0  |   } else { | 
1076  | 0  |     status = runtime_.stepFunction(newState);  | 
1077  | 0  |   }  | 
1078  |  | 
  | 
1079  | 0  |   if (status != ExecutionStatus::EXCEPTION)  | 
1080  | 0  |     state = newState;  | 
1081  | 0  |   return status;  | 
1082  | 0  | }  | 
1083  |  |  | 
1084  |  | ExecutionStatus Debugger::processInstUnderDebuggerOpCode(  | 
1085  | 0  |     InterpreterState &state) { | 
1086  | 0  |   auto *codeBlock = state.codeBlock;  | 
1087  | 0  |   uint32_t offset = state.offset;  | 
1088  | 0  |   InterpreterState newState{state}; | 
1089  | 0  |   const inst::Inst *ip = codeBlock->getOffsetPtr(offset);  | 
1090  |  | 
  | 
1091  | 0  |   auto locationOpt = getBreakpointLocation(codeBlock, offset);  | 
1092  | 0  |   if (locationOpt.hasValue()) { | 
1093  | 0  |     uninstallBreakpoint(codeBlock, offset, locationOpt->opCode);  | 
1094  | 0  |     if (ip->opCode == OpCode::Debugger) { | 
1095  |  |       // Breakpointed a debugger instruction, so move past it  | 
1096  |  |       // since we've already called the debugger on this instruction.  | 
1097  | 0  |       newState.offset = offset + 1;  | 
1098  | 0  |       state = newState;  | 
1099  | 0  |     } else if (ip->opCode == OpCode::Ret || isCallType(ip->opCode)) { | 
1100  | 0  |       if (ip->opCode == OpCode::Ret) { | 
1101  |  |         // Breakpoint the caller to make sure we'll get a chance to restore the  | 
1102  |  |         // uninstalled breakpoint.  | 
1103  | 0  |         breakpointCaller(/*forRestorationBreakpoint*/ true);  | 
1104  | 0  |       }  | 
1105  |  |  | 
1106  |  |       // Set pause on all CodeBlocks so that we get a chance to restore the  | 
1107  |  |       // uninstalled breakpoint.  | 
1108  | 0  |       pauseOnAllCodeBlocksToRestoreBreakpoint_ = true;  | 
1109  |  |  | 
1110  |  |       // For Ret & call opcodes, we won't recursively call the Interpreter.  | 
1111  |  |       // Instead, we'll leave the breakpoint uninstalled so that the Interpreter  | 
1112  |  |       // can continue to execute the real instruction. Then at the next  | 
1113  |  |       // opportunity we'll install the breakpoint back. This variable keeps  | 
1114  |  |       // track of the breakpoint to restore.  | 
1115  | 0  |       breakpointToRestore_ = {codeBlock, offset}; | 
1116  | 0  |     } else { | 
1117  | 0  |       runtime_.setCurrentIP(ip);  | 
1118  | 0  |       ExecutionStatus status = runtime_.stepFunction(newState);  | 
1119  | 0  |       runtime_.invalidateCurrentIP();  | 
1120  | 0  |       codeBlock->installBreakpointAtOffset(offset);  | 
1121  | 0  |       if (status == ExecutionStatus::EXCEPTION) { | 
1122  | 0  |         return status;  | 
1123  | 0  |       }  | 
1124  | 0  |       state = newState;  | 
1125  | 0  |     }  | 
1126  | 0  |   } else if (ip->opCode == OpCode::Debugger) { | 
1127  |  |     // No breakpoint and we've already run the debugger, just continue on.  | 
1128  | 0  |     newState.offset = offset + 1;  | 
1129  | 0  |     state = newState;  | 
1130  | 0  |   }  | 
1131  |  |   // Else, if the current instruction is no longer a debugger instruction,  | 
1132  |  |   // we're just going to keep executing from the current IP. So no change to  | 
1133  |  |   // InterpreterState.  | 
1134  |  |  | 
1135  | 0  |   return ExecutionStatus::RETURNED;  | 
1136  | 0  | }  | 
1137  |  |  | 
1138  |  | /// Starting from scope \p i, add and \p return the number of variables in the  | 
1139  |  | /// frame. The frame ends in the first scope that's not an inner scope.  | 
1140  |  | static unsigned getFrameSize(  | 
1141  |  |     const llvh::SmallVector<hbc::DebugScopeDescriptor, 4> &scopeDescs,  | 
1142  | 0  |     uint32_t i) { | 
1143  | 0  |   unsigned frameSize = 0;  | 
1144  |  | 
  | 
1145  | 0  |   do { | 
1146  | 0  |     frameSize += scopeDescs[i].names.size();  | 
1147  | 0  |   } while (scopeDescs[i++].flags.isInnerScope);  | 
1148  |  | 
  | 
1149  | 0  |   return frameSize;  | 
1150  | 0  | }  | 
1151  |  |  | 
1152  | 0  | auto Debugger::getLexicalInfoInFrame(uint32_t frame) const -> LexicalInfo { | 
1153  | 0  |   auto frameInfo = runtime_.stackFrameInfoByIndex(frame);  | 
1154  | 0  |   assert(frameInfo && "Invalid frame");  | 
1155  |  |  | 
1156  | 0  |   LexicalInfo result;  | 
1157  | 0  |   if (frameInfo->isGlobal) { | 
1158  |  |     // Globals not yet supported.  | 
1159  |  |     // TODO: support them. For now we have an empty entry for the global scope.  | 
1160  | 0  |     result.variableCountsByScope_.push_back(0);  | 
1161  | 0  |     return result;  | 
1162  | 0  |   }  | 
1163  | 0  |   const CodeBlock *cb = frameInfo->frame->getCalleeCodeBlock(runtime_);  | 
1164  | 0  |   if (!cb) { | 
1165  |  |     // Native functions have no saved code block.  | 
1166  | 0  |     result.variableCountsByScope_.push_back(0);  | 
1167  | 0  |     return result;  | 
1168  | 0  |   }  | 
1169  |  |  | 
1170  | 0  |   llvh::Optional<ScopeRegAndDescriptorChain> envRegAndDescChain =  | 
1171  | 0  |       scopeDescChainForBlock(runtime_, cb, frame);  | 
1172  | 0  |   if (!envRegAndDescChain) { | 
1173  |  |     // Binary was compiled without variable debug info.  | 
1174  | 0  |     result.variableCountsByScope_.push_back(0);  | 
1175  | 0  |     return result;  | 
1176  | 0  |   }  | 
1177  |  |  | 
1178  | 0  |   const llvh::SmallVector<hbc::DebugScopeDescriptor, 4> &scopeDescs =  | 
1179  | 0  |       envRegAndDescChain->scopeDescs;  | 
1180  | 0  |   uint32_t currFrame = 0;  | 
1181  | 0  |   while (auto idx = getScopeDescIndexForFrame(scopeDescs, currFrame++)) { | 
1182  | 0  |     result.variableCountsByScope_.push_back(getFrameSize(scopeDescs, *idx));  | 
1183  | 0  |   }  | 
1184  | 0  |   return result;  | 
1185  | 0  | }  | 
1186  |  |  | 
1187  |  | HermesValue Debugger::getVariableInFrame(  | 
1188  |  |     uint32_t frame,  | 
1189  |  |     uint32_t scopeDepth,  | 
1190  |  |     uint32_t variableIndex,  | 
1191  | 0  |     std::string *outName) const { | 
1192  | 0  |   GCScope gcScope{runtime_}; | 
1193  | 0  |   auto frameInfo = runtime_.stackFrameInfoByIndex(frame);  | 
1194  | 0  |   assert(frameInfo && "Invalid frame");  | 
1195  |  |  | 
1196  | 0  |   const HermesValue undefined = HermesValue::encodeUndefinedValue();  | 
1197  |  |  | 
1198  |  |   // Clear the outgoing info so we don't leave stale data there.  | 
1199  | 0  |   if (outName)  | 
1200  | 0  |     outName->clear();  | 
1201  |  | 
  | 
1202  | 0  |   if (frameInfo->isGlobal) { | 
1203  |  |     // Globals not yet supported.  | 
1204  |  |     // TODO: support them.  | 
1205  | 0  |     return undefined;  | 
1206  | 0  |   }  | 
1207  | 0  |   const CodeBlock *cb = frameInfo->frame->getCalleeCodeBlock(runtime_);  | 
1208  | 0  |   assert(cb && "Unexpectedly null code block");  | 
1209  | 0  |   llvh::Optional<ScopeRegAndDescriptorChain> envRegAndDescChain =  | 
1210  | 0  |       scopeDescChainForBlock(runtime_, cb, frame);  | 
1211  | 0  |   if (!envRegAndDescChain) { | 
1212  |  |     // Binary was compiled without variable debug info.  | 
1213  | 0  |     return undefined;  | 
1214  | 0  |   }  | 
1215  |  |  | 
1216  | 0  |   const llvh::SmallVector<hbc::DebugScopeDescriptor, 4> &scopeDescs =  | 
1217  | 0  |       envRegAndDescChain->scopeDescs;  | 
1218  |  |  | 
1219  |  |   // Find the scope desc for the requested scope. This is the inner most scope  | 
1220  |  |   // in the given frame.  | 
1221  | 0  |   auto idx = getScopeDescIndexForFrame(scopeDescs, scopeDepth);  | 
1222  | 0  |   if (!idx) { | 
1223  |  |     // Invalid scope frame.  | 
1224  | 0  |     return undefined;  | 
1225  | 0  |   }  | 
1226  |  |  | 
1227  |  |   // Find the first (top most) scope in the scope chain.  | 
1228  | 0  |   const PinnedHermesValue &envPHV =  | 
1229  | 0  |       (&frameInfo->frame.getFirstLocalRef())[envRegAndDescChain->reg];  | 
1230  | 0  |   assert(envPHV.isObject() && dyn_vmcast<Environment>(envPHV));  | 
1231  |  |  | 
1232  |  |   // Descend the environment chain to the desired depth, or stop at null. We may  | 
1233  |  |   // get a null environment if it has not been created.  | 
1234  | 0  |   MutableHandle<Environment> env(runtime_, vmcast<Environment>(envPHV));  | 
1235  | 0  |   unsigned varScopeIndex = *idx;  | 
1236  | 0  |   for (uint32_t i = varScopeIndex; env && i > 0; --i) { | 
1237  | 0  |     env = env->getParentEnvironment(runtime_);  | 
1238  | 0  |   }  | 
1239  |  |  | 
1240  |  |   // Now find variableIndex in the current frame. variableIndex could be  | 
1241  |  |   // indexing into an outer scope, thus we need to find the real target scope  | 
1242  |  |   // within the current frame.  | 
1243  | 0  |   bool newFrame = false;  | 
1244  | 0  |   while (env && env->getSize() <= variableIndex) { | 
1245  |  |     // If newFrame was set to true on the previous iteration, then this  | 
1246  |  |     // iteration is now accessing variables in an Environment that doesn't  | 
1247  |  |     // belong to the requested frame.  | 
1248  | 0  |     assert(!newFrame && "accessing variables from another frame");  | 
1249  | 0  |     (void)newFrame;  | 
1250  |  |  | 
1251  |  |     // Adjust the variableIndex to take into account the current environment.  | 
1252  | 0  |     variableIndex -= env->getSize();  | 
1253  | 0  |     env = env->getParentEnvironment(runtime_);  | 
1254  |  |  | 
1255  |  |     // Sanity-check: ensuring that this loop doesn't cross the frame boundary,  | 
1256  |  |     // i.e., the current scopeDescs[varScopeIndex] os an inner scope.  | 
1257  | 0  |     newFrame = !scopeDescs[varScopeIndex++].flags.isInnerScope;  | 
1258  | 0  |   }  | 
1259  |  |  | 
1260  | 0  |   if (!env) { | 
1261  | 0  |     return undefined;  | 
1262  | 0  |   }  | 
1263  | 0  |   assert(varScopeIndex < scopeDescs.size() && "OOB scope desc access");  | 
1264  |  |  | 
1265  |  |   // If the caller needs the variable name, populate it.  | 
1266  | 0  |   if (outName)  | 
1267  | 0  |     *outName = scopeDescs[varScopeIndex].names[variableIndex];  | 
1268  |  |  | 
1269  |  |   // Now we can get the variable, or undefined if we have no environment.  | 
1270  | 0  |   return env->slot(variableIndex);  | 
1271  | 0  | }  | 
1272  |  |  | 
1273  | 0  | HermesValue Debugger::getThisValue(uint32_t frame) const { | 
1274  | 0  |   const auto frameInfo = runtime_.stackFrameInfoByIndex(frame);  | 
1275  | 0  |   assert(frameInfo && "Invalid frame");  | 
1276  |  |  | 
1277  | 0  |   if (frameInfo->isGlobal) { | 
1278  |  |     // "this" value in the global frame is the global object.  | 
1279  | 0  |     return runtime_.getGlobal().getHermesValue();  | 
1280  | 0  |   }  | 
1281  |  |  | 
1282  | 0  |   return frameInfo->frame.getThisArgRef();  | 
1283  | 0  | }  | 
1284  |  |  | 
1285  |  | HermesValue Debugger::getExceptionAsEvalResult(  | 
1286  | 0  |     EvalResultMetadata *outMetadata) { | 
1287  | 0  |   outMetadata->isException = true;  | 
1288  |  | 
  | 
1289  | 0  |   Handle<> thrownValue = runtime_.makeHandle(runtime_.getThrownValue());  | 
1290  | 0  |   assert(!thrownValue->isEmpty() && "Runtime did not throw");  | 
1291  | 0  |   runtime_.clearThrownValue();  | 
1292  |  |  | 
1293  |  |   // Set the exceptionDetails.text to toString_RJS() of the thrown value.  | 
1294  |  |   // TODO: rationalize what should happen if toString_RJS() itself throws.  | 
1295  | 0  |   auto res = toString_RJS(runtime_, thrownValue);  | 
1296  | 0  |   if (res != ExecutionStatus::EXCEPTION) { | 
1297  | 0  |     llvh::SmallVector<char16_t, 64> errorText;  | 
1298  | 0  |     res->get()->appendUTF16String(errorText);  | 
1299  | 0  |     convertUTF16ToUTF8WithReplacements(  | 
1300  | 0  |         outMetadata->exceptionDetails.text, errorText);  | 
1301  | 0  |   }  | 
1302  |  |  | 
1303  |  |   // Try to fetch the stack trace. It may not exist; for example, if the  | 
1304  |  |   // exception was a parse error in eval(), then the exception will be set  | 
1305  |  |   // directly and the stack trace will not be collected.  | 
1306  | 0  |   if (auto errorHandle = Handle<JSError>::dyn_vmcast(thrownValue)) { | 
1307  | 0  |     if (auto stackTracePtr = errorHandle->getStackTrace()) { | 
1308  |  |       // Copy the stack trace to ensure it's not moved out from under us.  | 
1309  | 0  |       const auto stackTraceCopy = *stackTracePtr;  | 
1310  | 0  |       std::vector<CallFrameInfo> frames;  | 
1311  | 0  |       frames.reserve(stackTraceCopy.size());  | 
1312  | 0  |       for (const StackTraceInfo &sti : stackTraceCopy)  | 
1313  | 0  |         frames.push_back(getCallFrameInfo(sti.codeBlock, sti.bytecodeOffset));  | 
1314  | 0  |       outMetadata->exceptionDetails.stackTrace_ = StackTrace{std::move(frames)}; | 
1315  | 0  |     }  | 
1316  | 0  |   }  | 
1317  | 0  |   return *thrownValue;  | 
1318  | 0  | }  | 
1319  |  |  | 
1320  |  | HermesValue Debugger::evalInFrame(  | 
1321  |  |     const EvalArgs &args,  | 
1322  |  |     const std::string &src,  | 
1323  |  |     const InterpreterState &state,  | 
1324  | 0  |     EvalResultMetadata *outMetadata) { | 
1325  | 0  |   GCScope gcScope{runtime_}; | 
1326  | 0  |   *outMetadata = EvalResultMetadata{}; | 
1327  | 0  |   uint32_t frame = args.frameIdx;  | 
1328  | 0  |   auto frameInfo = runtime_.stackFrameInfoByIndex(frame);  | 
1329  | 0  |   if (!frameInfo) { | 
1330  | 0  |     return HermesValue::encodeUndefinedValue();  | 
1331  | 0  |   }  | 
1332  |  |  | 
1333  | 0  |   MutableHandle<> resultHandle(runtime_);  | 
1334  | 0  |   bool singleFunction = false;  | 
1335  |  | 
  | 
1336  | 0  |   const CodeBlock *cb = frameInfo->frame->getCalleeCodeBlock(runtime_);  | 
1337  | 0  |   llvh::Optional<ScopeRegAndDescriptorChain> envRegAndScopeChain =  | 
1338  | 0  |       scopeDescChainForBlock(runtime_, cb, frame);  | 
1339  |  |  | 
1340  |  |   // Interpreting code requires that the `thrownValue_` is empty.  | 
1341  |  |   // Save it temporarily so we can restore it after the evalInEnvironment.  | 
1342  | 0  |   Handle<> savedThrownValue = runtime_.makeHandle(runtime_.getThrownValue());  | 
1343  | 0  |   runtime_.clearThrownValue();  | 
1344  |  | 
  | 
1345  | 0  |   CallResult<HermesValue> result{ExecutionStatus::EXCEPTION}; | 
1346  |  | 
  | 
1347  | 0  |   if (!envRegAndScopeChain) { | 
1348  | 0  |     result = runtime_.raiseError("Can't evalInFrame: Environment not found"); | 
1349  | 0  |   } else { | 
1350  |  |     // Use the Environment for the current instruction.  | 
1351  | 0  |     const PinnedHermesValue &env =  | 
1352  | 0  |         (&frameInfo->frame.getFirstLocalRef())[envRegAndScopeChain->reg];  | 
1353  | 0  |     assert(env.isObject() && dyn_vmcast<Environment>(env));  | 
1354  |  |  | 
1355  |  |     // Create the scope chain. The scope chain should represent each  | 
1356  |  |     // Scope/Environment's names (without any accessible name from other  | 
1357  |  |     // scopes).  | 
1358  | 0  |     ScopeChain chain;  | 
1359  | 0  |     for (const hbc::DebugScopeDescriptor &scopeDesc :  | 
1360  | 0  |          envRegAndScopeChain->scopeDescs) { | 
1361  | 0  |       chain.scopes.emplace_back();  | 
1362  | 0  |       ScopeChainItem &scopeItem = chain.scopes.back();  | 
1363  | 0  |       for (const llvh::StringRef &name : scopeDesc.names) { | 
1364  | 0  |         scopeItem.variables.push_back(name);  | 
1365  | 0  |       }  | 
1366  | 0  |     }  | 
1367  |  | 
  | 
1368  | 0  |     result = evalInEnvironment(  | 
1369  | 0  |         runtime_,  | 
1370  | 0  |         src,  | 
1371  | 0  |         Handle<Environment>::vmcast(runtime_, env),  | 
1372  | 0  |         chain,  | 
1373  | 0  |         Handle<>(&frameInfo->frame->getThisArgRef()),  | 
1374  | 0  |         false,  | 
1375  | 0  |         singleFunction);  | 
1376  | 0  |   }  | 
1377  |  |  | 
1378  |  |   // Check if an exception was thrown.  | 
1379  | 0  |   if (result.getStatus() == ExecutionStatus::EXCEPTION) { | 
1380  | 0  |     resultHandle = getExceptionAsEvalResult(outMetadata);  | 
1381  | 0  |   } else { | 
1382  | 0  |     assert(  | 
1383  | 0  |         !result->isEmpty() &&  | 
1384  | 0  |         "eval result should not be empty unless exception was thrown");  | 
1385  | 0  |     resultHandle = *result;  | 
1386  | 0  |   }  | 
1387  |  |  | 
1388  | 0  |   runtime_.setThrownValue(savedThrownValue.getHermesValue());  | 
1389  | 0  |   return *resultHandle;  | 
1390  | 0  | }  | 
1391  |  |  | 
1392  |  | llvh::Optional<std::pair<InterpreterState, uint32_t>> Debugger::findCatchTarget(  | 
1393  | 0  |     const InterpreterState &state) const { | 
1394  | 0  |   auto *codeBlock = state.codeBlock;  | 
1395  | 0  |   auto offset = state.offset;  | 
1396  | 0  |   auto frames = runtime_.getStackFrames();  | 
1397  | 0  |   for (auto it = frames.begin(), e = frames.end(); it != e; ++it) { | 
1398  | 0  |     if (codeBlock) { | 
1399  | 0  |       auto handlerOffset = codeBlock->findCatchTargetOffset(offset);  | 
1400  | 0  |       if (handlerOffset != -1) { | 
1401  | 0  |         return std::make_pair(  | 
1402  | 0  |             InterpreterState(codeBlock, handlerOffset),  | 
1403  | 0  |             runtime_.calcFrameOffset(it));  | 
1404  | 0  |       }  | 
1405  | 0  |     }  | 
1406  | 0  |     codeBlock = it->getSavedCodeBlock();  | 
1407  | 0  |     if (codeBlock) { | 
1408  | 0  |       offset = codeBlock->getOffsetOf(it->getSavedIP());  | 
1409  | 0  |     }  | 
1410  | 0  |   }  | 
1411  | 0  |   return llvh::None;  | 
1412  | 0  | }  | 
1413  |  |  | 
1414  | 0  | bool Debugger::resolveBreakpointLocation(Breakpoint &breakpoint) const { | 
1415  | 0  |   using fhd::kInvalidLocation;  | 
1416  | 0  |   assert(!breakpoint.isResolved() && "breakpoint already resolved");  | 
1417  |  |  | 
1418  | 0  |   OptValue<hbc::DebugSearchResult> locationOpt{}; | 
1419  |  | 
  | 
1420  | 0  | #ifndef HERMESVM_LEAN  | 
1421  |  |   // If we could have lazy code blocks, compile them before we try to resolve.  | 
1422  |  |   // Eagerly compile code blocks that may contain the location.  | 
1423  |  |   // This is done using a search in which we enumerate all CodeBlocks in the  | 
1424  |  |   // runtime module, and we visit any code blocks which are lazy and check  | 
1425  |  |   // their ASTs to see if the breakpoint location is in them.  | 
1426  |  |   // Note that this works because we have the start and end locations  | 
1427  |  |   // exactly when a CodeBlock is lazy, because that's only when the AST exists.  | 
1428  |  |   // If it is, we compile the CodeBlock and start over,  | 
1429  |  |   // skipping any CodeBlocks we've seen before.  | 
1430  | 0  |   GCScope gcScope{runtime_}; | 
1431  | 0  |   for (auto &runtimeModule : runtime_.getRuntimeModules()) { | 
1432  | 0  |     llvh::DenseSet<CodeBlock *> visited{}; | 
1433  | 0  |     std::vector<CodeBlock *> toVisit{}; | 
1434  | 0  |     for (uint32_t i = 0, e = runtimeModule.getNumCodeBlocks(); i < e; ++i) { | 
1435  | 0  |       GCScopeMarkerRAII marker{gcScope}; | 
1436  |  |       // Use getCodeBlock to ensure they get initialized (but not compiled).  | 
1437  | 0  |       toVisit.push_back(runtimeModule.getCodeBlockMayAllocate(i));  | 
1438  | 0  |     }  | 
1439  |  | 
  | 
1440  | 0  |     while (!toVisit.empty()) { | 
1441  | 0  |       GCScopeMarkerRAII marker{gcScope}; | 
1442  | 0  |       CodeBlock *codeBlock = toVisit.back();  | 
1443  | 0  |       toVisit.pop_back();  | 
1444  |  | 
  | 
1445  | 0  |       if (!codeBlock || !codeBlock->isLazy()) { | 
1446  |  |         // When looking for a lazy code block to expand,  | 
1447  |  |         // there's no point looking at the non-lazy ones.  | 
1448  | 0  |         continue;  | 
1449  | 0  |       }  | 
1450  |  |  | 
1451  | 0  |       if (visited.count(codeBlock) > 0) { | 
1452  |  |         // We've already been here.  | 
1453  | 0  |         continue;  | 
1454  | 0  |       }  | 
1455  |  |  | 
1456  | 0  |       visited.insert(codeBlock);  | 
1457  | 0  |       auto start = codeBlock->getLazyFunctionStartLoc();  | 
1458  | 0  |       auto end = codeBlock->getLazyFunctionEndLoc();  | 
1459  |  | 
  | 
1460  | 0  |       const auto &request = breakpoint.requestedLocation;  | 
1461  | 0  |       if ((start.line < request.line && request.line < end.line) ||  | 
1462  | 0  |           ((start.line == request.line || request.line == end.line) &&  | 
1463  | 0  |            (start.col <= request.column && request.column <= end.col))) { | 
1464  |  |         // The code block probably contains the breakpoint we want to set.  | 
1465  |  |         // First, we compile it.  | 
1466  | 0  |         if (LLVM_UNLIKELY(  | 
1467  | 0  |                 codeBlock->lazyCompile(runtime_) ==  | 
1468  | 0  |                 ExecutionStatus::EXCEPTION)) { | 
1469  |  |           // TODO: how to better handle this?  | 
1470  | 0  |           runtime_.clearThrownValue();  | 
1471  | 0  |         }  | 
1472  |  |  | 
1473  |  |         // We've found the codeBlock at this level and expanded it,  | 
1474  |  |         // so there's no point continuing the search.  | 
1475  |  |         // Abandon the current toVisit queue and repopulate it.  | 
1476  | 0  |         toVisit.clear();  | 
1477  |  |  | 
1478  |  |         // Compiling the function will add more functions to the runtimeModule.  | 
1479  |  |         // Re-add them all so we can continue the search.  | 
1480  | 0  |         for (uint32_t i = 0, e = runtimeModule.getNumCodeBlocks(); i < e; ++i) { | 
1481  | 0  |           GCScopeMarkerRAII marker2{gcScope}; | 
1482  |  |           // Use getCodeBlock to ensure they get initialized (but not compiled).  | 
1483  | 0  |           toVisit.push_back(runtimeModule.getCodeBlockMayAllocate(i));  | 
1484  | 0  |         }  | 
1485  | 0  |       }  | 
1486  | 0  |     }  | 
1487  | 0  |   }  | 
1488  | 0  | #endif  | 
1489  |  |  | 
1490  |  |   // Iterate backwards through runtime modules, under the assumption that  | 
1491  |  |   // modules at the end of the list were added more recently, and are more  | 
1492  |  |   // likely to match the user's intention.  | 
1493  |  |   // Specifically, this will check any user source before runtime modules loaded  | 
1494  |  |   // by the VM.  | 
1495  | 0  |   for (auto it = runtime_.getRuntimeModules().rbegin();  | 
1496  | 0  |        it != runtime_.getRuntimeModules().rend();  | 
1497  | 0  |        ++it) { | 
1498  | 0  |     auto &runtimeModule = *it;  | 
1499  | 0  |     GCScope gcScope{runtime_}; | 
1500  |  | 
  | 
1501  | 0  |     if (!runtimeModule.isInitialized()) { | 
1502  |  |       // Uninitialized module.  | 
1503  | 0  |       continue;  | 
1504  | 0  |     }  | 
1505  | 0  |     if (!runtimeModule.getBytecode()->getDebugInfo()) { | 
1506  |  |       // No debug info in this module, keep going.  | 
1507  | 0  |       continue;  | 
1508  | 0  |     }  | 
1509  |  |  | 
1510  | 0  |     const auto *debugInfo = runtimeModule.getBytecode()->getDebugInfo();  | 
1511  | 0  |     const auto &fileRegions = debugInfo->viewFiles();  | 
1512  | 0  |     if (fileRegions.empty()) { | 
1513  | 0  |       continue;  | 
1514  | 0  |     }  | 
1515  |  |  | 
1516  | 0  |     uint32_t resolvedFileId = kInvalidLocation;  | 
1517  | 0  |     std::string resolvedFileName{}; | 
1518  |  | 
  | 
1519  | 0  |     if (!breakpoint.requestedLocation.fileName.empty()) { | 
1520  | 0  |       for (const auto ®ion : fileRegions) { | 
1521  | 0  |         std::string storage =  | 
1522  | 0  |             getFileNameAsUTF8(runtime_, &runtimeModule, region.filenameId);  | 
1523  | 0  |         llvh::StringRef storageRef{storage}; | 
1524  | 0  |         if (storageRef.consume_back(breakpoint.requestedLocation.fileName)) { | 
1525  | 0  |           resolvedFileId = region.filenameId;  | 
1526  | 0  |           resolvedFileName = std::move(storage);  | 
1527  | 0  |           break;  | 
1528  | 0  |         }  | 
1529  | 0  |       }  | 
1530  | 0  |     } else if (breakpoint.requestedLocation.fileId != kInvalidLocation) { | 
1531  | 0  |       for (const auto ®ion : fileRegions) { | 
1532  |  |         // We don't yet have a convincing story for debugging CommonJS, so for  | 
1533  |  |         // now just assert that we're still living in the one-file-per-RM world.  | 
1534  |  |         // TODO(T84976604): Properly handle setting breakpoints when there are  | 
1535  |  |         // multiple JS files per HBC file.  | 
1536  | 0  |         assert(  | 
1537  | 0  |             region.filenameId == 0 && "Unexpected multiple filenames per RM");  | 
1538  | 0  |         if (resolveScriptId(&runtimeModule, region.filenameId) ==  | 
1539  | 0  |             breakpoint.requestedLocation.fileId) { | 
1540  | 0  |           resolvedFileId = region.filenameId;  | 
1541  | 0  |           resolvedFileName =  | 
1542  | 0  |               getFileNameAsUTF8(runtime_, &runtimeModule, resolvedFileId);  | 
1543  | 0  |           break;  | 
1544  | 0  |         }  | 
1545  | 0  |       }  | 
1546  | 0  |     } else { | 
1547  |  |       // No requested file, just pick the first one.  | 
1548  | 0  |       resolvedFileId = fileRegions.front().filenameId;  | 
1549  | 0  |       resolvedFileName =  | 
1550  | 0  |           getFileNameAsUTF8(runtime_, &runtimeModule, resolvedFileId);  | 
1551  | 0  |     }  | 
1552  |  |  | 
1553  | 0  |     if (resolvedFileId == kInvalidLocation) { | 
1554  |  |       // Unable to find the file here.  | 
1555  | 0  |       continue;  | 
1556  | 0  |     }  | 
1557  |  |  | 
1558  | 0  |     locationOpt = debugInfo->getAddressForLocation(  | 
1559  | 0  |         resolvedFileId,  | 
1560  | 0  |         breakpoint.requestedLocation.line,  | 
1561  | 0  |         breakpoint.requestedLocation.column == kInvalidLocation  | 
1562  | 0  |             ? llvh::None  | 
1563  | 0  |             : OptValue<uint32_t>{breakpoint.requestedLocation.column}); | 
1564  |  | 
  | 
1565  | 0  |     if (locationOpt.hasValue()) { | 
1566  | 0  |       breakpoint.codeBlock =  | 
1567  | 0  |           runtimeModule.getCodeBlockMayAllocate(locationOpt->functionIndex);  | 
1568  | 0  |       breakpoint.offset = locationOpt->bytecodeOffset;  | 
1569  |  | 
  | 
1570  | 0  |       SourceLocation resolvedLocation;  | 
1571  | 0  |       resolvedLocation.line = locationOpt->line;  | 
1572  | 0  |       resolvedLocation.column = locationOpt->column;  | 
1573  | 0  |       resolvedLocation.fileId = resolveScriptId(&runtimeModule, resolvedFileId);  | 
1574  | 0  |       resolvedLocation.fileName = std::move(resolvedFileName);  | 
1575  | 0  |       breakpoint.resolvedLocation = resolvedLocation;  | 
1576  | 0  |       return true;  | 
1577  | 0  |     }  | 
1578  | 0  |   }  | 
1579  |  |  | 
1580  | 0  |   return false;  | 
1581  | 0  | }  | 
1582  |  |  | 
1583  | 0  | void Debugger::unresolveBreakpointLocation(Breakpoint &breakpoint) { | 
1584  | 0  |   assert(breakpoint.isResolved() && "Breakpoint already unresolved");  | 
1585  | 0  |   if (breakpoint.enabled) { | 
1586  | 0  |     unsetUserBreakpoint(breakpoint);  | 
1587  | 0  |   }  | 
1588  | 0  |   breakpoint.resolvedLocation.reset();  | 
1589  | 0  |   breakpoint.codeBlock = nullptr;  | 
1590  | 0  |   breakpoint.offset = -1;  | 
1591  | 0  | }  | 
1592  |  |  | 
1593  | 0  | auto Debugger::getSourceMappingUrl(ScriptID scriptId) const -> String { | 
1594  | 0  |   for (auto &runtimeModule : runtime_.getRuntimeModules()) { | 
1595  | 0  |     if (!runtimeModule.isInitialized()) { | 
1596  |  |       // Uninitialized module.  | 
1597  | 0  |       continue;  | 
1598  | 0  |     }  | 
1599  |  |  | 
1600  | 0  |     auto *debugInfo = runtimeModule.getBytecode()->getDebugInfo();  | 
1601  | 0  |     if (!debugInfo) { | 
1602  |  |       // No debug info in this module, keep going.  | 
1603  | 0  |       continue;  | 
1604  | 0  |     }  | 
1605  |  |  | 
1606  | 0  |     for (const auto &file : debugInfo->viewFiles()) { | 
1607  | 0  |       if (resolveScriptId(&runtimeModule, file.filenameId) == scriptId) { | 
1608  | 0  |         if (file.sourceMappingUrlId == fhd::kInvalidBreakpoint) { | 
1609  | 0  |           return "";  | 
1610  | 0  |         }  | 
1611  | 0  |         return getFileNameAsUTF8(  | 
1612  | 0  |             runtime_, &runtimeModule, file.sourceMappingUrlId);  | 
1613  | 0  |       }  | 
1614  | 0  |     }  | 
1615  | 0  |   }  | 
1616  |  |  | 
1617  | 0  |   return "";  | 
1618  | 0  | }  | 
1619  |  |  | 
1620  | 0  | auto Debugger::getLoadedScripts() const -> std::vector<SourceLocation> { | 
1621  | 0  |   std::vector<SourceLocation> loadedScripts;  | 
1622  | 0  |   for (auto &runtimeModule : runtime_.getRuntimeModules()) { | 
1623  | 0  |     if (!runtimeModule.isInitialized()) { | 
1624  |  |       // Uninitialized module.  | 
1625  | 0  |       continue;  | 
1626  | 0  |     }  | 
1627  |  |     // Only include a RuntimeModule if it's the root module  | 
1628  | 0  |     if (runtimeModule.getLazyRootModule() != &runtimeModule) { | 
1629  | 0  |       continue;  | 
1630  | 0  |     }  | 
1631  |  |  | 
1632  | 0  |     auto *debugInfo = runtimeModule.getBytecode()->getDebugInfo();  | 
1633  | 0  |     if (!debugInfo) { | 
1634  |  |       // No debug info in this module, keep going.  | 
1635  | 0  |       continue;  | 
1636  | 0  |     }  | 
1637  |  |  | 
1638  |  |     // Same as the temp breakpoint we set in Debugger::willExecuteModule() for  | 
1639  |  |     // pausing on script load.  | 
1640  | 0  |     auto globalFunctionIndex =  | 
1641  | 0  |         runtimeModule.getBytecode()->getGlobalFunctionIndex();  | 
1642  | 0  |     auto globalCodeBlock =  | 
1643  | 0  |         runtimeModule.getCodeBlockMayAllocate(globalFunctionIndex);  | 
1644  | 0  |     OptValue<hbc::DebugSourceLocation> debugSrcLoc =  | 
1645  | 0  |         globalCodeBlock->getSourceLocation();  | 
1646  | 0  |     if (!debugSrcLoc) { | 
1647  | 0  |       continue;  | 
1648  | 0  |     }  | 
1649  |  |  | 
1650  | 0  |     SourceLocation loc;  | 
1651  | 0  |     loc.fileId = resolveScriptId(&runtimeModule, debugSrcLoc->filenameId);  | 
1652  | 0  |     loc.line = debugSrcLoc->line;  | 
1653  | 0  |     loc.column = debugSrcLoc->column;  | 
1654  | 0  |     loc.fileName = debugInfo->getFilenameByID(debugSrcLoc->filenameId);  | 
1655  |  | 
  | 
1656  | 0  |     loadedScripts.push_back(loc);  | 
1657  | 0  |   }  | 
1658  | 0  |   return loadedScripts;  | 
1659  | 0  | }  | 
1660  |  |  | 
1661  |  | auto Debugger::resolveScriptId(  | 
1662  |  |     RuntimeModule *runtimeModule,  | 
1663  | 0  |     uint32_t filenameId) const -> ScriptID { | 
1664  | 0  |   return runtimeModule->getScriptID();  | 
1665  | 0  | }  | 
1666  |  |  | 
1667  |  | } // namespace vm  | 
1668  |  | } // namespace hermes  | 
1669  |  |  | 
1670  |  | #endif  | 
1671  |  |  | 
1672  |  | #endif // HERMES_ENABLE_DEBUGGER  |