1 /*
2 * Copyright (C) 2008, 2009 Apple Inc. All rights reserved.
3 * Copyright (C) 2010-2011 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 * its contributors may be used to endorse or promote products derived
16 * from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include "config.h"
31 #include "ScriptDebugServer.h"
32
33 #if ENABLE(JAVASCRIPT_DEBUGGER)
34
35 #include "EventLoop.h"
36 #include "Frame.h"
37 #include "JavaScriptCallFrame.h"
38 #include "ScriptBreakpoint.h"
39 #include "ScriptController.h"
40 #include "ScriptDebugListener.h"
41 #include <debugger/DebuggerCallFrame.h>
42 #include <parser/SourceProvider.h>
43 #include <runtime/JSLock.h>
44 #include <wtf/text/StringConcatenate.h>
45 #include <wtf/MainThread.h>
46
47 using namespace JSC;
48
49 namespace WebCore {
50
ScriptDebugServer()51 ScriptDebugServer::ScriptDebugServer()
52 : m_callingListeners(false)
53 , m_pauseOnExceptionsState(DontPauseOnExceptions)
54 , m_pauseOnNextStatement(false)
55 , m_paused(false)
56 , m_doneProcessingDebuggerEvents(true)
57 , m_breakpointsActivated(true)
58 , m_pauseOnCallFrame(0)
59 , m_recompileTimer(this, &ScriptDebugServer::recompileAllJSFunctions)
60 {
61 }
62
~ScriptDebugServer()63 ScriptDebugServer::~ScriptDebugServer()
64 {
65 deleteAllValues(m_pageListenersMap);
66 }
67
setBreakpoint(const String & sourceID,const ScriptBreakpoint & scriptBreakpoint,int * actualLineNumber,int * actualColumnNumber)68 String ScriptDebugServer::setBreakpoint(const String& sourceID, const ScriptBreakpoint& scriptBreakpoint, int* actualLineNumber, int* actualColumnNumber)
69 {
70 intptr_t sourceIDValue = sourceID.toIntPtr();
71 if (!sourceIDValue)
72 return "";
73 SourceIdToBreakpointsMap::iterator it = m_sourceIdToBreakpoints.find(sourceIDValue);
74 if (it == m_sourceIdToBreakpoints.end())
75 it = m_sourceIdToBreakpoints.set(sourceIDValue, LineToBreakpointMap()).first;
76 if (it->second.contains(scriptBreakpoint.lineNumber + 1))
77 return "";
78 it->second.set(scriptBreakpoint.lineNumber + 1, scriptBreakpoint);
79 *actualLineNumber = scriptBreakpoint.lineNumber;
80 // FIXME(WK53003): implement setting breakpoints by line:column.
81 *actualColumnNumber = 0;
82 return makeString(sourceID, ":", String::number(scriptBreakpoint.lineNumber));
83 }
84
removeBreakpoint(const String & breakpointId)85 void ScriptDebugServer::removeBreakpoint(const String& breakpointId)
86 {
87 Vector<String> tokens;
88 breakpointId.split(":", tokens);
89 if (tokens.size() != 2)
90 return;
91 bool success;
92 intptr_t sourceIDValue = tokens[0].toIntPtr(&success);
93 if (!success)
94 return;
95 unsigned lineNumber = tokens[1].toUInt(&success);
96 if (!success)
97 return;
98 SourceIdToBreakpointsMap::iterator it = m_sourceIdToBreakpoints.find(sourceIDValue);
99 if (it != m_sourceIdToBreakpoints.end())
100 it->second.remove(lineNumber + 1);
101 }
102
hasBreakpoint(intptr_t sourceID,const TextPosition0 & position) const103 bool ScriptDebugServer::hasBreakpoint(intptr_t sourceID, const TextPosition0& position) const
104 {
105 if (!m_breakpointsActivated)
106 return false;
107
108 SourceIdToBreakpointsMap::const_iterator it = m_sourceIdToBreakpoints.find(sourceID);
109 if (it == m_sourceIdToBreakpoints.end())
110 return false;
111 int lineNumber = position.m_line.convertAsOneBasedInt();
112 if (lineNumber <= 0)
113 return false;
114 LineToBreakpointMap::const_iterator breakIt = it->second.find(lineNumber);
115 if (breakIt == it->second.end())
116 return false;
117
118 // An empty condition counts as no condition which is equivalent to "true".
119 if (breakIt->second.condition.isEmpty())
120 return true;
121
122 JSValue exception;
123 JSValue result = m_currentCallFrame->evaluate(stringToUString(breakIt->second.condition), exception);
124 if (exception) {
125 // An erroneous condition counts as "false".
126 return false;
127 }
128 return result.toBoolean(m_currentCallFrame->scopeChain()->globalObject->globalExec());
129 }
130
clearBreakpoints()131 void ScriptDebugServer::clearBreakpoints()
132 {
133 m_sourceIdToBreakpoints.clear();
134 }
135
setBreakpointsActivated(bool activated)136 void ScriptDebugServer::setBreakpointsActivated(bool activated)
137 {
138 m_breakpointsActivated = activated;
139 }
140
setPauseOnExceptionsState(PauseOnExceptionsState pause)141 void ScriptDebugServer::setPauseOnExceptionsState(PauseOnExceptionsState pause)
142 {
143 m_pauseOnExceptionsState = pause;
144 }
145
setPauseOnNextStatement(bool pause)146 void ScriptDebugServer::setPauseOnNextStatement(bool pause)
147 {
148 m_pauseOnNextStatement = pause;
149 }
150
breakProgram()151 void ScriptDebugServer::breakProgram()
152 {
153 // FIXME(WK43332): implement this.
154 }
155
continueProgram()156 void ScriptDebugServer::continueProgram()
157 {
158 if (!m_paused)
159 return;
160
161 m_pauseOnNextStatement = false;
162 m_doneProcessingDebuggerEvents = true;
163 }
164
stepIntoStatement()165 void ScriptDebugServer::stepIntoStatement()
166 {
167 if (!m_paused)
168 return;
169
170 m_pauseOnNextStatement = true;
171 m_doneProcessingDebuggerEvents = true;
172 }
173
stepOverStatement()174 void ScriptDebugServer::stepOverStatement()
175 {
176 if (!m_paused)
177 return;
178
179 m_pauseOnCallFrame = m_currentCallFrame.get();
180 m_doneProcessingDebuggerEvents = true;
181 }
182
stepOutOfFunction()183 void ScriptDebugServer::stepOutOfFunction()
184 {
185 if (!m_paused)
186 return;
187
188 m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->caller() : 0;
189 m_doneProcessingDebuggerEvents = true;
190 }
191
editScriptSource(const String &,const String &,String *)192 bool ScriptDebugServer::editScriptSource(const String&, const String&, String*)
193 {
194 // FIXME(40300): implement this.
195 return false;
196 }
197
currentCallFrame()198 JavaScriptCallFrame* ScriptDebugServer::currentCallFrame()
199 {
200 if (!m_paused)
201 return 0;
202 return m_currentCallFrame.get();
203 }
204
dispatchDidPause(ScriptDebugListener * listener)205 void ScriptDebugServer::dispatchDidPause(ScriptDebugListener* listener)
206 {
207 ASSERT(m_paused);
208 ScriptState* state = m_currentCallFrame->scopeChain()->globalObject->globalExec();
209 listener->didPause(state);
210 }
211
dispatchDidContinue(ScriptDebugListener * listener)212 void ScriptDebugServer::dispatchDidContinue(ScriptDebugListener* listener)
213 {
214 listener->didContinue();
215 }
216
dispatchDidParseSource(const ListenerSet & listeners,SourceProvider * sourceProvider,bool isContentScript)217 void ScriptDebugServer::dispatchDidParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, bool isContentScript)
218 {
219 String sourceID = ustringToString(JSC::UString::number(sourceProvider->asID()));
220 String url = ustringToString(sourceProvider->url());
221 String data = ustringToString(JSC::UString(sourceProvider->data(), sourceProvider->length()));
222 int lineOffset = sourceProvider->startPosition().m_line.convertAsZeroBasedInt();
223 int columnOffset = sourceProvider->startPosition().m_column.convertAsZeroBasedInt();
224
225 Vector<ScriptDebugListener*> copy;
226 copyToVector(listeners, copy);
227 for (size_t i = 0; i < copy.size(); ++i)
228 copy[i]->didParseSource(sourceID, url, data, lineOffset, columnOffset, isContentScript);
229 }
230
dispatchFailedToParseSource(const ListenerSet & listeners,SourceProvider * sourceProvider,int errorLine,const String & errorMessage)231 void ScriptDebugServer::dispatchFailedToParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, int errorLine, const String& errorMessage)
232 {
233 String url = ustringToString(sourceProvider->url());
234 String data = ustringToString(JSC::UString(sourceProvider->data(), sourceProvider->length()));
235 int firstLine = sourceProvider->startPosition().m_line.oneBasedInt();
236
237 Vector<ScriptDebugListener*> copy;
238 copyToVector(listeners, copy);
239 for (size_t i = 0; i < copy.size(); ++i)
240 copy[i]->failedToParseSource(url, data, firstLine, errorLine, errorMessage);
241 }
242
isContentScript(ExecState * exec)243 static bool isContentScript(ExecState* exec)
244 {
245 return currentWorld(exec) != mainThreadNormalWorld();
246 }
247
detach(JSGlobalObject * globalObject)248 void ScriptDebugServer::detach(JSGlobalObject* globalObject)
249 {
250 // If we're detaching from the currently executing global object, manually tear down our
251 // stack, since we won't get further debugger callbacks to do so. Also, resume execution,
252 // since there's no point in staying paused once a window closes.
253 if (m_currentCallFrame && m_currentCallFrame->dynamicGlobalObject() == globalObject) {
254 m_currentCallFrame = 0;
255 m_pauseOnCallFrame = 0;
256 continueProgram();
257 }
258 Debugger::detach(globalObject);
259 }
260
sourceParsed(ExecState * exec,SourceProvider * sourceProvider,int errorLine,const UString & errorMessage)261 void ScriptDebugServer::sourceParsed(ExecState* exec, SourceProvider* sourceProvider, int errorLine, const UString& errorMessage)
262 {
263 if (m_callingListeners)
264 return;
265
266 ListenerSet* listeners = getListenersForGlobalObject(exec->lexicalGlobalObject());
267 if (!listeners)
268 return;
269 ASSERT(!listeners->isEmpty());
270
271 m_callingListeners = true;
272
273 bool isError = errorLine != -1;
274 if (isError)
275 dispatchFailedToParseSource(*listeners, sourceProvider, errorLine, ustringToString(errorMessage));
276 else
277 dispatchDidParseSource(*listeners, sourceProvider, isContentScript(exec));
278
279 m_callingListeners = false;
280 }
281
dispatchFunctionToListeners(const ListenerSet & listeners,JavaScriptExecutionCallback callback)282 void ScriptDebugServer::dispatchFunctionToListeners(const ListenerSet& listeners, JavaScriptExecutionCallback callback)
283 {
284 Vector<ScriptDebugListener*> copy;
285 copyToVector(listeners, copy);
286 for (size_t i = 0; i < copy.size(); ++i)
287 (this->*callback)(copy[i]);
288 }
289
dispatchFunctionToListeners(JavaScriptExecutionCallback callback,JSGlobalObject * globalObject)290 void ScriptDebugServer::dispatchFunctionToListeners(JavaScriptExecutionCallback callback, JSGlobalObject* globalObject)
291 {
292 if (m_callingListeners)
293 return;
294
295 m_callingListeners = true;
296
297 if (ListenerSet* listeners = getListenersForGlobalObject(globalObject)) {
298 ASSERT(!listeners->isEmpty());
299 dispatchFunctionToListeners(*listeners, callback);
300 }
301
302 m_callingListeners = false;
303 }
304
createCallFrameAndPauseIfNeeded(const DebuggerCallFrame & debuggerCallFrame,intptr_t sourceID,int lineNumber)305 void ScriptDebugServer::createCallFrameAndPauseIfNeeded(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
306 {
307 TextPosition0 textPosition(WTF::OneBasedNumber::fromOneBasedInt(lineNumber).convertToZeroBased(), WTF::ZeroBasedNumber::base());
308 m_currentCallFrame = JavaScriptCallFrame::create(debuggerCallFrame, m_currentCallFrame, sourceID, textPosition);
309 pauseIfNeeded(debuggerCallFrame.dynamicGlobalObject());
310 }
311
updateCallFrameAndPauseIfNeeded(const DebuggerCallFrame & debuggerCallFrame,intptr_t sourceID,int lineNumber)312 void ScriptDebugServer::updateCallFrameAndPauseIfNeeded(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
313 {
314 ASSERT(m_currentCallFrame);
315 if (!m_currentCallFrame)
316 return;
317
318 TextPosition0 textPosition(WTF::OneBasedNumber::fromOneBasedInt(lineNumber).convertToZeroBased(), WTF::ZeroBasedNumber::base());
319 m_currentCallFrame->update(debuggerCallFrame, sourceID, textPosition);
320 pauseIfNeeded(debuggerCallFrame.dynamicGlobalObject());
321 }
322
pauseIfNeeded(JSGlobalObject * dynamicGlobalObject)323 void ScriptDebugServer::pauseIfNeeded(JSGlobalObject* dynamicGlobalObject)
324 {
325 if (m_paused)
326 return;
327
328 if (!getListenersForGlobalObject(dynamicGlobalObject))
329 return;
330
331 bool pauseNow = m_pauseOnNextStatement;
332 pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame);
333 pauseNow |= hasBreakpoint(m_currentCallFrame->sourceID(), m_currentCallFrame->position());
334 if (!pauseNow)
335 return;
336
337 m_pauseOnCallFrame = 0;
338 m_pauseOnNextStatement = false;
339 m_paused = true;
340
341 dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidPause, dynamicGlobalObject);
342 didPause(dynamicGlobalObject);
343
344 TimerBase::fireTimersInNestedEventLoop();
345
346 EventLoop loop;
347 m_doneProcessingDebuggerEvents = false;
348 while (!m_doneProcessingDebuggerEvents && !loop.ended())
349 loop.cycle();
350
351 didContinue(dynamicGlobalObject);
352 dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidContinue, dynamicGlobalObject);
353
354 m_paused = false;
355 }
356
callEvent(const DebuggerCallFrame & debuggerCallFrame,intptr_t sourceID,int lineNumber)357 void ScriptDebugServer::callEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
358 {
359 if (!m_paused)
360 createCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
361 }
362
atStatement(const DebuggerCallFrame & debuggerCallFrame,intptr_t sourceID,int lineNumber)363 void ScriptDebugServer::atStatement(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
364 {
365 if (!m_paused)
366 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
367 }
368
returnEvent(const DebuggerCallFrame & debuggerCallFrame,intptr_t sourceID,int lineNumber)369 void ScriptDebugServer::returnEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
370 {
371 if (m_paused)
372 return;
373
374 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
375
376 // detach may have been called during pauseIfNeeded
377 if (!m_currentCallFrame)
378 return;
379
380 // Treat stepping over a return statement like stepping out.
381 if (m_currentCallFrame == m_pauseOnCallFrame)
382 m_pauseOnCallFrame = m_currentCallFrame->caller();
383 m_currentCallFrame = m_currentCallFrame->caller();
384 }
385
exception(const DebuggerCallFrame & debuggerCallFrame,intptr_t sourceID,int lineNumber,bool hasHandler)386 void ScriptDebugServer::exception(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber, bool hasHandler)
387 {
388 if (m_paused)
389 return;
390
391 if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasHandler))
392 m_pauseOnNextStatement = true;
393
394 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
395 }
396
willExecuteProgram(const DebuggerCallFrame & debuggerCallFrame,intptr_t sourceID,int lineNumber)397 void ScriptDebugServer::willExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
398 {
399 if (!m_paused)
400 createCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
401 }
402
didExecuteProgram(const DebuggerCallFrame & debuggerCallFrame,intptr_t sourceID,int lineNumber)403 void ScriptDebugServer::didExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
404 {
405 if (m_paused)
406 return;
407
408 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
409
410 // Treat stepping over the end of a program like stepping out.
411 if (m_currentCallFrame == m_pauseOnCallFrame)
412 m_pauseOnCallFrame = m_currentCallFrame->caller();
413 m_currentCallFrame = m_currentCallFrame->caller();
414 }
415
didReachBreakpoint(const DebuggerCallFrame & debuggerCallFrame,intptr_t sourceID,int lineNumber)416 void ScriptDebugServer::didReachBreakpoint(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
417 {
418 if (m_paused)
419 return;
420
421 m_pauseOnNextStatement = true;
422 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
423 }
424
recompileAllJSFunctionsSoon()425 void ScriptDebugServer::recompileAllJSFunctionsSoon()
426 {
427 m_recompileTimer.startOneShot(0);
428 }
429
430 } // namespace WebCore
431
432 #endif // ENABLE(JAVASCRIPT_DEBUGGER)
433