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