]>
Commit | Line | Data |
---|---|---|
9dae56ea | 1 | /* |
81345200 | 2 | * Copyright (C) 2008, 2013, 2014 Apple Inc. All rights reserved. |
9dae56ea A |
3 | * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) |
4 | * Copyright (C) 2001 Peter Kelly (pmk@post.com) | |
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 | ||
81345200 A |
25 | #include "CodeBlock.h" |
26 | #include "DebuggerCallFrame.h" | |
f9bf01c6 | 27 | #include "Error.h" |
81345200 | 28 | #include "HeapIterationScope.h" |
9dae56ea | 29 | #include "Interpreter.h" |
81345200 | 30 | #include "JSCJSValueInlines.h" |
f9bf01c6 A |
31 | #include "JSFunction.h" |
32 | #include "JSGlobalObject.h" | |
81345200 | 33 | #include "JSCInlines.h" |
9dae56ea | 34 | #include "Parser.h" |
f9bf01c6 | 35 | #include "Protect.h" |
81345200 | 36 | #include "VMEntryScope.h" |
9dae56ea | 37 | |
14957cd0 A |
38 | namespace { |
39 | ||
40 | using namespace JSC; | |
41 | ||
6fe7ccc8 | 42 | class Recompiler : public MarkedBlock::VoidFunctor { |
14957cd0 | 43 | public: |
6fe7ccc8 | 44 | Recompiler(JSC::Debugger*); |
14957cd0 | 45 | ~Recompiler(); |
ed1e77d3 | 46 | IterationStatus operator()(JSCell*); |
14957cd0 A |
47 | |
48 | private: | |
49 | typedef HashSet<FunctionExecutable*> FunctionExecutableSet; | |
50 | typedef HashMap<SourceProvider*, ExecState*> SourceProviderMap; | |
51 | ||
ed1e77d3 A |
52 | void visit(JSCell*); |
53 | ||
6fe7ccc8 | 54 | JSC::Debugger* m_debugger; |
14957cd0 A |
55 | FunctionExecutableSet m_functionExecutables; |
56 | SourceProviderMap m_sourceProviders; | |
57 | }; | |
58 | ||
6fe7ccc8 | 59 | inline Recompiler::Recompiler(JSC::Debugger* debugger) |
14957cd0 A |
60 | : m_debugger(debugger) |
61 | { | |
62 | } | |
63 | ||
64 | inline Recompiler::~Recompiler() | |
65 | { | |
66 | // Call sourceParsed() after reparsing all functions because it will execute | |
67 | // JavaScript in the inspector. | |
68 | SourceProviderMap::const_iterator end = m_sourceProviders.end(); | |
69 | for (SourceProviderMap::const_iterator iter = m_sourceProviders.begin(); iter != end; ++iter) | |
93a37866 | 70 | m_debugger->sourceParsed(iter->value, iter->key, -1, String()); |
14957cd0 A |
71 | } |
72 | ||
ed1e77d3 | 73 | inline void Recompiler::visit(JSCell* cell) |
14957cd0 | 74 | { |
81345200 | 75 | if (!cell->inherits(JSFunction::info())) |
14957cd0 A |
76 | return; |
77 | ||
6fe7ccc8 | 78 | JSFunction* function = jsCast<JSFunction*>(cell); |
14957cd0 A |
79 | if (function->executable()->isHostFunction()) |
80 | return; | |
81 | ||
82 | FunctionExecutable* executable = function->jsExecutable(); | |
83 | ||
84 | // Check if the function is already in the set - if so, | |
85 | // we've already retranslated it, nothing to do here. | |
6fe7ccc8 | 86 | if (!m_functionExecutables.add(executable).isNewEntry) |
14957cd0 A |
87 | return; |
88 | ||
93a37866 | 89 | ExecState* exec = function->scope()->globalObject()->JSGlobalObject::globalExec(); |
ed1e77d3 A |
90 | executable->clearCode(); |
91 | executable->clearUnlinkedCodeForRecompilation(); | |
93a37866 | 92 | if (m_debugger == function->scope()->globalObject()->debugger()) |
14957cd0 A |
93 | m_sourceProviders.add(executable->source().provider(), exec); |
94 | } | |
95 | ||
ed1e77d3 A |
96 | inline IterationStatus Recompiler::operator()(JSCell* cell) |
97 | { | |
98 | visit(cell); | |
99 | return IterationStatus::Continue; | |
100 | } | |
101 | ||
14957cd0 A |
102 | } // namespace |
103 | ||
9dae56ea A |
104 | namespace JSC { |
105 | ||
ed1e77d3 | 106 | class DebuggerPausedScope { |
81345200 | 107 | public: |
ed1e77d3 | 108 | DebuggerPausedScope(Debugger& debugger) |
81345200 A |
109 | : m_debugger(debugger) |
110 | { | |
111 | ASSERT(!m_debugger.m_currentDebuggerCallFrame); | |
112 | if (m_debugger.m_currentCallFrame) | |
113 | m_debugger.m_currentDebuggerCallFrame = DebuggerCallFrame::create(debugger.m_currentCallFrame); | |
114 | } | |
115 | ||
ed1e77d3 | 116 | ~DebuggerPausedScope() |
81345200 A |
117 | { |
118 | if (m_debugger.m_currentDebuggerCallFrame) { | |
119 | m_debugger.m_currentDebuggerCallFrame->invalidate(); | |
ed1e77d3 | 120 | m_debugger.m_currentDebuggerCallFrame = nullptr; |
81345200 A |
121 | } |
122 | } | |
123 | ||
124 | private: | |
125 | Debugger& m_debugger; | |
126 | }; | |
127 | ||
128 | // This is very similar to TemporaryChange<bool>, but that cannot be used | |
129 | // as the m_isPaused field uses only one bit. | |
130 | class TemporaryPausedState { | |
131 | public: | |
132 | TemporaryPausedState(Debugger& debugger) | |
133 | : m_debugger(debugger) | |
134 | { | |
135 | ASSERT(!m_debugger.m_isPaused); | |
136 | m_debugger.m_isPaused = true; | |
137 | } | |
138 | ||
139 | ~TemporaryPausedState() | |
140 | { | |
141 | m_debugger.m_isPaused = false; | |
142 | } | |
143 | ||
144 | private: | |
145 | Debugger& m_debugger; | |
146 | }; | |
147 | ||
148 | template<typename Functor> | |
149 | void Debugger::forEachCodeBlock(Functor& functor) | |
150 | { | |
ed1e77d3 | 151 | m_vm->prepareToDiscardCode(); |
81345200 A |
152 | m_vm->heap.forEachCodeBlock(functor); |
153 | } | |
154 | ||
155 | Debugger::Debugger(bool isInWorkerThread) | |
156 | : m_vm(nullptr) | |
157 | , m_pauseOnExceptionsState(DontPauseOnExceptions) | |
158 | , m_pauseOnNextStatement(false) | |
159 | , m_isPaused(false) | |
160 | , m_breakpointsActivated(true) | |
161 | , m_hasHandlerForExceptionCallback(false) | |
162 | , m_isInWorkerThread(isInWorkerThread) | |
163 | , m_steppingMode(SteppingModeDisabled) | |
164 | , m_reasonForPause(NotPaused) | |
165 | , m_pauseOnCallFrame(0) | |
166 | , m_currentCallFrame(0) | |
167 | , m_lastExecutedLine(UINT_MAX) | |
168 | , m_lastExecutedSourceID(noSourceID) | |
169 | , m_topBreakpointID(noBreakpointID) | |
ed1e77d3 | 170 | , m_pausingBreakpointID(noBreakpointID) |
81345200 A |
171 | { |
172 | } | |
173 | ||
9dae56ea A |
174 | Debugger::~Debugger() |
175 | { | |
176 | HashSet<JSGlobalObject*>::iterator end = m_globalObjects.end(); | |
177 | for (HashSet<JSGlobalObject*>::iterator it = m_globalObjects.begin(); it != end; ++it) | |
178 | (*it)->setDebugger(0); | |
179 | } | |
180 | ||
181 | void Debugger::attach(JSGlobalObject* globalObject) | |
182 | { | |
183 | ASSERT(!globalObject->debugger()); | |
81345200 A |
184 | if (!m_vm) |
185 | m_vm = &globalObject->vm(); | |
186 | else | |
187 | ASSERT(m_vm == &globalObject->vm()); | |
9dae56ea A |
188 | globalObject->setDebugger(this); |
189 | m_globalObjects.add(globalObject); | |
190 | } | |
191 | ||
81345200 | 192 | void Debugger::detach(JSGlobalObject* globalObject, ReasonForDetach reason) |
9dae56ea | 193 | { |
81345200 A |
194 | // If we're detaching from the currently executing global object, manually tear down our |
195 | // stack, since we won't get further debugger callbacks to do so. Also, resume execution, | |
196 | // since there's no point in staying paused once a window closes. | |
197 | if (m_currentCallFrame && m_currentCallFrame->vmEntryGlobalObject() == globalObject) { | |
198 | m_currentCallFrame = 0; | |
199 | m_pauseOnCallFrame = 0; | |
200 | continueProgram(); | |
201 | } | |
202 | ||
9dae56ea A |
203 | ASSERT(m_globalObjects.contains(globalObject)); |
204 | m_globalObjects.remove(globalObject); | |
81345200 A |
205 | |
206 | // If the globalObject is destructing, then its CodeBlocks will also be | |
207 | // destructed. There is no need to do the debugger requests clean up, and | |
208 | // it is not safe to access those CodeBlocks at this time anyway. | |
209 | if (reason != GlobalObjectIsDestructing) | |
210 | clearDebuggerRequests(globalObject); | |
211 | ||
9dae56ea | 212 | globalObject->setDebugger(0); |
81345200 A |
213 | if (!m_globalObjects.size()) |
214 | m_vm = nullptr; | |
215 | } | |
216 | ||
ed1e77d3 A |
217 | bool Debugger::isAttached(JSGlobalObject* globalObject) |
218 | { | |
219 | return globalObject->debugger() == this; | |
220 | } | |
221 | ||
81345200 A |
222 | class Debugger::SetSteppingModeFunctor { |
223 | public: | |
224 | SetSteppingModeFunctor(Debugger* debugger, SteppingMode mode) | |
225 | : m_debugger(debugger) | |
226 | , m_mode(mode) | |
227 | { | |
228 | } | |
229 | ||
230 | bool operator()(CodeBlock* codeBlock) | |
231 | { | |
232 | if (m_debugger == codeBlock->globalObject()->debugger()) { | |
233 | if (m_mode == SteppingModeEnabled) | |
234 | codeBlock->setSteppingMode(CodeBlock::SteppingModeEnabled); | |
235 | else | |
236 | codeBlock->setSteppingMode(CodeBlock::SteppingModeDisabled); | |
237 | } | |
238 | return false; | |
239 | } | |
240 | ||
241 | private: | |
242 | Debugger* m_debugger; | |
243 | SteppingMode m_mode; | |
244 | }; | |
245 | ||
246 | void Debugger::setSteppingMode(SteppingMode mode) | |
247 | { | |
248 | if (mode == m_steppingMode || !m_vm) | |
249 | return; | |
250 | ||
ed1e77d3 | 251 | m_vm->prepareToDiscardCode(); |
81345200 A |
252 | |
253 | m_steppingMode = mode; | |
254 | SetSteppingModeFunctor functor(this, mode); | |
255 | m_vm->heap.forEachCodeBlock(functor); | |
256 | } | |
257 | ||
258 | void Debugger::registerCodeBlock(CodeBlock* codeBlock) | |
259 | { | |
260 | // FIXME: We should never have to jettison a code block (due to pending breakpoints | |
261 | // or stepping mode) that is being registered. operationOptimize() should have | |
262 | // prevented the optimizing of such code blocks in the first place. Find a way to | |
263 | // express this with greater clarity in the code. See <https://webkit.org/b131771>. | |
264 | applyBreakpoints(codeBlock); | |
265 | if (isStepping()) | |
266 | codeBlock->setSteppingMode(CodeBlock::SteppingModeEnabled); | |
267 | } | |
268 | ||
269 | void Debugger::toggleBreakpoint(CodeBlock* codeBlock, Breakpoint& breakpoint, BreakpointState enabledOrNot) | |
270 | { | |
271 | ScriptExecutable* executable = codeBlock->ownerExecutable(); | |
272 | ||
273 | SourceID sourceID = static_cast<SourceID>(executable->sourceID()); | |
274 | if (breakpoint.sourceID != sourceID) | |
275 | return; | |
276 | ||
277 | unsigned line = breakpoint.line; | |
278 | unsigned column = breakpoint.column; | |
279 | ||
ed1e77d3 | 280 | unsigned startLine = executable->firstLine(); |
81345200 A |
281 | unsigned startColumn = executable->startColumn(); |
282 | unsigned endLine = executable->lastLine(); | |
283 | unsigned endColumn = executable->endColumn(); | |
284 | ||
285 | // Inspector breakpoint line and column values are zero-based but the executable | |
286 | // and CodeBlock line and column values are one-based. | |
287 | line += 1; | |
288 | column = column ? column + 1 : Breakpoint::unspecifiedColumn; | |
289 | ||
290 | if (line < startLine || line > endLine) | |
291 | return; | |
292 | if (column != Breakpoint::unspecifiedColumn) { | |
293 | if (line == startLine && column < startColumn) | |
294 | return; | |
295 | if (line == endLine && column > endColumn) | |
296 | return; | |
297 | } | |
298 | if (!codeBlock->hasOpDebugForLineAndColumn(line, column)) | |
299 | return; | |
300 | ||
301 | if (enabledOrNot == BreakpointEnabled) | |
302 | codeBlock->addBreakpoint(1); | |
303 | else | |
304 | codeBlock->removeBreakpoint(1); | |
305 | } | |
306 | ||
307 | void Debugger::applyBreakpoints(CodeBlock* codeBlock) | |
308 | { | |
309 | BreakpointIDToBreakpointMap& breakpoints = m_breakpointIDToBreakpoint; | |
310 | for (auto it = breakpoints.begin(); it != breakpoints.end(); ++it) { | |
311 | Breakpoint& breakpoint = *it->value; | |
312 | toggleBreakpoint(codeBlock, breakpoint, BreakpointEnabled); | |
313 | } | |
314 | } | |
315 | ||
316 | class Debugger::ToggleBreakpointFunctor { | |
317 | public: | |
318 | ToggleBreakpointFunctor(Debugger* debugger, Breakpoint& breakpoint, BreakpointState enabledOrNot) | |
319 | : m_debugger(debugger) | |
320 | , m_breakpoint(breakpoint) | |
321 | , m_enabledOrNot(enabledOrNot) | |
322 | { | |
323 | } | |
324 | ||
325 | bool operator()(CodeBlock* codeBlock) | |
326 | { | |
327 | if (m_debugger == codeBlock->globalObject()->debugger()) | |
328 | m_debugger->toggleBreakpoint(codeBlock, m_breakpoint, m_enabledOrNot); | |
329 | return false; | |
330 | } | |
331 | ||
332 | private: | |
333 | Debugger* m_debugger; | |
334 | Breakpoint& m_breakpoint; | |
335 | BreakpointState m_enabledOrNot; | |
336 | }; | |
337 | ||
338 | void Debugger::toggleBreakpoint(Breakpoint& breakpoint, Debugger::BreakpointState enabledOrNot) | |
339 | { | |
340 | if (!m_vm) | |
341 | return; | |
342 | ToggleBreakpointFunctor functor(this, breakpoint, enabledOrNot); | |
343 | forEachCodeBlock(functor); | |
9dae56ea A |
344 | } |
345 | ||
93a37866 | 346 | void Debugger::recompileAllJSFunctions(VM* vm) |
f9bf01c6 A |
347 | { |
348 | // If JavaScript is running, it's not safe to recompile, since we'll end | |
349 | // up throwing away code that is live on the stack. | |
81345200 | 350 | if (vm->entryScope) { |
ed1e77d3 A |
351 | auto listener = [] (VM& vm, JSGlobalObject* globalObject) |
352 | { | |
353 | if (Debugger* debugger = globalObject->debugger()) | |
354 | debugger->recompileAllJSFunctions(&vm); | |
355 | }; | |
356 | ||
357 | vm->entryScope->setEntryScopeDidPopListener(this, listener); | |
f9bf01c6 | 358 | return; |
81345200 A |
359 | } |
360 | ||
ed1e77d3 | 361 | vm->prepareToDiscardCode(); |
f9bf01c6 | 362 | |
14957cd0 | 363 | Recompiler recompiler(this); |
81345200 A |
364 | HeapIterationScope iterationScope(vm->heap); |
365 | vm->heap.objectSpace().forEachLiveCell(iterationScope, recompiler); | |
366 | } | |
367 | ||
368 | BreakpointID Debugger::setBreakpoint(Breakpoint breakpoint, unsigned& actualLine, unsigned& actualColumn) | |
369 | { | |
370 | SourceID sourceID = breakpoint.sourceID; | |
371 | unsigned line = breakpoint.line; | |
372 | unsigned column = breakpoint.column; | |
373 | ||
374 | SourceIDToBreakpointsMap::iterator it = m_sourceIDToBreakpoints.find(sourceID); | |
375 | if (it == m_sourceIDToBreakpoints.end()) | |
376 | it = m_sourceIDToBreakpoints.set(sourceID, LineToBreakpointsMap()).iterator; | |
377 | LineToBreakpointsMap::iterator breaksIt = it->value.find(line); | |
378 | if (breaksIt == it->value.end()) | |
379 | breaksIt = it->value.set(line, adoptRef(new BreakpointsList)).iterator; | |
380 | ||
381 | BreakpointsList& breakpoints = *breaksIt->value; | |
382 | for (Breakpoint* current = breakpoints.head(); current; current = current->next()) { | |
383 | if (current->column == column) { | |
384 | // The breakpoint already exists. We're not allowed to create a new | |
385 | // breakpoint at this location. Rather than returning the breakpointID | |
386 | // of the pre-existing breakpoint, we need to return noBreakpointID | |
387 | // to indicate that we're not creating a new one. | |
388 | return noBreakpointID; | |
389 | } | |
390 | } | |
391 | ||
392 | BreakpointID id = ++m_topBreakpointID; | |
393 | RELEASE_ASSERT(id != noBreakpointID); | |
394 | ||
395 | breakpoint.id = id; | |
396 | actualLine = line; | |
397 | actualColumn = column; | |
398 | ||
399 | Breakpoint* newBreakpoint = new Breakpoint(breakpoint); | |
400 | breakpoints.append(newBreakpoint); | |
401 | m_breakpointIDToBreakpoint.set(id, newBreakpoint); | |
402 | ||
403 | toggleBreakpoint(breakpoint, BreakpointEnabled); | |
404 | ||
405 | return id; | |
406 | } | |
407 | ||
408 | void Debugger::removeBreakpoint(BreakpointID id) | |
409 | { | |
410 | ASSERT(id != noBreakpointID); | |
411 | ||
412 | BreakpointIDToBreakpointMap::iterator idIt = m_breakpointIDToBreakpoint.find(id); | |
413 | ASSERT(idIt != m_breakpointIDToBreakpoint.end()); | |
414 | Breakpoint* breakpoint = idIt->value; | |
415 | ||
416 | SourceID sourceID = breakpoint->sourceID; | |
417 | ASSERT(sourceID); | |
418 | SourceIDToBreakpointsMap::iterator it = m_sourceIDToBreakpoints.find(sourceID); | |
419 | ASSERT(it != m_sourceIDToBreakpoints.end()); | |
420 | LineToBreakpointsMap::iterator breaksIt = it->value.find(breakpoint->line); | |
421 | ASSERT(breaksIt != it->value.end()); | |
422 | ||
423 | toggleBreakpoint(*breakpoint, BreakpointDisabled); | |
424 | ||
425 | BreakpointsList& breakpoints = *breaksIt->value; | |
426 | #if !ASSERT_DISABLED | |
427 | bool found = false; | |
428 | for (Breakpoint* current = breakpoints.head(); current && !found; current = current->next()) { | |
429 | if (current->id == breakpoint->id) | |
430 | found = true; | |
431 | } | |
432 | ASSERT(found); | |
433 | #endif | |
434 | ||
435 | m_breakpointIDToBreakpoint.remove(idIt); | |
436 | breakpoints.remove(breakpoint); | |
437 | delete breakpoint; | |
438 | ||
439 | if (breakpoints.isEmpty()) { | |
440 | it->value.remove(breaksIt); | |
441 | if (it->value.isEmpty()) | |
442 | m_sourceIDToBreakpoints.remove(it); | |
443 | } | |
444 | } | |
445 | ||
446 | bool Debugger::hasBreakpoint(SourceID sourceID, const TextPosition& position, Breakpoint *hitBreakpoint) | |
447 | { | |
448 | if (!m_breakpointsActivated) | |
449 | return false; | |
450 | ||
451 | SourceIDToBreakpointsMap::const_iterator it = m_sourceIDToBreakpoints.find(sourceID); | |
452 | if (it == m_sourceIDToBreakpoints.end()) | |
453 | return false; | |
454 | ||
455 | unsigned line = position.m_line.zeroBasedInt(); | |
456 | unsigned column = position.m_column.zeroBasedInt(); | |
457 | ||
458 | LineToBreakpointsMap::const_iterator breaksIt = it->value.find(line); | |
459 | if (breaksIt == it->value.end()) | |
460 | return false; | |
461 | ||
462 | bool hit = false; | |
463 | const BreakpointsList& breakpoints = *breaksIt->value; | |
464 | Breakpoint* breakpoint; | |
465 | for (breakpoint = breakpoints.head(); breakpoint; breakpoint = breakpoint->next()) { | |
466 | unsigned breakLine = breakpoint->line; | |
467 | unsigned breakColumn = breakpoint->column; | |
468 | // Since frontend truncates the indent, the first statement in a line must match the breakpoint (line,0). | |
469 | ASSERT(this == m_currentCallFrame->codeBlock()->globalObject()->debugger()); | |
470 | if ((line != m_lastExecutedLine && line == breakLine && !breakColumn) | |
471 | || (line == breakLine && column == breakColumn)) { | |
472 | hit = true; | |
473 | break; | |
474 | } | |
475 | } | |
476 | if (!hit) | |
477 | return false; | |
478 | ||
479 | if (hitBreakpoint) | |
480 | *hitBreakpoint = *breakpoint; | |
481 | ||
482 | if (breakpoint->condition.isEmpty()) | |
483 | return true; | |
484 | ||
485 | // We cannot stop in the debugger while executing condition code, | |
486 | // so make it looks like the debugger is already paused. | |
487 | TemporaryPausedState pausedState(*this); | |
488 | ||
ed1e77d3 | 489 | NakedPtr<Exception> exception; |
81345200 A |
490 | DebuggerCallFrame* debuggerCallFrame = currentDebuggerCallFrame(); |
491 | JSValue result = debuggerCallFrame->evaluate(breakpoint->condition, exception); | |
492 | ||
493 | // We can lose the debugger while executing JavaScript. | |
494 | if (!m_currentCallFrame) | |
495 | return false; | |
496 | ||
497 | if (exception) { | |
498 | // An erroneous condition counts as "false". | |
499 | handleExceptionInBreakpointCondition(m_currentCallFrame, exception); | |
500 | return false; | |
501 | } | |
502 | ||
503 | return result.toBoolean(m_currentCallFrame); | |
504 | } | |
505 | ||
506 | class Debugger::ClearCodeBlockDebuggerRequestsFunctor { | |
507 | public: | |
508 | ClearCodeBlockDebuggerRequestsFunctor(Debugger* debugger) | |
509 | : m_debugger(debugger) | |
510 | { | |
511 | } | |
512 | ||
513 | bool operator()(CodeBlock* codeBlock) | |
514 | { | |
515 | if (codeBlock->hasDebuggerRequests() && m_debugger == codeBlock->globalObject()->debugger()) | |
516 | codeBlock->clearDebuggerRequests(); | |
517 | return false; | |
518 | } | |
519 | ||
520 | private: | |
521 | Debugger* m_debugger; | |
522 | }; | |
523 | ||
524 | void Debugger::clearBreakpoints() | |
525 | { | |
526 | m_topBreakpointID = noBreakpointID; | |
527 | m_breakpointIDToBreakpoint.clear(); | |
528 | m_sourceIDToBreakpoints.clear(); | |
529 | ||
530 | if (!m_vm) | |
531 | return; | |
532 | ClearCodeBlockDebuggerRequestsFunctor functor(this); | |
533 | forEachCodeBlock(functor); | |
f9bf01c6 A |
534 | } |
535 | ||
81345200 A |
536 | class Debugger::ClearDebuggerRequestsFunctor { |
537 | public: | |
538 | ClearDebuggerRequestsFunctor(JSGlobalObject* globalObject) | |
539 | : m_globalObject(globalObject) | |
540 | { | |
541 | } | |
542 | ||
543 | bool operator()(CodeBlock* codeBlock) | |
544 | { | |
545 | if (codeBlock->hasDebuggerRequests() && m_globalObject == codeBlock->globalObject()) | |
546 | codeBlock->clearDebuggerRequests(); | |
547 | return false; | |
548 | } | |
549 | ||
550 | private: | |
551 | JSGlobalObject* m_globalObject; | |
552 | }; | |
553 | ||
554 | void Debugger::clearDebuggerRequests(JSGlobalObject* globalObject) | |
555 | { | |
556 | ASSERT(m_vm); | |
557 | ClearDebuggerRequestsFunctor functor(globalObject); | |
558 | forEachCodeBlock(functor); | |
559 | } | |
560 | ||
561 | void Debugger::setBreakpointsActivated(bool activated) | |
9dae56ea | 562 | { |
81345200 A |
563 | m_breakpointsActivated = activated; |
564 | } | |
565 | ||
566 | void Debugger::setPauseOnExceptionsState(PauseOnExceptionsState pause) | |
567 | { | |
568 | m_pauseOnExceptionsState = pause; | |
569 | } | |
570 | ||
571 | void Debugger::setPauseOnNextStatement(bool pause) | |
572 | { | |
573 | m_pauseOnNextStatement = pause; | |
574 | if (pause) | |
575 | setSteppingMode(SteppingModeEnabled); | |
576 | } | |
9dae56ea | 577 | |
81345200 A |
578 | void Debugger::breakProgram() |
579 | { | |
580 | if (m_isPaused) | |
581 | return; | |
582 | ||
583 | m_pauseOnNextStatement = true; | |
584 | setSteppingMode(SteppingModeEnabled); | |
585 | m_currentCallFrame = m_vm->topCallFrame; | |
586 | ASSERT(m_currentCallFrame); | |
587 | pauseIfNeeded(m_currentCallFrame); | |
588 | } | |
589 | ||
590 | void Debugger::continueProgram() | |
591 | { | |
592 | if (!m_isPaused) | |
593 | return; | |
594 | ||
595 | m_pauseOnNextStatement = false; | |
596 | notifyDoneProcessingDebuggerEvents(); | |
597 | } | |
598 | ||
599 | void Debugger::stepIntoStatement() | |
600 | { | |
601 | if (!m_isPaused) | |
602 | return; | |
603 | ||
604 | m_pauseOnNextStatement = true; | |
605 | setSteppingMode(SteppingModeEnabled); | |
606 | notifyDoneProcessingDebuggerEvents(); | |
607 | } | |
608 | ||
609 | void Debugger::stepOverStatement() | |
610 | { | |
611 | if (!m_isPaused) | |
612 | return; | |
613 | ||
614 | m_pauseOnCallFrame = m_currentCallFrame; | |
615 | notifyDoneProcessingDebuggerEvents(); | |
616 | } | |
617 | ||
618 | void Debugger::stepOutOfFunction() | |
619 | { | |
620 | if (!m_isPaused) | |
621 | return; | |
622 | ||
ed1e77d3 A |
623 | VMEntryFrame* topVMEntryFrame = m_vm->topVMEntryFrame; |
624 | m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->callerFrame(topVMEntryFrame) : 0; | |
81345200 A |
625 | notifyDoneProcessingDebuggerEvents(); |
626 | } | |
627 | ||
628 | void Debugger::updateCallFrame(CallFrame* callFrame) | |
629 | { | |
630 | m_currentCallFrame = callFrame; | |
631 | SourceID sourceID = DebuggerCallFrame::sourceIDForCallFrame(callFrame); | |
632 | if (m_lastExecutedSourceID != sourceID) { | |
633 | m_lastExecutedLine = UINT_MAX; | |
634 | m_lastExecutedSourceID = sourceID; | |
14957cd0 | 635 | } |
81345200 A |
636 | } |
637 | ||
638 | void Debugger::updateCallFrameAndPauseIfNeeded(CallFrame* callFrame) | |
639 | { | |
640 | updateCallFrame(callFrame); | |
641 | pauseIfNeeded(callFrame); | |
642 | if (!isStepping()) | |
643 | m_currentCallFrame = 0; | |
644 | } | |
9dae56ea | 645 | |
81345200 A |
646 | void Debugger::pauseIfNeeded(CallFrame* callFrame) |
647 | { | |
648 | if (m_isPaused) | |
649 | return; | |
650 | ||
651 | JSGlobalObject* vmEntryGlobalObject = callFrame->vmEntryGlobalObject(); | |
652 | if (!needPauseHandling(vmEntryGlobalObject)) | |
653 | return; | |
654 | ||
655 | Breakpoint breakpoint; | |
656 | bool didHitBreakpoint = false; | |
657 | bool pauseNow = m_pauseOnNextStatement; | |
658 | pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame); | |
659 | ||
ed1e77d3 | 660 | DebuggerPausedScope debuggerPausedScope(*this); |
81345200 A |
661 | |
662 | intptr_t sourceID = DebuggerCallFrame::sourceIDForCallFrame(m_currentCallFrame); | |
663 | TextPosition position = DebuggerCallFrame::positionForCallFrame(m_currentCallFrame); | |
664 | pauseNow |= didHitBreakpoint = hasBreakpoint(sourceID, position, &breakpoint); | |
665 | m_lastExecutedLine = position.m_line.zeroBasedInt(); | |
666 | if (!pauseNow) | |
667 | return; | |
668 | ||
669 | // Make sure we are not going to pause again on breakpoint actions by | |
670 | // reseting the pause state before executing any breakpoint actions. | |
671 | TemporaryPausedState pausedState(*this); | |
672 | m_pauseOnCallFrame = 0; | |
673 | m_pauseOnNextStatement = false; | |
674 | ||
675 | if (didHitBreakpoint) { | |
ed1e77d3 | 676 | handleBreakpointHit(vmEntryGlobalObject, breakpoint); |
81345200 A |
677 | // Note that the actions can potentially stop the debugger, so we need to check that |
678 | // we still have a current call frame when we get back. | |
679 | if (breakpoint.autoContinue || !m_currentCallFrame) | |
680 | return; | |
ed1e77d3 | 681 | m_pausingBreakpointID = breakpoint.id; |
81345200 A |
682 | } |
683 | ||
ed1e77d3 A |
684 | { |
685 | PauseReasonDeclaration reason(*this, didHitBreakpoint ? PausedForBreakpoint : m_reasonForPause); | |
686 | handlePause(vmEntryGlobalObject, m_reasonForPause); | |
687 | } | |
688 | ||
689 | m_pausingBreakpointID = noBreakpointID; | |
81345200 A |
690 | |
691 | if (!m_pauseOnNextStatement && !m_pauseOnCallFrame) { | |
692 | setSteppingMode(SteppingModeDisabled); | |
693 | m_currentCallFrame = nullptr; | |
694 | } | |
695 | } | |
696 | ||
ed1e77d3 | 697 | void Debugger::exception(CallFrame* callFrame, JSValue exception, bool hasCatchHandler) |
81345200 A |
698 | { |
699 | if (m_isPaused) | |
700 | return; | |
701 | ||
702 | PauseReasonDeclaration reason(*this, PausedForException); | |
ed1e77d3 | 703 | if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasCatchHandler)) { |
81345200 A |
704 | m_pauseOnNextStatement = true; |
705 | setSteppingMode(SteppingModeEnabled); | |
706 | } | |
707 | ||
708 | m_hasHandlerForExceptionCallback = true; | |
709 | m_currentException = exception; | |
710 | updateCallFrameAndPauseIfNeeded(callFrame); | |
711 | m_currentException = JSValue(); | |
712 | m_hasHandlerForExceptionCallback = false; | |
713 | } | |
714 | ||
715 | void Debugger::atStatement(CallFrame* callFrame) | |
716 | { | |
717 | if (m_isPaused) | |
718 | return; | |
719 | ||
720 | PauseReasonDeclaration reason(*this, PausedAtStatement); | |
721 | updateCallFrameAndPauseIfNeeded(callFrame); | |
722 | } | |
723 | ||
724 | void Debugger::callEvent(CallFrame* callFrame) | |
725 | { | |
726 | if (m_isPaused) | |
727 | return; | |
728 | ||
729 | PauseReasonDeclaration reason(*this, PausedAfterCall); | |
730 | updateCallFrameAndPauseIfNeeded(callFrame); | |
731 | } | |
732 | ||
733 | void Debugger::returnEvent(CallFrame* callFrame) | |
734 | { | |
735 | if (m_isPaused) | |
736 | return; | |
737 | ||
738 | PauseReasonDeclaration reason(*this, PausedBeforeReturn); | |
739 | updateCallFrameAndPauseIfNeeded(callFrame); | |
740 | ||
741 | // detach may have been called during pauseIfNeeded | |
742 | if (!m_currentCallFrame) | |
743 | return; | |
744 | ||
745 | // Treat stepping over a return statement like stepping out. | |
ed1e77d3 A |
746 | if (m_currentCallFrame == m_pauseOnCallFrame) { |
747 | VMEntryFrame* topVMEntryFrame = m_vm->topVMEntryFrame; | |
748 | m_pauseOnCallFrame = m_currentCallFrame->callerFrame(topVMEntryFrame); | |
749 | } | |
81345200 | 750 | |
ed1e77d3 A |
751 | VMEntryFrame* topVMEntryFrame = m_vm->topVMEntryFrame; |
752 | m_currentCallFrame = m_currentCallFrame->callerFrame(topVMEntryFrame); | |
81345200 A |
753 | } |
754 | ||
755 | void Debugger::willExecuteProgram(CallFrame* callFrame) | |
756 | { | |
757 | if (m_isPaused) | |
758 | return; | |
759 | ||
760 | PauseReasonDeclaration reason(*this, PausedAtStartOfProgram); | |
761 | // FIXME: This check for whether we're debugging a worker thread is a workaround | |
762 | // for https://bugs.webkit.org/show_bug.cgi?id=102637. Remove it when we rework | |
763 | // the debugger implementation to not require callbacks. | |
764 | if (!m_isInWorkerThread) | |
765 | updateCallFrameAndPauseIfNeeded(callFrame); | |
766 | else if (isStepping()) | |
767 | updateCallFrame(callFrame); | |
768 | } | |
769 | ||
770 | void Debugger::didExecuteProgram(CallFrame* callFrame) | |
771 | { | |
772 | if (m_isPaused) | |
773 | return; | |
774 | ||
775 | PauseReasonDeclaration reason(*this, PausedAtEndOfProgram); | |
776 | updateCallFrameAndPauseIfNeeded(callFrame); | |
777 | ||
778 | // Treat stepping over the end of a program like stepping out. | |
779 | if (!m_currentCallFrame) | |
780 | return; | |
781 | if (m_currentCallFrame == m_pauseOnCallFrame) { | |
ed1e77d3 A |
782 | VMEntryFrame* topVMEntryFrame = m_vm->topVMEntryFrame; |
783 | m_pauseOnCallFrame = m_currentCallFrame->callerFrame(topVMEntryFrame); | |
81345200 A |
784 | if (!m_currentCallFrame) |
785 | return; | |
14957cd0 | 786 | } |
ed1e77d3 A |
787 | VMEntryFrame* topVMEntryFrame = m_vm->topVMEntryFrame; |
788 | m_currentCallFrame = m_currentCallFrame->callerFrame(topVMEntryFrame); | |
81345200 A |
789 | } |
790 | ||
791 | void Debugger::didReachBreakpoint(CallFrame* callFrame) | |
792 | { | |
793 | if (m_isPaused) | |
794 | return; | |
795 | ||
ed1e77d3 | 796 | PauseReasonDeclaration reason(*this, PausedForDebuggerStatement); |
81345200 A |
797 | m_pauseOnNextStatement = true; |
798 | setSteppingMode(SteppingModeEnabled); | |
799 | updateCallFrameAndPauseIfNeeded(callFrame); | |
800 | } | |
801 | ||
802 | DebuggerCallFrame* Debugger::currentDebuggerCallFrame() const | |
803 | { | |
804 | ASSERT(m_currentDebuggerCallFrame); | |
805 | return m_currentDebuggerCallFrame.get(); | |
9dae56ea A |
806 | } |
807 | ||
808 | } // namespace JSC |