1/*
2 * Copyright (C) 2008-2019 Apple Inc. All rights reserved.
3 * Copyright (C) 1999-2001 Harri Porten ([email protected])
4 * Copyright (C) 2001 Peter Kelly ([email protected])
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 *
20 */
21
22#include "config.h"
23#include "Debugger.h"
24
25#include "CodeBlock.h"
26#include "DebuggerCallFrame.h"
27#include "Error.h"
28#include "HeapIterationScope.h"
29#include "Interpreter.h"
30#include "JSCInlines.h"
31#include "JSCJSValueInlines.h"
32#include "JSFunction.h"
33#include "JSGlobalObject.h"
34#include "MarkedSpaceInlines.h"
35#include "Parser.h"
36#include "Protect.h"
37#include "VMEntryScope.h"
38
39namespace {
40
41using namespace JSC;
42
43struct GatherSourceProviders : public MarkedBlock::VoidFunctor {
44 // FIXME: This is a mutable field because this isn't a C++ lambda.
45 // https://bugs.webkit.org/show_bug.cgi?id=159644
46 mutable HashSet<SourceProvider*> sourceProviders;
47 JSGlobalObject* m_globalObject;
48
49 GatherSourceProviders(JSGlobalObject* globalObject)
50 : m_globalObject(globalObject) { }
51
52 IterationStatus operator()(HeapCell* heapCell, HeapCell::Kind kind) const
53 {
54 if (!isJSCellKind(kind))
55 return IterationStatus::Continue;
56
57 JSCell* cell = static_cast<JSCell*>(heapCell);
58
59 JSFunction* function = jsDynamicCast<JSFunction*>(cell->vm(), cell);
60 if (!function)
61 return IterationStatus::Continue;
62
63 if (function->scope()->globalObject() != m_globalObject)
64 return IterationStatus::Continue;
65
66 if (!function->executable()->isFunctionExecutable())
67 return IterationStatus::Continue;
68
69 if (function->isHostOrBuiltinFunction())
70 return IterationStatus::Continue;
71
72 sourceProviders.add(
73 jsCast<FunctionExecutable*>(function->executable())->source().provider());
74 return IterationStatus::Continue;
75 }
76};
77
78} // namespace
79
80namespace JSC {
81
82class DebuggerPausedScope {
83public:
84 DebuggerPausedScope(Debugger& debugger)
85 : m_debugger(debugger)
86 {
87 ASSERT(!m_debugger.m_currentDebuggerCallFrame);
88 }
89
90 ~DebuggerPausedScope()
91 {
92 if (m_debugger.m_currentDebuggerCallFrame) {
93 m_debugger.m_currentDebuggerCallFrame->invalidate();
94 m_debugger.m_currentDebuggerCallFrame = nullptr;
95 }
96 }
97
98private:
99 Debugger& m_debugger;
100};
101
102// This is very similar to SetForScope<bool>, but that cannot be used
103// as the m_isPaused field uses only one bit.
104class TemporaryPausedState {
105public:
106 TemporaryPausedState(Debugger& debugger)
107 : m_debugger(debugger)
108 {
109 ASSERT(!m_debugger.m_isPaused);
110 m_debugger.m_isPaused = true;
111 }
112
113 ~TemporaryPausedState()
114 {
115 m_debugger.m_isPaused = false;
116 }
117
118private:
119 Debugger& m_debugger;
120};
121
122
123Debugger::ProfilingClient::~ProfilingClient()
124{
125}
126
127Debugger::Debugger(VM& vm)
128 : m_vm(vm)
129 , m_pauseOnExceptionsState(DontPauseOnExceptions)
130 , m_pauseAtNextOpportunity(false)
131 , m_pastFirstExpressionInStatement(false)
132 , m_isPaused(false)
133 , m_breakpointsActivated(false)
134 , m_hasHandlerForExceptionCallback(false)
135 , m_suppressAllPauses(false)
136 , m_steppingMode(SteppingModeDisabled)
137 , m_reasonForPause(NotPaused)
138 , m_lastExecutedLine(UINT_MAX)
139 , m_lastExecutedSourceID(noSourceID)
140 , m_topBreakpointID(noBreakpointID)
141 , m_pausingBreakpointID(noBreakpointID)
142{
143}
144
145Debugger::~Debugger()
146{
147 HashSet<JSGlobalObject*>::iterator end = m_globalObjects.end();
148 for (HashSet<JSGlobalObject*>::iterator it = m_globalObjects.begin(); it != end; ++it)
149 (*it)->setDebugger(nullptr);
150}
151
152void Debugger::attach(JSGlobalObject* globalObject)
153{
154 ASSERT(!globalObject->debugger());
155 globalObject->setDebugger(this);
156 m_globalObjects.add(globalObject);
157
158 m_vm.setShouldBuildPCToCodeOriginMapping();
159
160 // Call sourceParsed because it will execute JavaScript in the inspector.
161 GatherSourceProviders gatherSourceProviders(globalObject);
162 {
163 HeapIterationScope iterationScope(m_vm.heap);
164 m_vm.heap.objectSpace().forEachLiveCell(iterationScope, gatherSourceProviders);
165 }
166 for (auto* sourceProvider : gatherSourceProviders.sourceProviders)
167 sourceParsed(globalObject, sourceProvider, -1, String());
168}
169
170void Debugger::detach(JSGlobalObject* globalObject, ReasonForDetach reason)
171{
172 // If we're detaching from the currently executing global object, manually tear down our
173 // stack, since we won't get further debugger callbacks to do so. Also, resume execution,
174 // since there's no point in staying paused once a window closes.
175 // We know there is an entry scope, otherwise, m_currentCallFrame would be null.
176 VM& vm = globalObject->vm();
177 if (m_isPaused && m_currentCallFrame && vm.entryScope->globalObject() == globalObject) {
178 m_currentCallFrame = nullptr;
179 m_pauseOnCallFrame = nullptr;
180 continueProgram();
181 }
182
183 ASSERT(m_globalObjects.contains(globalObject));
184 m_globalObjects.remove(globalObject);
185
186 // If the globalObject is destructing, then its CodeBlocks will also be
187 // destructed. There is no need to do the debugger requests clean up, and
188 // it is not safe to access those CodeBlocks at this time anyway.
189 if (reason != GlobalObjectIsDestructing)
190 clearDebuggerRequests(globalObject);
191
192 globalObject->setDebugger(nullptr);
193
194 if (m_globalObjects.isEmpty())
195 clearParsedData();
196}
197
198bool Debugger::isAttached(JSGlobalObject* globalObject)
199{
200 return globalObject->debugger() == this;
201}
202
203class Debugger::SetSteppingModeFunctor {
204public:
205 SetSteppingModeFunctor(Debugger* debugger, SteppingMode mode)
206 : m_debugger(debugger)
207 , m_mode(mode)
208 {
209 }
210
211 void operator()(CodeBlock* codeBlock) const
212 {
213 if (m_debugger == codeBlock->globalObject()->debugger()) {
214 if (m_mode == SteppingModeEnabled)
215 codeBlock->setSteppingMode(CodeBlock::SteppingModeEnabled);
216 else
217 codeBlock->setSteppingMode(CodeBlock::SteppingModeDisabled);
218 }
219 }
220
221private:
222 Debugger* m_debugger;
223 SteppingMode m_mode;
224};
225
226void Debugger::setSteppingMode(SteppingMode mode)
227{
228 if (mode == m_steppingMode)
229 return;
230
231 m_vm.heap.completeAllJITPlans();
232
233 m_steppingMode = mode;
234 SetSteppingModeFunctor functor(this, mode);
235 m_vm.heap.forEachCodeBlock(functor);
236}
237
238void Debugger::registerCodeBlock(CodeBlock* codeBlock)
239{
240 applyBreakpoints(codeBlock);
241 if (isStepping())
242 codeBlock->setSteppingMode(CodeBlock::SteppingModeEnabled);
243}
244
245void Debugger::setProfilingClient(ProfilingClient* client)
246{
247 ASSERT(!!m_profilingClient != !!client);
248 m_profilingClient = client;
249}
250
251Seconds Debugger::willEvaluateScript()
252{
253 return m_profilingClient->willEvaluateScript();
254}
255
256void Debugger::didEvaluateScript(Seconds startTime, ProfilingReason reason)
257{
258 m_profilingClient->didEvaluateScript(startTime, reason);
259}
260
261void Debugger::toggleBreakpoint(CodeBlock* codeBlock, Breakpoint& breakpoint, BreakpointState enabledOrNot)
262{
263 ASSERT(breakpoint.resolved);
264
265 ScriptExecutable* executable = codeBlock->ownerExecutable();
266
267 SourceID sourceID = static_cast<SourceID>(executable->sourceID());
268 if (breakpoint.sourceID != sourceID)
269 return;
270
271 unsigned startLine = executable->firstLine();
272 unsigned startColumn = executable->startColumn();
273 unsigned endLine = executable->lastLine();
274 unsigned endColumn = executable->endColumn();
275
276 // Inspector breakpoint line and column values are zero-based but the executable
277 // and CodeBlock line and column values are one-based.
278 unsigned line = breakpoint.line + 1;
279 Optional<unsigned> column;
280 if (breakpoint.column)
281 column = breakpoint.column + 1;
282
283 if (line < startLine || line > endLine)
284 return;
285 if (column) {
286 if (line == startLine && column < startColumn)
287 return;
288 if (line == endLine && column > endColumn)
289 return;
290 }
291
292 if (!codeBlock->hasOpDebugForLineAndColumn(line, column))
293 return;
294
295 if (enabledOrNot == BreakpointEnabled)
296 codeBlock->addBreakpoint(1);
297 else
298 codeBlock->removeBreakpoint(1);
299}
300
301void Debugger::applyBreakpoints(CodeBlock* codeBlock)
302{
303 BreakpointIDToBreakpointMap& breakpoints = m_breakpointIDToBreakpoint;
304 for (auto* breakpoint : breakpoints.values())
305 toggleBreakpoint(codeBlock, *breakpoint, BreakpointEnabled);
306}
307
308class Debugger::ToggleBreakpointFunctor {
309public:
310 ToggleBreakpointFunctor(Debugger* debugger, Breakpoint& breakpoint, BreakpointState enabledOrNot)
311 : m_debugger(debugger)
312 , m_breakpoint(breakpoint)
313 , m_enabledOrNot(enabledOrNot)
314 {
315 }
316
317 void operator()(CodeBlock* codeBlock) const
318 {
319 if (m_debugger == codeBlock->globalObject()->debugger())
320 m_debugger->toggleBreakpoint(codeBlock, m_breakpoint, m_enabledOrNot);
321 }
322
323private:
324 Debugger* m_debugger;
325 Breakpoint& m_breakpoint;
326 BreakpointState m_enabledOrNot;
327};
328
329void Debugger::toggleBreakpoint(Breakpoint& breakpoint, Debugger::BreakpointState enabledOrNot)
330{
331 m_vm.heap.completeAllJITPlans();
332
333 ToggleBreakpointFunctor functor(this, breakpoint, enabledOrNot);
334 m_vm.heap.forEachCodeBlock(functor);
335}
336
337void Debugger::recompileAllJSFunctions()
338{
339 m_vm.deleteAllCode(PreventCollectionAndDeleteAllCode);
340}
341
342DebuggerParseData& Debugger::debuggerParseData(SourceID sourceID, SourceProvider* provider)
343{
344 auto iter = m_parseDataMap.find(sourceID);
345 if (iter != m_parseDataMap.end())
346 return iter->value;
347
348 DebuggerParseData parseData;
349 gatherDebuggerParseDataForSource(m_vm, provider, parseData);
350 auto result = m_parseDataMap.add(sourceID, parseData);
351 return result.iterator->value;
352}
353
354void Debugger::resolveBreakpoint(Breakpoint& breakpoint, SourceProvider* sourceProvider)
355{
356 RELEASE_ASSERT(!breakpoint.resolved);
357 ASSERT(breakpoint.sourceID != noSourceID);
358
359 // FIXME: <https://webkit.org/b/162771> Web Inspector: Adopt TextPosition in Inspector to avoid oneBasedInt/zeroBasedInt ambiguity
360 // Inspector breakpoint line and column values are zero-based but the executable
361 // and CodeBlock line values are one-based while column is zero-based.
362 int line = breakpoint.line + 1;
363 int column = breakpoint.column;
364
365 // Account for a <script>'s start position on the first line only.
366 int providerStartLine = sourceProvider->startPosition().m_line.oneBasedInt(); // One based to match the already adjusted line.
367 int providerStartColumn = sourceProvider->startPosition().m_column.zeroBasedInt(); // Zero based so column zero is zero.
368 if (line == providerStartLine && breakpoint.column) {
369 ASSERT(providerStartColumn <= column);
370 if (providerStartColumn)
371 column -= providerStartColumn;
372 }
373
374 DebuggerParseData& parseData = debuggerParseData(breakpoint.sourceID, sourceProvider);
375 Optional<JSTextPosition> resolvedPosition = parseData.pausePositions.breakpointLocationForLineColumn(line, column);
376 if (!resolvedPosition)
377 return;
378
379 int resolvedLine = resolvedPosition->line;
380 int resolvedColumn = resolvedPosition->column();
381
382 // Re-account for a <script>'s start position on the first line only.
383 if (resolvedLine == providerStartLine && breakpoint.column) {
384 if (providerStartColumn)
385 resolvedColumn += providerStartColumn;
386 }
387
388 breakpoint.line = resolvedLine - 1;
389 breakpoint.column = resolvedColumn;
390 breakpoint.resolved = true;
391}
392
393BreakpointID Debugger::setBreakpoint(Breakpoint& breakpoint, bool& existing)
394{
395 ASSERT(breakpoint.resolved);
396 ASSERT(breakpoint.sourceID != noSourceID);
397
398 SourceID sourceID = breakpoint.sourceID;
399 unsigned line = breakpoint.line;
400 unsigned column = breakpoint.column;
401
402 SourceIDToBreakpointsMap::iterator it = m_sourceIDToBreakpoints.find(breakpoint.sourceID);
403 if (it == m_sourceIDToBreakpoints.end())
404 it = m_sourceIDToBreakpoints.set(sourceID, LineToBreakpointsMap()).iterator;
405
406 LineToBreakpointsMap::iterator breaksIt = it->value.find(line);
407 if (breaksIt == it->value.end())
408 breaksIt = it->value.set(line, adoptRef(new BreakpointsList)).iterator;
409
410 BreakpointsList& breakpoints = *breaksIt->value;
411 for (Breakpoint* current = breakpoints.head(); current; current = current->next()) {
412 if (current->column == column) {
413 // Found existing breakpoint. Do not create a duplicate at this location.
414 existing = true;
415 return current->id;
416 }
417 }
418
419 existing = false;
420 BreakpointID id = ++m_topBreakpointID;
421 RELEASE_ASSERT(id != noBreakpointID);
422
423 breakpoint.id = id;
424
425 Breakpoint* newBreakpoint = new Breakpoint(breakpoint);
426 breakpoints.append(newBreakpoint);
427 m_breakpointIDToBreakpoint.set(id, newBreakpoint);
428
429 toggleBreakpoint(*newBreakpoint, BreakpointEnabled);
430
431 return id;
432}
433
434void Debugger::removeBreakpoint(BreakpointID id)
435{
436 ASSERT(id != noBreakpointID);
437
438 BreakpointIDToBreakpointMap::iterator idIt = m_breakpointIDToBreakpoint.find(id);
439 ASSERT(idIt != m_breakpointIDToBreakpoint.end());
440 Breakpoint* breakpoint = idIt->value;
441
442 SourceID sourceID = breakpoint->sourceID;
443 ASSERT(sourceID);
444 SourceIDToBreakpointsMap::iterator it = m_sourceIDToBreakpoints.find(sourceID);
445 ASSERT(it != m_sourceIDToBreakpoints.end());
446 LineToBreakpointsMap::iterator breaksIt = it->value.find(breakpoint->line);
447 ASSERT(breaksIt != it->value.end());
448
449 toggleBreakpoint(*breakpoint, BreakpointDisabled);
450
451 BreakpointsList& breakpoints = *breaksIt->value;
452#if !ASSERT_DISABLED
453 bool found = false;
454 for (Breakpoint* current = breakpoints.head(); current && !found; current = current->next()) {
455 if (current->id == breakpoint->id)
456 found = true;
457 }
458 ASSERT(found);
459#endif
460
461 m_breakpointIDToBreakpoint.remove(idIt);
462 breakpoints.remove(breakpoint);
463 delete breakpoint;
464
465 if (breakpoints.isEmpty()) {
466 it->value.remove(breaksIt);
467 if (it->value.isEmpty())
468 m_sourceIDToBreakpoints.remove(it);
469 }
470}
471
472bool Debugger::hasBreakpoint(SourceID sourceID, const TextPosition& position, Breakpoint *hitBreakpoint)
473{
474 if (!m_breakpointsActivated)
475 return false;
476
477 SourceIDToBreakpointsMap::const_iterator it = m_sourceIDToBreakpoints.find(sourceID);
478 if (it == m_sourceIDToBreakpoints.end())
479 return false;
480
481 unsigned line = position.m_line.zeroBasedInt();
482 unsigned column = position.m_column.zeroBasedInt();
483
484 LineToBreakpointsMap::const_iterator breaksIt = it->value.find(line);
485 if (breaksIt == it->value.end())
486 return false;
487
488 bool hit = false;
489 const BreakpointsList& breakpoints = *breaksIt->value;
490 Breakpoint* breakpoint;
491 for (breakpoint = breakpoints.head(); breakpoint; breakpoint = breakpoint->next()) {
492 unsigned breakLine = breakpoint->line;
493 unsigned breakColumn = breakpoint->column;
494 // Since frontend truncates the indent, the first statement in a line must match the breakpoint (line,0).
495 ASSERT(this == m_currentCallFrame->codeBlock()->globalObject()->debugger());
496 if ((line != m_lastExecutedLine && line == breakLine && !breakColumn)
497 || (line == breakLine && column == breakColumn)) {
498 hit = true;
499 break;
500 }
501 }
502 if (!hit)
503 return false;
504
505 if (hitBreakpoint)
506 *hitBreakpoint = *breakpoint;
507
508 breakpoint->hitCount++;
509 if (breakpoint->ignoreCount >= breakpoint->hitCount)
510 return false;
511
512 if (breakpoint->condition.isEmpty())
513 return true;
514
515 // We cannot stop in the debugger while executing condition code,
516 // so make it looks like the debugger is already paused.
517 TemporaryPausedState pausedState(*this);
518
519 NakedPtr<Exception> exception;
520 DebuggerCallFrame& debuggerCallFrame = currentDebuggerCallFrame();
521 JSObject* scopeExtensionObject = nullptr;
522 JSValue result = debuggerCallFrame.evaluateWithScopeExtension(breakpoint->condition, scopeExtensionObject, exception);
523
524 // We can lose the debugger while executing JavaScript.
525 if (!m_currentCallFrame)
526 return false;
527
528 JSGlobalObject* globalObject = m_currentCallFrame->lexicalGlobalObject(m_vm);
529 if (exception) {
530 // An erroneous condition counts as "false".
531 handleExceptionInBreakpointCondition(globalObject, exception);
532 return false;
533 }
534
535 return result.toBoolean(globalObject);
536}
537
538class Debugger::ClearCodeBlockDebuggerRequestsFunctor {
539public:
540 ClearCodeBlockDebuggerRequestsFunctor(Debugger* debugger)
541 : m_debugger(debugger)
542 {
543 }
544
545 void operator()(CodeBlock* codeBlock) const
546 {
547 if (codeBlock->hasDebuggerRequests() && m_debugger == codeBlock->globalObject()->debugger())
548 codeBlock->clearDebuggerRequests();
549 }
550
551private:
552 Debugger* m_debugger;
553};
554
555void Debugger::clearBreakpoints()
556{
557 m_vm.heap.completeAllJITPlans();
558
559 m_topBreakpointID = noBreakpointID;
560 m_breakpointIDToBreakpoint.clear();
561 m_sourceIDToBreakpoints.clear();
562
563 ClearCodeBlockDebuggerRequestsFunctor functor(this);
564 m_vm.heap.forEachCodeBlock(functor);
565}
566
567class Debugger::ClearDebuggerRequestsFunctor {
568public:
569 ClearDebuggerRequestsFunctor(JSGlobalObject* globalObject)
570 : m_globalObject(globalObject)
571 {
572 }
573
574 void operator()(CodeBlock* codeBlock) const
575 {
576 if (codeBlock->hasDebuggerRequests() && m_globalObject == codeBlock->globalObject())
577 codeBlock->clearDebuggerRequests();
578 }
579
580private:
581 JSGlobalObject* m_globalObject;
582};
583
584void Debugger::clearDebuggerRequests(JSGlobalObject* globalObject)
585{
586 m_vm.heap.completeAllJITPlans();
587
588 ClearDebuggerRequestsFunctor functor(globalObject);
589 m_vm.heap.forEachCodeBlock(functor);
590}
591
592void Debugger::clearParsedData()
593{
594 m_parseDataMap.clear();
595}
596
597void Debugger::setBreakpointsActivated(bool activated)
598{
599 if (activated == m_breakpointsActivated)
600 return;
601
602 m_breakpointsActivated = activated;
603 recompileAllJSFunctions();
604}
605
606void Debugger::setPauseOnExceptionsState(PauseOnExceptionsState pause)
607{
608 m_pauseOnExceptionsState = pause;
609}
610
611void Debugger::setPauseOnNextStatement(bool pause)
612{
613 m_pauseAtNextOpportunity = pause;
614 if (pause)
615 setSteppingMode(SteppingModeEnabled);
616}
617
618void Debugger::breakProgram()
619{
620 if (m_isPaused)
621 return;
622
623 if (!m_vm.topCallFrame)
624 return;
625
626 m_pauseAtNextOpportunity = true;
627 setSteppingMode(SteppingModeEnabled);
628 m_currentCallFrame = m_vm.topCallFrame;
629 pauseIfNeeded(m_currentCallFrame->lexicalGlobalObject(m_vm));
630}
631
632void Debugger::continueProgram()
633{
634 clearNextPauseState();
635
636 if (!m_isPaused)
637 return;
638
639 notifyDoneProcessingDebuggerEvents();
640}
641
642void Debugger::stepIntoStatement()
643{
644 if (!m_isPaused)
645 return;
646
647 m_pauseAtNextOpportunity = true;
648 setSteppingMode(SteppingModeEnabled);
649 notifyDoneProcessingDebuggerEvents();
650}
651
652void Debugger::stepOverStatement()
653{
654 if (!m_isPaused)
655 return;
656
657 m_pauseOnCallFrame = m_currentCallFrame;
658 setSteppingMode(SteppingModeEnabled);
659 notifyDoneProcessingDebuggerEvents();
660}
661
662void Debugger::stepOutOfFunction()
663{
664 if (!m_isPaused)
665 return;
666
667 EntryFrame* topEntryFrame = m_vm.topEntryFrame;
668 m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->callerFrame(topEntryFrame) : nullptr;
669 m_pauseOnStepOut = true;
670 setSteppingMode(SteppingModeEnabled);
671 notifyDoneProcessingDebuggerEvents();
672}
673
674static inline JSGlobalObject* lexicalGlobalObjectForCallFrame(VM& vm, CallFrame* callFrame)
675{
676 if (!callFrame)
677 return nullptr;
678 return callFrame->lexicalGlobalObject(vm);
679}
680
681void Debugger::updateCallFrame(JSGlobalObject* globalObject, CallFrame* callFrame, CallFrameUpdateAction action)
682{
683 if (!callFrame) {
684 m_currentCallFrame = nullptr;
685 return;
686 }
687 updateCallFrameInternal(callFrame);
688
689 if (action == AttemptPause)
690 pauseIfNeeded(globalObject);
691
692 if (!isStepping())
693 m_currentCallFrame = nullptr;
694}
695
696void Debugger::updateCallFrameInternal(CallFrame* callFrame)
697{
698 m_currentCallFrame = callFrame;
699 SourceID sourceID = DebuggerCallFrame::sourceIDForCallFrame(callFrame);
700 if (m_lastExecutedSourceID != sourceID) {
701 m_lastExecutedLine = UINT_MAX;
702 m_lastExecutedSourceID = sourceID;
703 }
704}
705
706void Debugger::pauseIfNeeded(JSGlobalObject* globalObject)
707{
708 VM& vm = m_vm;
709 auto scope = DECLARE_THROW_SCOPE(vm);
710
711 if (m_isPaused)
712 return;
713
714 if (m_suppressAllPauses)
715 return;
716
717 intptr_t sourceID = DebuggerCallFrame::sourceIDForCallFrame(m_currentCallFrame);
718
719 auto blackboxTypeIterator = m_blackboxedScripts.find(sourceID);
720 if (blackboxTypeIterator != m_blackboxedScripts.end() && blackboxTypeIterator->value == BlackboxType::Ignored)
721 return;
722
723 DebuggerPausedScope debuggerPausedScope(*this);
724
725 bool pauseNow = m_pauseAtNextOpportunity;
726 pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame);
727
728 bool didPauseForStep = pauseNow;
729
730 Breakpoint breakpoint;
731 TextPosition position = DebuggerCallFrame::positionForCallFrame(vm, m_currentCallFrame);
732 bool didHitBreakpoint = hasBreakpoint(sourceID, position, &breakpoint);
733 pauseNow |= didHitBreakpoint;
734 m_lastExecutedLine = position.m_line.zeroBasedInt();
735 if (!pauseNow)
736 return;
737
738 bool afterBlackboxedScript = m_afterBlackboxedScript;
739 clearNextPauseState();
740
741 // Make sure we are not going to pause again on breakpoint actions by
742 // reseting the pause state before executing any breakpoint actions.
743 TemporaryPausedState pausedState(*this);
744
745 if (didHitBreakpoint) {
746 handleBreakpointHit(globalObject, breakpoint);
747 // Note that the actions can potentially stop the debugger, so we need to check that
748 // we still have a current call frame when we get back.
749 if (!m_currentCallFrame)
750 return;
751
752 if (breakpoint.autoContinue) {
753 if (!didPauseForStep)
754 return;
755 didHitBreakpoint = false;
756 } else
757 m_pausingBreakpointID = breakpoint.id;
758 }
759
760 if (blackboxTypeIterator != m_blackboxedScripts.end() && blackboxTypeIterator->value == BlackboxType::Deferred) {
761 m_afterBlackboxedScript = true;
762 setPauseOnNextStatement(true);
763 return;
764 }
765
766 {
767 auto reason = m_reasonForPause;
768 if (afterBlackboxedScript)
769 reason = PausedAfterBlackboxedScript;
770 else if (didHitBreakpoint)
771 reason = PausedForBreakpoint;
772 PauseReasonDeclaration rauseReasonDeclaration(*this, reason);
773
774 handlePause(globalObject, m_reasonForPause);
775 scope.releaseAssertNoException();
776 }
777
778 m_pausingBreakpointID = noBreakpointID;
779
780 if (!m_pauseAtNextOpportunity && !m_pauseOnCallFrame) {
781 setSteppingMode(SteppingModeDisabled);
782 m_currentCallFrame = nullptr;
783 }
784}
785
786void Debugger::exception(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue exception, bool hasCatchHandler)
787{
788 if (m_isPaused)
789 return;
790
791 if (JSObject* object = jsDynamicCast<JSObject*>(m_vm, exception)) {
792 if (object->isErrorInstance()) {
793 ErrorInstance* error = static_cast<ErrorInstance*>(object);
794 // FIXME: <https://webkit.org/b/173625> Web Inspector: Should be able to pause and debug a StackOverflow Exception
795 // FIXME: <https://webkit.org/b/173627> Web Inspector: Should be able to pause and debug an OutOfMemory Exception
796 if (error->isStackOverflowError() || error->isOutOfMemoryError())
797 return;
798 }
799 }
800
801 PauseReasonDeclaration reason(*this, PausedForException);
802 if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasCatchHandler)) {
803 m_pauseAtNextOpportunity = true;
804 setSteppingMode(SteppingModeEnabled);
805 }
806
807 m_hasHandlerForExceptionCallback = true;
808 m_currentException = exception;
809 updateCallFrame(globalObject, callFrame, AttemptPause);
810 m_currentException = JSValue();
811 m_hasHandlerForExceptionCallback = false;
812}
813
814void Debugger::atStatement(CallFrame* callFrame)
815{
816 if (m_isPaused)
817 return;
818
819 m_pastFirstExpressionInStatement = false;
820
821 PauseReasonDeclaration reason(*this, PausedAtStatement);
822 updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callFrame), callFrame, AttemptPause);
823}
824
825void Debugger::atExpression(CallFrame* callFrame)
826{
827 if (m_isPaused)
828 return;
829
830 // If this is the first call in a statement, then we would have paused at the statement.
831 if (!m_pastFirstExpressionInStatement) {
832 m_pastFirstExpressionInStatement = true;
833 return;
834 }
835
836 // Only pause at the next expression with step-in and step-out, not step-over.
837 bool shouldAttemptPause = m_pauseAtNextOpportunity || m_pauseOnStepOut;
838
839 PauseReasonDeclaration reason(*this, PausedAtExpression);
840 updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callFrame), callFrame, shouldAttemptPause ? AttemptPause : NoPause);
841}
842
843void Debugger::callEvent(CallFrame* callFrame)
844{
845 if (m_isPaused)
846 return;
847
848 updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callFrame), callFrame, NoPause);
849}
850
851void Debugger::returnEvent(CallFrame* callFrame)
852{
853 if (m_isPaused)
854 return;
855
856 {
857 PauseReasonDeclaration reason(*this, PausedBeforeReturn);
858 updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callFrame), callFrame, AttemptPause);
859 }
860
861 // Detach may have been called during pauseIfNeeded.
862 if (!m_currentCallFrame)
863 return;
864
865 EntryFrame* topEntryFrame = m_vm.topEntryFrame;
866 CallFrame* callerFrame = m_currentCallFrame->callerFrame(topEntryFrame);
867
868 // Returning from a call, there was at least one expression on the statement we are returning to.
869 m_pastFirstExpressionInStatement = true;
870
871 // Treat stepping over a return statement like a step-out.
872 if (m_currentCallFrame == m_pauseOnCallFrame) {
873 m_pauseOnCallFrame = callerFrame;
874 m_pauseOnStepOut = true;
875 }
876
877 updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callerFrame), callerFrame, NoPause);
878}
879
880void Debugger::unwindEvent(CallFrame* callFrame)
881{
882 if (m_isPaused)
883 return;
884
885 updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callFrame), callFrame, NoPause);
886
887 if (!m_currentCallFrame)
888 return;
889
890 EntryFrame* topEntryFrame = m_vm.topEntryFrame;
891 CallFrame* callerFrame = m_currentCallFrame->callerFrame(topEntryFrame);
892
893 // Treat stepping over an exception location like a step-out.
894 if (m_currentCallFrame == m_pauseOnCallFrame)
895 m_pauseOnCallFrame = callerFrame;
896
897 updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callerFrame), callerFrame, NoPause);
898}
899
900void Debugger::willExecuteProgram(CallFrame* callFrame)
901{
902 if (m_isPaused)
903 return;
904
905 updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callFrame), callFrame, NoPause);
906}
907
908void Debugger::didExecuteProgram(CallFrame* callFrame)
909{
910 if (m_isPaused)
911 return;
912
913 PauseReasonDeclaration reason(*this, PausedAtEndOfProgram);
914 updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callFrame), callFrame, AttemptPause);
915
916 // Detach may have been called during pauseIfNeeded.
917 if (!m_currentCallFrame)
918 return;
919
920 EntryFrame* topEntryFrame = m_vm.topEntryFrame;
921 CallFrame* callerFrame = m_currentCallFrame->callerFrame(topEntryFrame);
922
923 // Returning from a program, could be eval(), there was at least one expression on the statement we are returning to.
924 m_pastFirstExpressionInStatement = true;
925
926 // Treat stepping over the end of a program like a step-out.
927 if (m_currentCallFrame == m_pauseOnCallFrame) {
928 m_pauseOnCallFrame = callerFrame;
929 m_pauseAtNextOpportunity = true;
930 }
931
932 updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callerFrame), callerFrame, NoPause);
933
934 // Do not continue stepping into an unknown future program.
935 if (!m_currentCallFrame)
936 clearNextPauseState();
937}
938
939void Debugger::clearNextPauseState()
940{
941 m_pauseOnCallFrame = nullptr;
942 m_pauseAtNextOpportunity = false;
943 m_pauseOnStepOut = false;
944 m_afterBlackboxedScript = false;
945}
946
947void Debugger::didReachBreakpoint(CallFrame* callFrame)
948{
949 if (m_isPaused)
950 return;
951
952 PauseReasonDeclaration reason(*this, PausedForDebuggerStatement);
953 m_pauseAtNextOpportunity = true;
954 setSteppingMode(SteppingModeEnabled);
955 updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callFrame), callFrame, AttemptPause);
956}
957
958DebuggerCallFrame& Debugger::currentDebuggerCallFrame()
959{
960 if (!m_currentDebuggerCallFrame)
961 m_currentDebuggerCallFrame = DebuggerCallFrame::create(m_vm, m_currentCallFrame);
962 return *m_currentDebuggerCallFrame;
963}
964
965void Debugger::setBlackboxType(SourceID sourceID, Optional<BlackboxType> type)
966{
967 if (type)
968 m_blackboxedScripts.set(sourceID, type.value());
969 else
970 m_blackboxedScripts.remove(sourceID);
971}
972
973void Debugger::clearBlackbox()
974{
975 m_blackboxedScripts.clear();
976}
977
978} // namespace JSC
979