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 dispatchFunctionToListeners([&] (ScriptDebugListener& listener) {
96 listener.breakpointActionLog(debuggerCallFrame.globalObject(), breakpointAction.data);
97 });
98 break;
99
100 case ScriptBreakpointActionTypeEvaluate: {
101 NakedPtr<Exception> exception;
102 JSObject* scopeExtensionObject = nullptr;
103 debuggerCallFrame.evaluateWithScopeExtension(breakpointAction.data, scopeExtensionObject, exception);
104 if (exception)
105 reportException(debuggerCallFrame.globalObject(), exception);
106 break;
107 }
108
109 case ScriptBreakpointActionTypeSound:
110 dispatchFunctionToListeners([&] (ScriptDebugListener& listener) {
111 listener.breakpointActionSound(breakpointAction.identifier);
112 });
113 break;
114
115 case ScriptBreakpointActionTypeProbe: {
116 NakedPtr<Exception> exception;
117 JSObject* scopeExtensionObject = nullptr;
118 JSValue result = debuggerCallFrame.evaluateWithScopeExtension(breakpointAction.data, scopeExtensionObject, exception);
119 JSC::JSGlobalObject* globalObject = debuggerCallFrame.globalObject();
120 if (exception)
121 reportException(globalObject, exception);
122
123 dispatchFunctionToListeners([&] (ScriptDebugListener& listener) {
124 listener.breakpointActionProbe(globalObject, breakpointAction, m_currentProbeBatchId, m_nextProbeSampleId++, exception ? exception->value() : result);
125 });
126 break;
127 }
128
129 default:
130 ASSERT_NOT_REACHED();
131 }
132
133 return true;
134}
135
136void ScriptDebugServer::sourceParsed(JSGlobalObject* globalObject, SourceProvider* sourceProvider, int errorLine, const String& errorMessage)
137{
138 // Preemptively check whether we can dispatch so that we don't do any unnecessary allocations.
139 if (!canDispatchFunctionToListeners())
140 return;
141
142 if (errorLine != -1) {
143 auto url = sourceProvider->url();
144 auto data = sourceProvider->source().toString();
145 auto firstLine = sourceProvider->startPosition().m_line.oneBasedInt();
146 dispatchFunctionToListeners([&] (ScriptDebugListener& listener) {
147 listener.failedToParseSource(url, data, firstLine, errorLine, errorMessage);
148 });
149 return;
150 }
151
152 JSC::SourceID sourceID = sourceProvider->asID();
153
154 // FIXME: <https://webkit.org/b/162773> Web Inspector: Simplify ScriptDebugListener::Script to use SourceProvider
155 ScriptDebugListener::Script script;
156 script.sourceProvider = sourceProvider;
157 script.url = sourceProvider->url();
158 script.source = sourceProvider->source().toString();
159 script.startLine = sourceProvider->startPosition().m_line.zeroBasedInt();
160 script.startColumn = sourceProvider->startPosition().m_column.zeroBasedInt();
161 script.isContentScript = isContentScript(globalObject);
162 script.sourceURL = sourceProvider->sourceURLDirective();
163 script.sourceMappingURL = sourceProvider->sourceMappingURLDirective();
164
165 int sourceLength = script.source.length();
166 int lineCount = 1;
167 int lastLineStart = 0;
168 for (int i = 0; i < sourceLength; ++i) {
169 if (script.source[i] == '\n') {
170 lineCount += 1;
171 lastLineStart = i + 1;
172 }
173 }
174
175 script.endLine = script.startLine + lineCount - 1;
176 if (lineCount == 1)
177 script.endColumn = script.startColumn + sourceLength;
178 else
179 script.endColumn = sourceLength - lastLineStart;
180
181 dispatchFunctionToListeners([&] (ScriptDebugListener& listener) {
182 listener.didParseSource(sourceID, script);
183 });
184}
185
186void ScriptDebugServer::willRunMicrotask()
187{
188 dispatchFunctionToListeners([&] (ScriptDebugListener& listener) {
189 listener.willRunMicrotask();
190 });
191}
192
193void ScriptDebugServer::didRunMicrotask()
194{
195 dispatchFunctionToListeners([&] (ScriptDebugListener& listener) {
196 listener.didRunMicrotask();
197 });
198}
199
200bool ScriptDebugServer::canDispatchFunctionToListeners() const
201{
202 if (m_callingListeners)
203 return false;
204 if (m_listeners.isEmpty())
205 return false;
206 return true;
207}
208
209void ScriptDebugServer::dispatchFunctionToListeners(Function<void(ScriptDebugListener&)> callback)
210{
211 if (!canDispatchFunctionToListeners())
212 return;
213
214 SetForScope<bool> change(m_callingListeners, true);
215
216 for (auto* listener : copyToVector(m_listeners))
217 callback(*listener);
218}
219
220void ScriptDebugServer::notifyDoneProcessingDebuggerEvents()
221{
222 m_doneProcessingDebuggerEvents = true;
223}
224
225void ScriptDebugServer::handleBreakpointHit(JSC::JSGlobalObject* globalObject, const JSC::Breakpoint& breakpoint)
226{
227 ASSERT(isAttached(globalObject));
228
229 m_currentProbeBatchId++;
230
231 auto entry = m_breakpointIDToActions.find(breakpoint.id);
232 if (entry != m_breakpointIDToActions.end()) {
233 BreakpointActions actions = entry->value;
234 for (size_t i = 0; i < actions.size(); ++i) {
235 if (!evaluateBreakpointAction(actions[i]))
236 return;
237 if (!isAttached(globalObject))
238 return;
239 }
240 }
241}
242
243void ScriptDebugServer::handleExceptionInBreakpointCondition(JSC::JSGlobalObject* globalObject, JSC::Exception* exception) const
244{
245 reportException(globalObject, exception);
246}
247
248void ScriptDebugServer::handlePause(JSGlobalObject* globalObject, Debugger::ReasonForPause)
249{
250 dispatchFunctionToListeners([&] (ScriptDebugListener& listener) {
251 ASSERT(isPaused());
252 auto& debuggerCallFrame = currentDebuggerCallFrame();
253 auto* globalObject = debuggerCallFrame.scope()->globalObject();
254 auto jsCallFrame = toJS(globalObject, globalObject, JavaScriptCallFrame::create(debuggerCallFrame).ptr());
255 listener.didPause(globalObject, jsCallFrame, exceptionOrCaughtValue(globalObject));
256 });
257
258 didPause(globalObject);
259
260 m_doneProcessingDebuggerEvents = false;
261 runEventLoopWhilePaused();
262
263 didContinue(globalObject);
264
265 dispatchFunctionToListeners([&] (ScriptDebugListener& listener) {
266 listener.didContinue();
267 });
268}
269
270void ScriptDebugServer::addListener(ScriptDebugListener* listener)
271{
272 ASSERT(listener);
273
274 bool wasEmpty = m_listeners.isEmpty();
275 m_listeners.add(listener);
276
277 // First listener. Attach the debugger.
278 if (wasEmpty)
279 attachDebugger();
280}
281
282void ScriptDebugServer::removeListener(ScriptDebugListener* listener, bool isBeingDestroyed)
283{
284 ASSERT(listener);
285
286 m_listeners.remove(listener);
287
288 // Last listener. Detach the debugger.
289 if (m_listeners.isEmpty())
290 detachDebugger(isBeingDestroyed);
291}
292
293JSC::JSValue ScriptDebugServer::exceptionOrCaughtValue(JSC::JSGlobalObject* globalObject)
294{
295 if (reasonForPause() == PausedForException)
296 return currentException();
297
298 for (RefPtr<DebuggerCallFrame> frame = &currentDebuggerCallFrame(); frame; frame = frame->callerFrame()) {
299 DebuggerScope& scope = *frame->scope();
300 if (scope.isCatchScope())
301 return scope.caughtValue(globalObject);
302 }
303
304 return { };
305}
306
307} // namespace Inspector
308