[JSC] Shrink UnlinkedFunctionExecutable
[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 "SourceProvider.h"
41 #include <wtf/NeverDestroyed.h>
42 #include <wtf/SetForScope.h>
43
44 using namespace JSC;
45
46 namespace Inspector {
47
48 ScriptDebugServer::ScriptDebugServer(VM& vm)
49     : Debugger(vm)
50 {
51 }
52
53 ScriptDebugServer::~ScriptDebugServer()
54 {
55 }
56
57 void 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
65 void ScriptDebugServer::removeBreakpointActions(BreakpointID id)
66 {
67     ASSERT(id != noBreakpointID);
68
69     m_breakpointIDToActions.remove(id);
70 }
71
72 const 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
84 void ScriptDebugServer::clearBreakpointActions()
85 {
86     m_breakpointIDToActions.clear();
87 }
88
89 bool 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
127 void 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
137 void 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
151 void 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
165 void 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
181 void ScriptDebugServer::dispatchDidContinue(ScriptDebugListener* listener)
182 {
183     listener->didContinue();
184 }
185
186 void 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
221 void 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
231 void 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
248 void 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
261 void ScriptDebugServer::dispatchFunctionToListeners(const ListenerSet& listeners, JavaScriptExecutionCallback callback)
262 {
263     for (auto* listener : copyToVector(listeners))
264         (this->*callback)(listener);
265 }
266
267 void ScriptDebugServer::notifyDoneProcessingDebuggerEvents()
268 {
269     m_doneProcessingDebuggerEvents = true;
270 }
271
272 void 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
290 void ScriptDebugServer::handleExceptionInBreakpointCondition(JSC::ExecState* exec, JSC::Exception* exception) const
291 {
292     reportException(exec, exception);
293 }
294
295 void 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
307 void 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
319 void 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
330 JSC::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