1/* 2 * Copyright (C) 2008, 2009, 2013, 2014 Apple Inc. All rights reserved. 3 * Copyright (C) 2010-2011 Google Inc. All rights reserved. 4 * Copyright (C) 2013 University of Washington. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of Apple Inc. ("Apple") nor the names of 16 * its contributors may be used to endorse or promote products derived 17 * from this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31#include "config.h" 32#include "ScriptDebugServer.h" 33 34#if ENABLE(INSPECTOR) 35 36#include "DebuggerCallFrame.h" 37#include "JSJavaScriptCallFrame.h" 38#include "JSLock.h" 39#include "JavaScriptCallFrame.h" 40#include "ScriptValue.h" 41#include "SourceProvider.h" 42#include <wtf/NeverDestroyed.h> 43#include <wtf/TemporaryChange.h> 44#include <wtf/text/WTFString.h> 45 46using namespace JSC; 47 48namespace Inspector { 49 50ScriptDebugServer::ScriptDebugServer(bool isInWorkerThread) 51 : Debugger(isInWorkerThread) 52 , m_doneProcessingDebuggerEvents(true) 53 , m_callingListeners(false) 54{ 55} 56 57ScriptDebugServer::~ScriptDebugServer() 58{ 59} 60 61JSC::BreakpointID ScriptDebugServer::setBreakpoint(JSC::SourceID sourceID, const ScriptBreakpoint& scriptBreakpoint, unsigned* actualLineNumber, unsigned* actualColumnNumber) 62{ 63 if (!sourceID) 64 return JSC::noBreakpointID; 65 66 JSC::Breakpoint breakpoint(sourceID, scriptBreakpoint.lineNumber, scriptBreakpoint.columnNumber, scriptBreakpoint.condition, scriptBreakpoint.autoContinue); 67 JSC::BreakpointID id = Debugger::setBreakpoint(breakpoint, *actualLineNumber, *actualColumnNumber); 68 if (id != JSC::noBreakpointID && !scriptBreakpoint.actions.isEmpty()) { 69#ifndef NDEBUG 70 BreakpointIDToActionsMap::iterator it = m_breakpointIDToActions.find(id); 71 ASSERT(it == m_breakpointIDToActions.end()); 72#endif 73 const BreakpointActions& actions = scriptBreakpoint.actions; 74 m_breakpointIDToActions.set(id, actions); 75 } 76 return id; 77} 78 79void ScriptDebugServer::removeBreakpoint(JSC::BreakpointID id) 80{ 81 ASSERT(id != JSC::noBreakpointID); 82 BreakpointIDToActionsMap::iterator it = m_breakpointIDToActions.find(id); 83 if (it != m_breakpointIDToActions.end()) 84 m_breakpointIDToActions.remove(it); 85 86 Debugger::removeBreakpoint(id); 87} 88 89bool ScriptDebugServer::evaluateBreakpointAction(const ScriptBreakpointAction& breakpointAction) 90{ 91 DebuggerCallFrame* debuggerCallFrame = currentDebuggerCallFrame(); 92 93 switch (breakpointAction.type) { 94 case ScriptBreakpointActionTypeLog: { 95 dispatchBreakpointActionLog(debuggerCallFrame->exec(), breakpointAction.data); 96 break; 97 } 98 case ScriptBreakpointActionTypeEvaluate: { 99 JSValue exception; 100 debuggerCallFrame->evaluate(breakpointAction.data, exception); 101 if (exception) 102 reportException(debuggerCallFrame->exec(), exception); 103 break; 104 } 105 case ScriptBreakpointActionTypeSound: 106 dispatchBreakpointActionSound(debuggerCallFrame->exec(), breakpointAction.identifier); 107 break; 108 case ScriptBreakpointActionTypeProbe: { 109 JSValue exception; 110 JSValue result = debuggerCallFrame->evaluate(breakpointAction.data, exception); 111 if (exception) 112 reportException(debuggerCallFrame->exec(), exception); 113 114 JSC::ExecState* state = debuggerCallFrame->scope()->globalObject()->globalExec(); 115 Deprecated::ScriptValue wrappedResult = Deprecated::ScriptValue(state->vm(), exception ? exception : result); 116 dispatchBreakpointActionProbe(state, breakpointAction, wrappedResult); 117 break; 118 } 119 default: 120 ASSERT_NOT_REACHED(); 121 } 122 123 return true; 124} 125 126void ScriptDebugServer::clearBreakpoints() 127{ 128 Debugger::clearBreakpoints(); 129 m_breakpointIDToActions.clear(); 130} 131 132void ScriptDebugServer::dispatchDidPause(ScriptDebugListener* listener) 133{ 134 ASSERT(isPaused()); 135 DebuggerCallFrame* debuggerCallFrame = currentDebuggerCallFrame(); 136 JSGlobalObject* globalObject = debuggerCallFrame->scope()->globalObject(); 137 JSC::ExecState* state = globalObject->globalExec(); 138 RefPtr<JavaScriptCallFrame> javaScriptCallFrame = JavaScriptCallFrame::create(debuggerCallFrame); 139 JSValue jsCallFrame = toJS(state, globalObject, javaScriptCallFrame.get()); 140 listener->didPause(state, Deprecated::ScriptValue(state->vm(), jsCallFrame), Deprecated::ScriptValue()); 141} 142 143void ScriptDebugServer::dispatchBreakpointActionLog(ExecState* exec, const String& message) 144{ 145 if (m_callingListeners) 146 return; 147 148 ListenerSet* listeners = getListenersForGlobalObject(exec->lexicalGlobalObject()); 149 if (!listeners) 150 return; 151 ASSERT(!listeners->isEmpty()); 152 153 TemporaryChange<bool> change(m_callingListeners, true); 154 155 Vector<ScriptDebugListener*> listenersCopy; 156 copyToVector(*listeners, listenersCopy); 157 for (auto* listener : listenersCopy) 158 listener->breakpointActionLog(exec, message); 159} 160 161void ScriptDebugServer::dispatchBreakpointActionSound(ExecState* exec, int breakpointActionIdentifier) 162{ 163 if (m_callingListeners) 164 return; 165 166 ListenerSet* listeners = getListenersForGlobalObject(exec->lexicalGlobalObject()); 167 if (!listeners) 168 return; 169 ASSERT(!listeners->isEmpty()); 170 171 TemporaryChange<bool> change(m_callingListeners, true); 172 173 Vector<ScriptDebugListener*> listenersCopy; 174 copyToVector(*listeners, listenersCopy); 175 for (auto* listener : listenersCopy) 176 listener->breakpointActionSound(breakpointActionIdentifier); 177} 178 179void ScriptDebugServer::dispatchBreakpointActionProbe(ExecState* exec, const ScriptBreakpointAction& action, const Deprecated::ScriptValue& sample) 180{ 181 if (m_callingListeners) 182 return; 183 184 ListenerSet* listeners = getListenersForGlobalObject(exec->lexicalGlobalObject()); 185 if (!listeners) 186 return; 187 ASSERT(!listeners->isEmpty()); 188 189 TemporaryChange<bool> change(m_callingListeners, true); 190 191 Vector<ScriptDebugListener*> listenersCopy; 192 copyToVector(*listeners, listenersCopy); 193 for (auto* listener : listenersCopy) 194 listener->breakpointActionProbe(exec, action, m_hitCount, sample); 195} 196 197void ScriptDebugServer::dispatchDidContinue(ScriptDebugListener* listener) 198{ 199 listener->didContinue(); 200} 201 202void ScriptDebugServer::dispatchDidParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, bool isContentScript) 203{ 204 JSC::SourceID sourceID = sourceProvider->asID(); 205 206 ScriptDebugListener::Script script; 207 script.url = sourceProvider->url(); 208 script.source = sourceProvider->source(); 209 script.startLine = sourceProvider->startPosition().m_line.zeroBasedInt(); 210 script.startColumn = sourceProvider->startPosition().m_column.zeroBasedInt(); 211 script.isContentScript = isContentScript; 212 213 int sourceLength = script.source.length(); 214 int lineCount = 1; 215 int lastLineStart = 0; 216 for (int i = 0; i < sourceLength; ++i) { 217 if (script.source[i] == '\n') { 218 lineCount += 1; 219 lastLineStart = i + 1; 220 } 221 } 222 223 script.endLine = script.startLine + lineCount - 1; 224 if (lineCount == 1) 225 script.endColumn = script.startColumn + sourceLength; 226 else 227 script.endColumn = sourceLength - lastLineStart; 228 229 Vector<ScriptDebugListener*> copy; 230 copyToVector(listeners, copy); 231 for (size_t i = 0; i < copy.size(); ++i) 232 copy[i]->didParseSource(sourceID, script); 233} 234 235void ScriptDebugServer::dispatchFailedToParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, int errorLine, const String& errorMessage) 236{ 237 String url = sourceProvider->url(); 238 const String& data = sourceProvider->source(); 239 int firstLine = sourceProvider->startPosition().m_line.oneBasedInt(); 240 241 Vector<ScriptDebugListener*> copy; 242 copyToVector(listeners, copy); 243 for (size_t i = 0; i < copy.size(); ++i) 244 copy[i]->failedToParseSource(url, data, firstLine, errorLine, errorMessage); 245} 246 247void ScriptDebugServer::sourceParsed(ExecState* exec, SourceProvider* sourceProvider, int errorLine, const String& errorMessage) 248{ 249 if (m_callingListeners) 250 return; 251 252 ListenerSet* listeners = getListenersForGlobalObject(exec->lexicalGlobalObject()); 253 if (!listeners) 254 return; 255 ASSERT(!listeners->isEmpty()); 256 257 TemporaryChange<bool> change(m_callingListeners, true); 258 259 bool isError = errorLine != -1; 260 if (isError) 261 dispatchFailedToParseSource(*listeners, sourceProvider, errorLine, errorMessage); 262 else 263 dispatchDidParseSource(*listeners, sourceProvider, isContentScript(exec)); 264} 265 266void ScriptDebugServer::dispatchFunctionToListeners(const ListenerSet& listeners, JavaScriptExecutionCallback callback) 267{ 268 Vector<ScriptDebugListener*> copy; 269 copyToVector(listeners, copy); 270 for (size_t i = 0; i < copy.size(); ++i) 271 (this->*callback)(copy[i]); 272} 273 274void ScriptDebugServer::dispatchFunctionToListeners(JavaScriptExecutionCallback callback, JSGlobalObject* globalObject) 275{ 276 if (m_callingListeners) 277 return; 278 279 TemporaryChange<bool> change(m_callingListeners, true); 280 281 if (ListenerSet* listeners = getListenersForGlobalObject(globalObject)) { 282 if (!listeners->isEmpty()) 283 dispatchFunctionToListeners(*listeners, callback); 284 } 285} 286 287void ScriptDebugServer::notifyDoneProcessingDebuggerEvents() 288{ 289 m_doneProcessingDebuggerEvents = true; 290} 291 292bool ScriptDebugServer::needPauseHandling(JSGlobalObject* globalObject) 293{ 294 return !!getListenersForGlobalObject(globalObject); 295} 296 297void ScriptDebugServer::handleBreakpointHit(const JSC::Breakpoint& breakpoint) 298{ 299 m_hitCount++; 300 BreakpointIDToActionsMap::iterator it = m_breakpointIDToActions.find(breakpoint.id); 301 if (it != m_breakpointIDToActions.end()) { 302 BreakpointActions& actions = it->value; 303 for (size_t i = 0; i < actions.size(); ++i) { 304 if (!evaluateBreakpointAction(actions[i])) 305 return; 306 } 307 } 308} 309 310void ScriptDebugServer::handleExceptionInBreakpointCondition(JSC::ExecState* exec, JSC::JSValue exception) const 311{ 312 reportException(exec, exception); 313} 314 315void ScriptDebugServer::handlePause(Debugger::ReasonForPause, JSGlobalObject* vmEntryGlobalObject) 316{ 317 dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidPause, vmEntryGlobalObject); 318 didPause(vmEntryGlobalObject); 319 320 m_doneProcessingDebuggerEvents = false; 321 runEventLoopWhilePaused(); 322 323 didContinue(vmEntryGlobalObject); 324 dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidContinue, vmEntryGlobalObject); 325} 326 327const BreakpointActions& ScriptDebugServer::getActionsForBreakpoint(JSC::BreakpointID breakpointID) 328{ 329 ASSERT(breakpointID != JSC::noBreakpointID); 330 331 if (m_breakpointIDToActions.contains(breakpointID)) 332 return m_breakpointIDToActions.find(breakpointID)->value; 333 334 static NeverDestroyed<BreakpointActions> emptyActionVector = BreakpointActions(); 335 return emptyActionVector; 336} 337 338} // namespace Inspector 339 340#endif // ENABLE(INSPECTOR) 341