• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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