1/*
2 * Copyright (C) 2008-2018 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->globalExec(), 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 line = breakpoint.line;
272 unsigned column = breakpoint.column;
273
274 unsigned startLine = executable->firstLine();
275 unsigned startColumn = executable->startColumn();
276 unsigned endLine = executable->lastLine();
277 unsigned endColumn = executable->endColumn();
278
279 // Inspector breakpoint line and column values are zero-based but the executable
280 // and CodeBlock line and column values are one-based.
281 line += 1;
282 column = column ? column + 1 : Breakpoint::unspecifiedColumn;
283
284 if (line < startLine || line > endLine)
285 return;
286 if (column != Breakpoint::unspecifiedColumn) {
287 if (line == startLine && column < startColumn)
288 return;
289 if (line == endLine && column > endColumn)
290 return;
291 }
292
293 if (!codeBlock->hasOpDebugForLineAndColumn(line, column))
294 return;
295
296 if (enabledOrNot == BreakpointEnabled)
297 codeBlock->addBreakpoint(1);
298 else
299 codeBlock->removeBreakpoint(1);
300}
301
302void Debugger::applyBreakpoints(CodeBlock* codeBlock)
303{
304 BreakpointIDToBreakpointMap& breakpoints = m_breakpointIDToBreakpoint;
305 for (auto* breakpoint : breakpoints.values())
306 toggleBreakpoint(codeBlock, *breakpoint, BreakpointEnabled);
307}
308
309class Debugger::ToggleBreakpointFunctor {
310public:
311 ToggleBreakpointFunctor(Debugger* debugger, Breakpoint& breakpoint, BreakpointState enabledOrNot)
312 : m_debugger(debugger)
313 , m_breakpoint(breakpoint)
314 , m_enabledOrNot(enabledOrNot)
315 {
316 }
317
318 void operator()(CodeBlock* codeBlock) const
319 {
320 if (m_debugger == codeBlock->globalObject()->debugger())
321 m_debugger->toggleBreakpoint(codeBlock, m_breakpoint, m_enabledOrNot);
322 }
323
324private:
325 Debugger* m_debugger;
326 Breakpoint& m_breakpoint;
327 BreakpointState m_enabledOrNot;
328};
329
330void Debugger::toggleBreakpoint(Breakpoint& breakpoint, Debugger::BreakpointState enabledOrNot)
331{
332 m_vm.heap.completeAllJITPlans();
333
334 ToggleBreakpointFunctor functor(this, breakpoint, enabledOrNot);
335 m_vm.heap.forEachCodeBlock(functor);
336}
337
338void Debugger::recompileAllJSFunctions()
339{
340 m_vm.deleteAllCode(PreventCollectionAndDeleteAllCode);
341}
342
343DebuggerParseData& Debugger::debuggerParseData(SourceID sourceID, SourceProvider* provider)
344{
345 auto iter = m_parseDataMap.find(sourceID);
346 if (iter != m_parseDataMap.end())
347 return iter->value;
348
349 DebuggerParseData parseData;
350 gatherDebuggerParseDataForSource(m_vm, provider, parseData);
351 auto result = m_parseDataMap.add(sourceID, parseData);
352 return result.iterator->value;
353}
354
355void Debugger::resolveBreakpoint(Breakpoint& breakpoint, SourceProvider* sourceProvider)
356{
357 RELEASE_ASSERT(!breakpoint.resolved);
358 ASSERT(breakpoint.sourceID != noSourceID);
359
360 // FIXME: <https://webkit.org/b/162771> Web Inspector: Adopt TextPosition in Inspector to avoid oneBasedInt/zeroBasedInt ambiguity
361 // Inspector breakpoint line and column values are zero-based but the executable
362 // and CodeBlock line and column values are one-based.
363 unsigned line = breakpoint.line + 1;
364 unsigned column = breakpoint.column ? breakpoint.column : Breakpoint::unspecifiedColumn;
365
366 DebuggerParseData& parseData = debuggerParseData(breakpoint.sourceID, sourceProvider);
367 Optional<JSTextPosition> resolvedPosition = parseData.pausePositions.breakpointLocationForLineColumn((int)line, (int)column);
368 if (!resolvedPosition)
369 return;
370
371 unsigned resolvedLine = resolvedPosition->line;
372 unsigned resolvedColumn = resolvedPosition->offset - resolvedPosition->lineStartOffset + 1;
373
374 breakpoint.line = resolvedLine - 1;
375 breakpoint.column = resolvedColumn - 1;
376 breakpoint.resolved = true;
377}
378
379BreakpointID Debugger::setBreakpoint(Breakpoint& breakpoint, bool& existing)
380{
381 ASSERT(breakpoint.resolved);
382 ASSERT(breakpoint.sourceID != noSourceID);
383
384 SourceID sourceID = breakpoint.sourceID;
385 unsigned line = breakpoint.line;
386 unsigned column = breakpoint.column;
387
388 SourceIDToBreakpointsMap::iterator it = m_sourceIDToBreakpoints.find(breakpoint.sourceID);
389 if (it == m_sourceIDToBreakpoints.end())
390 it = m_sourceIDToBreakpoints.set(sourceID, LineToBreakpointsMap()).iterator;
391
392 LineToBreakpointsMap::iterator breaksIt = it->value.find(line);
393 if (breaksIt == it->value.end())
394 breaksIt = it->value.set(line, adoptRef(new BreakpointsList)).iterator;
395
396 BreakpointsList& breakpoints = *breaksIt->value;
397 for (Breakpoint* current = breakpoints.head(); current; current = current->next()) {
398 if (current->column == column) {
399 // Found existing breakpoint. Do not create a duplicate at this location.
400 existing = true;
401 return current->id;
402 }
403 }
404
405 existing = false;
406 BreakpointID id = ++m_topBreakpointID;
407 RELEASE_ASSERT(id != noBreakpointID);
408
409 breakpoint.id = id;
410
411 Breakpoint* newBreakpoint = new Breakpoint(breakpoint);
412 breakpoints.append(newBreakpoint);
413 m_breakpointIDToBreakpoint.set(id, newBreakpoint);
414
415 toggleBreakpoint(*newBreakpoint, BreakpointEnabled);
416
417 return id;
418}
419
420void Debugger::removeBreakpoint(BreakpointID id)
421{
422 ASSERT(id != noBreakpointID);
423
424 BreakpointIDToBreakpointMap::iterator idIt = m_breakpointIDToBreakpoint.find(id);
425 ASSERT(idIt != m_breakpointIDToBreakpoint.end());
426 Breakpoint* breakpoint = idIt->value;
427
428 SourceID sourceID = breakpoint->sourceID;
429 ASSERT(sourceID);
430 SourceIDToBreakpointsMap::iterator it = m_sourceIDToBreakpoints.find(sourceID);
431 ASSERT(it != m_sourceIDToBreakpoints.end());
432 LineToBreakpointsMap::iterator breaksIt = it->value.find(breakpoint->line);
433 ASSERT(breaksIt != it->value.end());
434
435 toggleBreakpoint(*breakpoint, BreakpointDisabled);
436
437 BreakpointsList& breakpoints = *breaksIt->value;
438#if !ASSERT_DISABLED
439 bool found = false;
440 for (Breakpoint* current = breakpoints.head(); current && !found; current = current->next()) {
441 if (current->id == breakpoint->id)
442 found = true;
443 }
444 ASSERT(found);
445#endif
446
447 m_breakpointIDToBreakpoint.remove(idIt);
448 breakpoints.remove(breakpoint);
449 delete breakpoint;
450
451 if (breakpoints.isEmpty()) {
452 it->value.remove(breaksIt);
453 if (it->value.isEmpty())
454 m_sourceIDToBreakpoints.remove(it);
455 }
456}
457
458bool Debugger::hasBreakpoint(SourceID sourceID, const TextPosition& position, Breakpoint *hitBreakpoint)
459{
460 if (!m_breakpointsActivated)
461 return false;
462
463 SourceIDToBreakpointsMap::const_iterator it = m_sourceIDToBreakpoints.find(sourceID);
464 if (it == m_sourceIDToBreakpoints.end())
465 return false;
466
467 unsigned line = position.m_line.zeroBasedInt();
468 unsigned column = position.m_column.zeroBasedInt();
469
470 LineToBreakpointsMap::const_iterator breaksIt = it->value.find(line);
471 if (breaksIt == it->value.end())
472 return false;
473
474 bool hit = false;
475 const BreakpointsList& breakpoints = *breaksIt->value;
476 Breakpoint* breakpoint;
477 for (breakpoint = breakpoints.head(); breakpoint; breakpoint = breakpoint->next()) {
478 unsigned breakLine = breakpoint->line;
479 unsigned breakColumn = breakpoint->column;
480 // Since frontend truncates the indent, the first statement in a line must match the breakpoint (line,0).
481 ASSERT(this == m_currentCallFrame->codeBlock()->globalObject()->debugger());
482 if ((line != m_lastExecutedLine && line == breakLine && !breakColumn)
483 || (line == breakLine && column == breakColumn)) {
484 hit = true;
485 break;
486 }
487 }
488 if (!hit)
489 return false;
490
491 if (hitBreakpoint)
492 *hitBreakpoint = *breakpoint;
493
494 breakpoint->hitCount++;
495 if (breakpoint->ignoreCount >= breakpoint->hitCount)
496 return false;
497
498 if (breakpoint->condition.isEmpty())
499 return true;
500
501 // We cannot stop in the debugger while executing condition code,
502 // so make it looks like the debugger is already paused.
503 TemporaryPausedState pausedState(*this);
504
505 NakedPtr<Exception> exception;
506 DebuggerCallFrame& debuggerCallFrame = currentDebuggerCallFrame();
507 JSObject* scopeExtensionObject = nullptr;
508 JSValue result = debuggerCallFrame.evaluateWithScopeExtension(breakpoint->condition, scopeExtensionObject, exception);
509
510 // We can lose the debugger while executing JavaScript.
511 if (!m_currentCallFrame)
512 return false;
513
514 if (exception) {
515 // An erroneous condition counts as "false".
516 handleExceptionInBreakpointCondition(m_currentCallFrame, exception);
517 return false;
518 }
519
520 return result.toBoolean(m_currentCallFrame);
521}
522
523class Debugger::ClearCodeBlockDebuggerRequestsFunctor {
524public:
525 ClearCodeBlockDebuggerRequestsFunctor(Debugger* debugger)
526 : m_debugger(debugger)
527 {
528 }
529
530 void operator()(CodeBlock* codeBlock) const
531 {
532 if (codeBlock->hasDebuggerRequests() && m_debugger == codeBlock->globalObject()->debugger())
533 codeBlock->clearDebuggerRequests();
534 }
535
536private:
537 Debugger* m_debugger;
538};
539
540void Debugger::clearBreakpoints()
541{
542 m_vm.heap.completeAllJITPlans();
543
544 m_topBreakpointID = noBreakpointID;
545 m_breakpointIDToBreakpoint.clear();
546 m_sourceIDToBreakpoints.clear();
547
548 ClearCodeBlockDebuggerRequestsFunctor functor(this);
549 m_vm.heap.forEachCodeBlock(functor);
550}
551
552class Debugger::ClearDebuggerRequestsFunctor {
553public:
554 ClearDebuggerRequestsFunctor(JSGlobalObject* globalObject)
555 : m_globalObject(globalObject)
556 {
557 }
558
559 void operator()(CodeBlock* codeBlock) const
560 {
561 if (codeBlock->hasDebuggerRequests() && m_globalObject == codeBlock->globalObject())
562 codeBlock->clearDebuggerRequests();
563 }
564
565private:
566 JSGlobalObject* m_globalObject;
567};
568
569void Debugger::clearDebuggerRequests(JSGlobalObject* globalObject)
570{
571 m_vm.heap.completeAllJITPlans();
572
573 ClearDebuggerRequestsFunctor functor(globalObject);
574 m_vm.heap.forEachCodeBlock(functor);
575}
576
577void Debugger::clearParsedData()
578{
579 m_parseDataMap.clear();
580}
581
582void Debugger::setBreakpointsActivated(bool activated)
583{
584 if (activated == m_breakpointsActivated)
585 return;
586
587 m_breakpointsActivated = activated;
588 recompileAllJSFunctions();
589}
590
591void Debugger::setPauseOnExceptionsState(PauseOnExceptionsState pause)
592{
593 m_pauseOnExceptionsState = pause;
594}
595
596void Debugger::setPauseOnNextStatement(bool pause)
597{
598 m_pauseAtNextOpportunity = pause;
599 if (pause)
600 setSteppingMode(SteppingModeEnabled);
601}
602
603void Debugger::breakProgram()
604{
605 if (m_isPaused)
606 return;
607
608 if (!m_vm.topCallFrame)
609 return;
610
611 m_pauseAtNextOpportunity = true;
612 setSteppingMode(SteppingModeEnabled);
613 m_currentCallFrame = m_vm.topCallFrame;
614 pauseIfNeeded(m_currentCallFrame);
615}
616
617void Debugger::continueProgram()
618{
619 clearNextPauseState();
620
621 if (!m_isPaused)
622 return;
623
624 notifyDoneProcessingDebuggerEvents();
625}
626
627void Debugger::stepIntoStatement()
628{
629 if (!m_isPaused)
630 return;
631
632 m_pauseAtNextOpportunity = true;
633 setSteppingMode(SteppingModeEnabled);
634 notifyDoneProcessingDebuggerEvents();
635}
636
637void Debugger::stepOverStatement()
638{
639 if (!m_isPaused)
640 return;
641
642 m_pauseOnCallFrame = m_currentCallFrame;
643 setSteppingMode(SteppingModeEnabled);
644 notifyDoneProcessingDebuggerEvents();
645}
646
647void Debugger::stepOutOfFunction()
648{
649 if (!m_isPaused)
650 return;
651
652 EntryFrame* topEntryFrame = m_vm.topEntryFrame;
653 m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->callerFrame(topEntryFrame) : nullptr;
654 m_pauseOnStepOut = true;
655 setSteppingMode(SteppingModeEnabled);
656 notifyDoneProcessingDebuggerEvents();
657}
658
659void Debugger::updateCallFrame(CallFrame* callFrame, CallFrameUpdateAction action)
660{
661 if (!callFrame) {
662 m_currentCallFrame = nullptr;
663 return;
664 }
665
666 updateCallFrameInternal(callFrame);
667
668 if (action == AttemptPause)
669 pauseIfNeeded(callFrame);
670
671 if (!isStepping())
672 m_currentCallFrame = nullptr;
673}
674
675void Debugger::updateCallFrameInternal(CallFrame* callFrame)
676{
677 m_currentCallFrame = callFrame;
678 SourceID sourceID = DebuggerCallFrame::sourceIDForCallFrame(callFrame);
679 if (m_lastExecutedSourceID != sourceID) {
680 m_lastExecutedLine = UINT_MAX;
681 m_lastExecutedSourceID = sourceID;
682 }
683}
684
685void Debugger::pauseIfNeeded(CallFrame* callFrame)
686{
687 VM& vm = m_vm;
688 auto scope = DECLARE_THROW_SCOPE(vm);
689 ASSERT(callFrame);
690
691 if (m_isPaused)
692 return;
693
694 if (m_suppressAllPauses)
695 return;
696
697 intptr_t sourceID = DebuggerCallFrame::sourceIDForCallFrame(m_currentCallFrame);
698 if (isBlacklisted(sourceID))
699 return;
700
701 DebuggerPausedScope debuggerPausedScope(*this);
702
703 bool pauseNow = m_pauseAtNextOpportunity;
704 pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame);
705
706 bool didPauseForStep = pauseNow;
707
708 Breakpoint breakpoint;
709 TextPosition position = DebuggerCallFrame::positionForCallFrame(vm, m_currentCallFrame);
710 bool didHitBreakpoint = hasBreakpoint(sourceID, position, &breakpoint);
711 pauseNow |= didHitBreakpoint;
712 m_lastExecutedLine = position.m_line.zeroBasedInt();
713 if (!pauseNow)
714 return;
715
716 clearNextPauseState();
717
718 // Make sure we are not going to pause again on breakpoint actions by
719 // reseting the pause state before executing any breakpoint actions.
720 TemporaryPausedState pausedState(*this);
721
722 JSGlobalObject* vmEntryGlobalObject = vm.vmEntryGlobalObject(callFrame);
723
724 if (didHitBreakpoint) {
725 handleBreakpointHit(vmEntryGlobalObject, breakpoint);
726 // Note that the actions can potentially stop the debugger, so we need to check that
727 // we still have a current call frame when we get back.
728 if (!m_currentCallFrame)
729 return;
730
731 if (breakpoint.autoContinue) {
732 if (!didPauseForStep)
733 return;
734 didHitBreakpoint = false;
735 } else
736 m_pausingBreakpointID = breakpoint.id;
737 }
738
739 {
740 PauseReasonDeclaration reason(*this, didHitBreakpoint ? PausedForBreakpoint : m_reasonForPause);
741 handlePause(vmEntryGlobalObject, m_reasonForPause);
742 scope.releaseAssertNoException();
743 }
744
745 m_pausingBreakpointID = noBreakpointID;
746
747 if (!m_pauseAtNextOpportunity && !m_pauseOnCallFrame) {
748 setSteppingMode(SteppingModeDisabled);
749 m_currentCallFrame = nullptr;
750 }
751}
752
753void Debugger::exception(CallFrame* callFrame, JSValue exception, bool hasCatchHandler)
754{
755 if (m_isPaused)
756 return;
757
758 if (JSObject* object = jsDynamicCast<JSObject*>(m_vm, exception)) {
759 if (object->isErrorInstance()) {
760 ErrorInstance* error = static_cast<ErrorInstance*>(object);
761 // FIXME: <https://webkit.org/b/173625> Web Inspector: Should be able to pause and debug a StackOverflow Exception
762 // FIXME: <https://webkit.org/b/173627> Web Inspector: Should be able to pause and debug an OutOfMemory Exception
763 if (error->isStackOverflowError() || error->isOutOfMemoryError())
764 return;
765 }
766 }
767
768 PauseReasonDeclaration reason(*this, PausedForException);
769 if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasCatchHandler)) {
770 m_pauseAtNextOpportunity = true;
771 setSteppingMode(SteppingModeEnabled);
772 }
773
774 m_hasHandlerForExceptionCallback = true;
775 m_currentException = exception;
776 updateCallFrame(callFrame, AttemptPause);
777 m_currentException = JSValue();
778 m_hasHandlerForExceptionCallback = false;
779}
780
781void Debugger::atStatement(CallFrame* callFrame)
782{
783 if (m_isPaused)
784 return;
785
786 m_pastFirstExpressionInStatement = false;
787
788 PauseReasonDeclaration reason(*this, PausedAtStatement);
789 updateCallFrame(callFrame, AttemptPause);
790}
791
792void Debugger::atExpression(CallFrame* callFrame)
793{
794 if (m_isPaused)
795 return;
796
797 // If this is the first call in a statement, then we would have paused at the statement.
798 if (!m_pastFirstExpressionInStatement) {
799 m_pastFirstExpressionInStatement = true;
800 return;
801 }
802
803 // Only pause at the next expression with step-in and step-out, not step-over.
804 bool shouldAttemptPause = m_pauseAtNextOpportunity || m_pauseOnStepOut;
805
806 PauseReasonDeclaration reason(*this, PausedAtExpression);
807 updateCallFrame(callFrame, shouldAttemptPause ? AttemptPause : NoPause);
808}
809
810void Debugger::callEvent(CallFrame* callFrame)
811{
812 if (m_isPaused)
813 return;
814
815 updateCallFrame(callFrame, NoPause);
816}
817
818void Debugger::returnEvent(CallFrame* callFrame)
819{
820 if (m_isPaused)
821 return;
822
823 {
824 PauseReasonDeclaration reason(*this, PausedBeforeReturn);
825 updateCallFrame(callFrame, AttemptPause);
826 }
827
828 // Detach may have been called during pauseIfNeeded.
829 if (!m_currentCallFrame)
830 return;
831
832 EntryFrame* topEntryFrame = m_vm.topEntryFrame;
833 CallFrame* callerFrame = m_currentCallFrame->callerFrame(topEntryFrame);
834
835 // Returning from a call, there was at least one expression on the statement we are returning to.
836 m_pastFirstExpressionInStatement = true;
837
838 // Treat stepping over a return statement like a step-out.
839 if (m_currentCallFrame == m_pauseOnCallFrame) {
840 m_pauseOnCallFrame = callerFrame;
841 m_pauseOnStepOut = true;
842 }
843
844 updateCallFrame(callerFrame, NoPause);
845}
846
847void Debugger::unwindEvent(CallFrame* callFrame)
848{
849 if (m_isPaused)
850 return;
851
852 updateCallFrame(callFrame, NoPause);
853
854 if (!m_currentCallFrame)
855 return;
856
857 EntryFrame* topEntryFrame = m_vm.topEntryFrame;
858 CallFrame* callerFrame = m_currentCallFrame->callerFrame(topEntryFrame);
859
860 // Treat stepping over an exception location like a step-out.
861 if (m_currentCallFrame == m_pauseOnCallFrame)
862 m_pauseOnCallFrame = callerFrame;
863
864 updateCallFrame(callerFrame, NoPause);
865}
866
867void Debugger::willExecuteProgram(CallFrame* callFrame)
868{
869 if (m_isPaused)
870 return;
871
872 updateCallFrame(callFrame, NoPause);
873}
874
875void Debugger::didExecuteProgram(CallFrame* callFrame)
876{
877 if (m_isPaused)
878 return;
879
880 PauseReasonDeclaration reason(*this, PausedAtEndOfProgram);
881 updateCallFrame(callFrame, AttemptPause);
882
883 // Detach may have been called during pauseIfNeeded.
884 if (!m_currentCallFrame)
885 return;
886
887 EntryFrame* topEntryFrame = m_vm.topEntryFrame;
888 CallFrame* callerFrame = m_currentCallFrame->callerFrame(topEntryFrame);
889
890 // Returning from a program, could be eval(), there was at least one expression on the statement we are returning to.
891 m_pastFirstExpressionInStatement = true;
892
893 // Treat stepping over the end of a program like a step-out.
894 if (m_currentCallFrame == m_pauseOnCallFrame) {
895 m_pauseOnCallFrame = callerFrame;
896 m_pauseAtNextOpportunity = true;
897 }
898
899 updateCallFrame(callerFrame, NoPause);
900
901 // Do not continue stepping into an unknown future program.
902 if (!m_currentCallFrame)
903 clearNextPauseState();
904}
905
906void Debugger::clearNextPauseState()
907{
908 m_pauseOnCallFrame = nullptr;
909 m_pauseAtNextOpportunity = false;
910 m_pauseOnStepOut = false;
911}
912
913void Debugger::didReachBreakpoint(CallFrame* callFrame)
914{
915 if (m_isPaused)
916 return;
917
918 PauseReasonDeclaration reason(*this, PausedForDebuggerStatement);
919 m_pauseAtNextOpportunity = true;
920 setSteppingMode(SteppingModeEnabled);
921 updateCallFrame(callFrame, AttemptPause);
922}
923
924DebuggerCallFrame& Debugger::currentDebuggerCallFrame()
925{
926 if (!m_currentDebuggerCallFrame)
927 m_currentDebuggerCallFrame = DebuggerCallFrame::create(m_vm, m_currentCallFrame);
928 return *m_currentDebuggerCallFrame;
929}
930
931bool Debugger::isBlacklisted(SourceID sourceID) const
932{
933 return m_blacklistedScripts.contains(sourceID);
934}
935
936void Debugger::addToBlacklist(SourceID sourceID)
937{
938 m_blacklistedScripts.add(sourceID);
939}
940
941void Debugger::clearBlacklist()
942{
943 m_blacklistedScripts.clear();
944}
945
946} // namespace JSC
947