Hook up ShadowChicken to the debugger to show tail deleted frames
[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 "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
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 JSC::BreakpointID ScriptDebugServer::setBreakpoint(JSC::SourceID sourceID, const ScriptBreakpoint& scriptBreakpoint, unsigned* actualLineNumber, unsigned* actualColumnNumber)
59 {
60     if (!sourceID)
61         return JSC::noBreakpointID;
62
63     JSC::Breakpoint breakpoint(sourceID, scriptBreakpoint.lineNumber, scriptBreakpoint.columnNumber, scriptBreakpoint.condition, scriptBreakpoint.autoContinue, scriptBreakpoint.ignoreCount);
64     JSC::BreakpointID id = Debugger::setBreakpoint(breakpoint, *actualLineNumber, *actualColumnNumber);
65     if (id != JSC::noBreakpointID && !scriptBreakpoint.actions.isEmpty()) {
66 #ifndef NDEBUG
67         BreakpointIDToActionsMap::iterator it = m_breakpointIDToActions.find(id);
68         ASSERT(it == m_breakpointIDToActions.end());
69 #endif
70         const BreakpointActions& actions = scriptBreakpoint.actions;
71         m_breakpointIDToActions.set(id, actions);
72     }
73     return id;
74 }
75
76 void ScriptDebugServer::removeBreakpoint(JSC::BreakpointID id)
77 {
78     ASSERT(id != JSC::noBreakpointID);
79     BreakpointIDToActionsMap::iterator it = m_breakpointIDToActions.find(id);
80     if (it != m_breakpointIDToActions.end())
81         m_breakpointIDToActions.remove(it);
82
83     Debugger::removeBreakpoint(id);
84 }
85
86 bool ScriptDebugServer::evaluateBreakpointAction(const ScriptBreakpointAction& breakpointAction)
87 {
88     DebuggerCallFrame* debuggerCallFrame = currentDebuggerCallFrame();
89
90     switch (breakpointAction.type) {
91     case ScriptBreakpointActionTypeLog: {
92         dispatchBreakpointActionLog(debuggerCallFrame->globalExec(), breakpointAction.data);
93         break;
94     }
95     case ScriptBreakpointActionTypeEvaluate: {
96         NakedPtr<Exception> exception;
97         JSObject* scopeExtensionObject = nullptr;
98         debuggerCallFrame->evaluateWithScopeExtension(breakpointAction.data, scopeExtensionObject, exception);
99         if (exception)
100             reportException(debuggerCallFrame->globalExec(), exception);
101         break;
102     }
103     case ScriptBreakpointActionTypeSound:
104         dispatchBreakpointActionSound(debuggerCallFrame->globalExec(), breakpointAction.identifier);
105         break;
106     case ScriptBreakpointActionTypeProbe: {
107         NakedPtr<Exception> exception;
108         JSObject* scopeExtensionObject = nullptr;
109         JSValue result = debuggerCallFrame->evaluateWithScopeExtension(breakpointAction.data, scopeExtensionObject, exception);
110         JSC::ExecState* exec = debuggerCallFrame->globalExec();
111         if (exception)
112             reportException(exec, exception);
113         
114         dispatchBreakpointActionProbe(exec, breakpointAction, exception ? exception->value() : result);
115         break;
116     }
117     default:
118         ASSERT_NOT_REACHED();
119     }
120
121     return true;
122 }
123
124 void ScriptDebugServer::clearBreakpoints()
125 {
126     Debugger::clearBreakpoints();
127     m_breakpointIDToActions.clear();
128 }
129
130 void ScriptDebugServer::dispatchDidPause(ScriptDebugListener* listener)
131 {
132     ASSERT(isPaused());
133     DebuggerCallFrame* debuggerCallFrame = currentDebuggerCallFrame();
134     JSGlobalObject* globalObject = debuggerCallFrame->scope()->globalObject();
135     JSC::ExecState& state = *globalObject->globalExec();
136     JSValue jsCallFrame = toJS(&state, globalObject, JavaScriptCallFrame::create(debuggerCallFrame).ptr());
137     listener->didPause(state, jsCallFrame, exceptionOrCaughtValue(&state));
138 }
139
140 void ScriptDebugServer::dispatchBreakpointActionLog(ExecState* exec, const String& message)
141 {
142     if (m_callingListeners)
143         return;
144
145     if (m_listeners.isEmpty())
146         return;
147
148     TemporaryChange<bool> change(m_callingListeners, true);
149
150     Vector<ScriptDebugListener*> listenersCopy;
151     copyToVector(m_listeners, listenersCopy);
152     for (auto* listener : listenersCopy)
153         listener->breakpointActionLog(*exec, message);
154 }
155
156 void ScriptDebugServer::dispatchBreakpointActionSound(ExecState*, int breakpointActionIdentifier)
157 {
158     if (m_callingListeners)
159         return;
160
161     if (m_listeners.isEmpty())
162         return;
163
164     TemporaryChange<bool> change(m_callingListeners, true);
165
166     Vector<ScriptDebugListener*> listenersCopy;
167     copyToVector(m_listeners, listenersCopy);
168     for (auto* listener : listenersCopy)
169         listener->breakpointActionSound(breakpointActionIdentifier);
170 }
171
172 void ScriptDebugServer::dispatchBreakpointActionProbe(ExecState* exec, const ScriptBreakpointAction& action, JSC::JSValue sampleValue)
173 {
174     if (m_callingListeners)
175         return;
176
177     if (m_listeners.isEmpty())
178         return;
179
180     TemporaryChange<bool> change(m_callingListeners, true);
181
182     unsigned sampleId = m_nextProbeSampleId++;
183
184     Vector<ScriptDebugListener*> listenersCopy;
185     copyToVector(m_listeners, listenersCopy);
186     for (auto* listener : listenersCopy)
187         listener->breakpointActionProbe(*exec, action, m_currentProbeBatchId, sampleId, sampleValue);
188 }
189
190 void ScriptDebugServer::dispatchDidContinue(ScriptDebugListener* listener)
191 {
192     listener->didContinue();
193 }
194
195 void ScriptDebugServer::dispatchDidParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, bool isContentScript)
196 {
197     JSC::SourceID sourceID = sourceProvider->asID();
198
199     ScriptDebugListener::Script script;
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     TemporaryChange<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     TemporaryChange<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     BreakpointIDToActionsMap::iterator it = m_breakpointIDToActions.find(breakpoint.id);
292     if (it != m_breakpointIDToActions.end()) {
293         BreakpointActions actions = it->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 const BreakpointActions& ScriptDebugServer::getActionsForBreakpoint(JSC::BreakpointID breakpointID)
321 {
322     ASSERT(breakpointID != JSC::noBreakpointID);
323
324     if (m_breakpointIDToActions.contains(breakpointID))
325         return m_breakpointIDToActions.find(breakpointID)->value;
326     
327     static NeverDestroyed<BreakpointActions> emptyActionVector = BreakpointActions();
328     return emptyActionVector;
329 }
330
331 void ScriptDebugServer::addListener(ScriptDebugListener* listener)
332 {
333     ASSERT(listener);
334
335     bool wasEmpty = m_listeners.isEmpty();
336     m_listeners.add(listener);
337
338     // First listener. Attach the debugger.
339     if (wasEmpty)
340         attachDebugger();
341 }
342
343 void ScriptDebugServer::removeListener(ScriptDebugListener* listener, bool isBeingDestroyed)
344 {
345     ASSERT(listener);
346
347     m_listeners.remove(listener);
348
349     // Last listener. Detach the debugger.
350     if (m_listeners.isEmpty())
351         detachDebugger(isBeingDestroyed);
352 }
353
354 JSC::JSValue ScriptDebugServer::exceptionOrCaughtValue(JSC::ExecState* state)
355 {
356     if (reasonForPause() == PausedForException)
357         return currentException();
358
359     for (RefPtr<DebuggerCallFrame> frame = currentDebuggerCallFrame(); frame; frame = frame->callerFrame()) {
360         DebuggerScope& scope = *frame->scope();
361         if (scope.isCatchScope())
362             return scope.caughtValue(state);
363     }
364
365     return { };
366 }
367
368 } // namespace Inspector