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