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