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