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 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 63 ScriptDebugServer::~ScriptDebugServer() 64 { 65 deleteAllValues(m_pageListenersMap); 66 } 67 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 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 103 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 131 void ScriptDebugServer::clearBreakpoints() 132 { 133 m_sourceIdToBreakpoints.clear(); 134 } 135 136 void ScriptDebugServer::setBreakpointsActivated(bool activated) 137 { 138 m_breakpointsActivated = activated; 139 } 140 141 void ScriptDebugServer::setPauseOnExceptionsState(PauseOnExceptionsState pause) 142 { 143 m_pauseOnExceptionsState = pause; 144 } 145 146 void ScriptDebugServer::setPauseOnNextStatement(bool pause) 147 { 148 m_pauseOnNextStatement = pause; 149 } 150 151 void ScriptDebugServer::breakProgram() 152 { 153 // FIXME(WK43332): implement this. 154 } 155 156 void ScriptDebugServer::continueProgram() 157 { 158 if (!m_paused) 159 return; 160 161 m_pauseOnNextStatement = false; 162 m_doneProcessingDebuggerEvents = true; 163 } 164 165 void ScriptDebugServer::stepIntoStatement() 166 { 167 if (!m_paused) 168 return; 169 170 m_pauseOnNextStatement = true; 171 m_doneProcessingDebuggerEvents = true; 172 } 173 174 void ScriptDebugServer::stepOverStatement() 175 { 176 if (!m_paused) 177 return; 178 179 m_pauseOnCallFrame = m_currentCallFrame.get(); 180 m_doneProcessingDebuggerEvents = true; 181 } 182 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 192 bool ScriptDebugServer::editScriptSource(const String&, const String&, String*) 193 { 194 // FIXME(40300): implement this. 195 return false; 196 } 197 198 JavaScriptCallFrame* ScriptDebugServer::currentCallFrame() 199 { 200 if (!m_paused) 201 return 0; 202 return m_currentCallFrame.get(); 203 } 204 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 212 void ScriptDebugServer::dispatchDidContinue(ScriptDebugListener* listener) 213 { 214 listener->didContinue(); 215 } 216 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 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 243 static bool isContentScript(ExecState* exec) 244 { 245 return currentWorld(exec) != mainThreadNormalWorld(); 246 } 247 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 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 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 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 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 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 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 357 void ScriptDebugServer::callEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber) 358 { 359 if (!m_paused) 360 createCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber); 361 } 362 363 void ScriptDebugServer::atStatement(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber) 364 { 365 if (!m_paused) 366 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber); 367 } 368 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 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 397 void ScriptDebugServer::willExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber) 398 { 399 if (!m_paused) 400 createCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber); 401 } 402 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 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 425 void ScriptDebugServer::recompileAllJSFunctionsSoon() 426 { 427 m_recompileTimer.startOneShot(0); 428 } 429 430 } // namespace WebCore 431 432 #endif // ENABLE(JAVASCRIPT_DEBUGGER) 433