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#include "DebuggerCallFrame.h"
35#include "DebuggerScope.h"
36#include "Exception.h"
37#include "JSCInlines.h"
38#include "JSJavaScriptCallFrame.h"
39#include "JavaScriptCallFrame.h"
40#include "SourceProvider.h"
41#include <wtf/NeverDestroyed.h>
42#include <wtf/SetForScope.h>
43
44using namespace JSC;
45
46namespace Inspector {
47
48ScriptDebugServer::ScriptDebugServer(VM& vm)
49 : Debugger(vm)
50{
51}
52
53ScriptDebugServer::~ScriptDebugServer()
54{
55}
56
57void ScriptDebugServer::setBreakpointActions(BreakpointID id, const ScriptBreakpoint& scriptBreakpoint)
58{
59 ASSERT(id != noBreakpointID);
60 ASSERT(!m_breakpointIDToActions.contains(id));
61
62 m_breakpointIDToActions.set(id, scriptBreakpoint.actions);
63}
64
65void ScriptDebugServer::removeBreakpointActions(BreakpointID id)
66{
67 ASSERT(id != noBreakpointID);
68
69 m_breakpointIDToActions.remove(id);
70}
71
72const BreakpointActions& ScriptDebugServer::getActionsForBreakpoint(BreakpointID id)
73{
74 ASSERT(id != noBreakpointID);
75
76 auto entry = m_breakpointIDToActions.find(id);
77 if (entry != m_breakpointIDToActions.end())
78 return entry->value;
79
80 static NeverDestroyed<BreakpointActions> emptyActionVector = BreakpointActions();
81 return emptyActionVector;
82}
83
84void ScriptDebugServer::clearBreakpointActions()
85{
86 m_breakpointIDToActions.clear();
87}
88
89bool ScriptDebugServer::evaluateBreakpointAction(const ScriptBreakpointAction& breakpointAction)
90{
91 DebuggerCallFrame& debuggerCallFrame = currentDebuggerCallFrame();
92
93 switch (breakpointAction.type) {
94 case ScriptBreakpointActionTypeLog: {
95 dispatchBreakpointActionLog(debuggerCallFrame.globalExec(), breakpointAction.data);
96 break;
97 }
98 case ScriptBreakpointActionTypeEvaluate: {
99 NakedPtr<Exception> exception;
100 JSObject* scopeExtensionObject = nullptr;
101 debuggerCallFrame.evaluateWithScopeExtension(breakpointAction.data, scopeExtensionObject, exception);
102 if (exception)
103 reportException(debuggerCallFrame.globalExec(), exception);
104 break;
105 }
106 case ScriptBreakpointActionTypeSound:
107 dispatchBreakpointActionSound(debuggerCallFrame.globalExec(), breakpointAction.identifier);
108 break;
109 case ScriptBreakpointActionTypeProbe: {
110 NakedPtr<Exception> exception;
111 JSObject* scopeExtensionObject = nullptr;
112 JSValue result = debuggerCallFrame.evaluateWithScopeExtension(breakpointAction.data, scopeExtensionObject, exception);
113 JSC::ExecState* exec = debuggerCallFrame.globalExec();
114 if (exception)
115 reportException(exec, exception);
116
117 dispatchBreakpointActionProbe(exec, breakpointAction, exception ? exception->value() : result);
118 break;
119 }
120 default:
121 ASSERT_NOT_REACHED();
122 }
123
124 return true;
125}
126
127void ScriptDebugServer::dispatchDidPause(ScriptDebugListener* listener)
128{
129 ASSERT(isPaused());
130 DebuggerCallFrame& debuggerCallFrame = currentDebuggerCallFrame();
131 JSGlobalObject* globalObject = debuggerCallFrame.scope()->globalObject();
132 JSC::ExecState& state = *globalObject->globalExec();
133 JSValue jsCallFrame = toJS(&state, globalObject, JavaScriptCallFrame::create(debuggerCallFrame).ptr());
134 listener->didPause(state, jsCallFrame, exceptionOrCaughtValue(&state));
135}
136
137void ScriptDebugServer::dispatchBreakpointActionLog(ExecState* exec, const String& message)
138{
139 if (m_callingListeners)
140 return;
141
142 if (m_listeners.isEmpty())
143 return;
144
145 SetForScope<bool> change(m_callingListeners, true);
146
147 for (auto* listener : copyToVector(m_listeners))
148 listener->breakpointActionLog(*exec, message);
149}
150
151void ScriptDebugServer::dispatchBreakpointActionSound(ExecState*, int breakpointActionIdentifier)
152{
153 if (m_callingListeners)
154 return;
155
156 if (m_listeners.isEmpty())
157 return;
158
159 SetForScope<bool> change(m_callingListeners, true);
160
161 for (auto* listener : copyToVector(m_listeners))
162 listener->breakpointActionSound(breakpointActionIdentifier);
163}
164
165void ScriptDebugServer::dispatchBreakpointActionProbe(ExecState* exec, const ScriptBreakpointAction& action, JSC::JSValue sampleValue)
166{
167 if (m_callingListeners)
168 return;
169
170 if (m_listeners.isEmpty())
171 return;
172
173 SetForScope<bool> change(m_callingListeners, true);
174
175 unsigned sampleId = m_nextProbeSampleId++;
176
177 for (auto* listener : copyToVector(m_listeners))
178 listener->breakpointActionProbe(*exec, action, m_currentProbeBatchId, sampleId, sampleValue);
179}
180
181void ScriptDebugServer::dispatchDidContinue(ScriptDebugListener* listener)
182{
183 listener->didContinue();
184}
185
186void ScriptDebugServer::dispatchDidParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, bool isContentScript)
187{
188 JSC::SourceID sourceID = sourceProvider->asID();
189
190 // FIXME: <https://webkit.org/b/162773> Web Inspector: Simplify ScriptDebugListener::Script to use SourceProvider
191 ScriptDebugListener::Script script;
192 script.sourceProvider = sourceProvider;
193 script.url = sourceProvider->url();
194 script.source = sourceProvider->source().toString();
195 script.startLine = sourceProvider->startPosition().m_line.zeroBasedInt();
196 script.startColumn = sourceProvider->startPosition().m_column.zeroBasedInt();
197 script.isContentScript = isContentScript;
198 script.sourceURL = sourceProvider->sourceURLDirective();
199 script.sourceMappingURL = sourceProvider->sourceMappingURLDirective();
200
201 int sourceLength = script.source.length();
202 int lineCount = 1;
203 int lastLineStart = 0;
204 for (int i = 0; i < sourceLength; ++i) {
205 if (script.source[i] == '\n') {
206 lineCount += 1;
207 lastLineStart = i + 1;
208 }
209 }
210
211 script.endLine = script.startLine + lineCount - 1;
212 if (lineCount == 1)
213 script.endColumn = script.startColumn + sourceLength;
214 else
215 script.endColumn = sourceLength - lastLineStart;
216
217 for (auto* listener : copyToVector(listeners))
218 listener->didParseSource(sourceID, script);
219}
220
221void ScriptDebugServer::dispatchFailedToParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, int errorLine, const String& errorMessage)
222{
223 String url = sourceProvider->url();
224 String data = sourceProvider->source().toString();
225 int firstLine = sourceProvider->startPosition().m_line.oneBasedInt();
226
227 for (auto* listener : copyToVector(listeners))
228 listener->failedToParseSource(url, data, firstLine, errorLine, errorMessage);
229}
230
231void ScriptDebugServer::sourceParsed(ExecState* exec, SourceProvider* sourceProvider, int errorLine, const String& errorMessage)
232{
233 if (m_callingListeners)
234 return;
235
236 if (m_listeners.isEmpty())
237 return;
238
239 SetForScope<bool> change(m_callingListeners, true);
240
241 bool isError = errorLine != -1;
242 if (isError)
243 dispatchFailedToParseSource(m_listeners, sourceProvider, errorLine, errorMessage);
244 else
245 dispatchDidParseSource(m_listeners, sourceProvider, isContentScript(exec));
246}
247
248void ScriptDebugServer::dispatchFunctionToListeners(JavaScriptExecutionCallback callback)
249{
250 if (m_callingListeners)
251 return;
252
253 if (m_listeners.isEmpty())
254 return;
255
256 SetForScope<bool> change(m_callingListeners, true);
257
258 dispatchFunctionToListeners(m_listeners, callback);
259}
260
261void ScriptDebugServer::dispatchFunctionToListeners(const ListenerSet& listeners, JavaScriptExecutionCallback callback)
262{
263 for (auto* listener : copyToVector(listeners))
264 (this->*callback)(listener);
265}
266
267void ScriptDebugServer::notifyDoneProcessingDebuggerEvents()
268{
269 m_doneProcessingDebuggerEvents = true;
270}
271
272void ScriptDebugServer::handleBreakpointHit(JSC::JSGlobalObject* globalObject, const JSC::Breakpoint& breakpoint)
273{
274 ASSERT(isAttached(globalObject));
275
276 m_currentProbeBatchId++;
277
278 auto entry = m_breakpointIDToActions.find(breakpoint.id);
279 if (entry != m_breakpointIDToActions.end()) {
280 BreakpointActions actions = entry->value;
281 for (size_t i = 0; i < actions.size(); ++i) {
282 if (!evaluateBreakpointAction(actions[i]))
283 return;
284 if (!isAttached(globalObject))
285 return;
286 }
287 }
288}
289
290void ScriptDebugServer::handleExceptionInBreakpointCondition(JSC::ExecState* exec, JSC::Exception* exception) const
291{
292 reportException(exec, exception);
293}
294
295void ScriptDebugServer::handlePause(JSGlobalObject* vmEntryGlobalObject, Debugger::ReasonForPause)
296{
297 dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidPause);
298 didPause(vmEntryGlobalObject);
299
300 m_doneProcessingDebuggerEvents = false;
301 runEventLoopWhilePaused();
302
303 didContinue(vmEntryGlobalObject);
304 dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidContinue);
305}
306
307void ScriptDebugServer::addListener(ScriptDebugListener* listener)
308{
309 ASSERT(listener);
310
311 bool wasEmpty = m_listeners.isEmpty();
312 m_listeners.add(listener);
313
314 // First listener. Attach the debugger.
315 if (wasEmpty)
316 attachDebugger();
317}
318
319void ScriptDebugServer::removeListener(ScriptDebugListener* listener, bool isBeingDestroyed)
320{
321 ASSERT(listener);
322
323 m_listeners.remove(listener);
324
325 // Last listener. Detach the debugger.
326 if (m_listeners.isEmpty())
327 detachDebugger(isBeingDestroyed);
328}
329
330JSC::JSValue ScriptDebugServer::exceptionOrCaughtValue(JSC::ExecState* state)
331{
332 if (reasonForPause() == PausedForException)
333 return currentException();
334
335 for (RefPtr<DebuggerCallFrame> frame = &currentDebuggerCallFrame(); frame; frame = frame->callerFrame()) {
336 DebuggerScope& scope = *frame->scope();
337 if (scope.isCatchScope())
338 return scope.caughtValue(state);
339 }
340
341 return { };
342}
343
344} // namespace Inspector
345