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