1/****************************************************************************
2**
3** Copyright (C) 2015 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the QtScript module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "config.h"
41#include "qscriptengineagent.h"
42#include "qscriptengineagent_p.h"
43#include "qscriptengine.h"
44#include "qscriptengine_p.h"
45
46#include "CodeBlock.h"
47#include "Instruction.h"
48
49QT_BEGIN_NAMESPACE
50
51/*!
52 \since 4.4
53 \class QScriptEngineAgent
54 \inmodule QtScript
55
56 \brief The QScriptEngineAgent class provides an interface to report events pertaining to QScriptEngine execution.
57
58 \ingroup script
59
60
61 The QScriptEngineAgent class is the basis of tools that monitor and/or control the execution of a
62 QScriptEngine, such as debuggers and profilers.
63
64 To process script loading and unloading events, reimplement the
65 scriptLoad() and scriptUnload() functions. scriptLoad() is called
66 after the input to QScriptEngine::evaluate() has been parsed, right
67 before the given script is executed. The engine assigns each
68 script an ID, which is available as one of the arguments to
69 scriptLoad(); subsequently, other event handlers can use the ID to
70 identify a particular script. One common usage of scriptLoad() is
71 to retain the script text, filename and base line number (the
72 original input to QScriptEngine::evaluate()), so that other event
73 handlers can e.g. map a line number to the corresponding line of
74 text.
75
76 scriptUnload() is called when the QScriptEngine has no further use
77 for a script; the QScriptEngineAgent may at this point safely
78 discard any resources associated with the script (such as the
79 script text). Note that after scriptUnload() has been called, the
80 QScriptEngine may reuse the relevant script ID for new scripts
81 (i.e. as argument to a subsequent call to scriptLoad()).
82
83 Evaluating the following script will result in scriptUnload()
84 being called immediately after evaluation has completed:
85
86 \snippet code/src_script_qscriptengineagent.cpp 0
87
88 Evaluating the following script will \b{not} result in a call to
89 scriptUnload() when evaluation has completed:
90
91 \snippet code/src_script_qscriptengineagent.cpp 1
92
93 The script isn't unloaded because it defines a function (\c{cube})
94 that remains in the script environment after evaluation has
95 completed. If a subsequent script removed the \c{cube} function
96 (e.g. by setting it to \c{null}), scriptUnload() would be called
97 when the function is garbage collected. In general terms, a script
98 isn't unloaded until the engine has determined that none of its
99 contents is referenced.
100
101 To process script function calls and returns, reimplement the
102 functionEntry() and functionExit() functions. functionEntry() is
103 called when a script function is about to be executed;
104 functionExit() is called when a script function is about to return,
105 either normally or due to an exception.
106
107 To process individual script statements, reimplement
108 positionChange(). positionChange() is called each time the engine is
109 about to execute a new statement of a script, and thus offers the
110 finest level of script monitoring.
111
112 To process exceptions, reimplement exceptionThrow() and
113 exceptionCatch(). exceptionThrow() is called when a script exception
114 is thrown, before it has been handled. exceptionCatch() is called
115 when an exception handler is present, and execution is about to be
116 resumed at the handler code.
117
118 \sa QScriptEngine::setAgent(), QScriptContextInfo
119*/
120
121/*!
122 \enum QScriptEngineAgent::Extension
123
124 This enum specifies the possible extensions to a QScriptEngineAgent.
125
126 \value DebuggerInvocationRequest The agent handles \c{debugger} script statements.
127
128 \sa extension()
129*/
130
131
132void QScriptEngineAgentPrivate::attach()
133{
134 if (engine->originalGlobalObject()->debugger())
135 engine->originalGlobalObject()->setDebugger(0);
136 JSC::Debugger::attach(engine->originalGlobalObject());
137 if (!QScriptEnginePrivate::get(d: engine)->isEvaluating())
138 JSC::Debugger::recompileAllJSFunctions(engine->globalData);
139}
140
141void QScriptEngineAgentPrivate::detach()
142{
143 JSC::Debugger::detach(engine->originalGlobalObject());
144}
145
146void QScriptEngineAgentPrivate::returnEvent(const JSC::DebuggerCallFrame& frame, intptr_t sourceID, int lineno)
147{
148 Q_UNUSED(frame);
149 Q_UNUSED(lineno);
150 Q_UNUSED(sourceID);
151}
152
153void QScriptEngineAgentPrivate::exceptionThrow(const JSC::DebuggerCallFrame& frame, intptr_t sourceID, bool hasHandler)
154{
155 JSC::CallFrame *oldFrame = engine->currentFrame;
156 int oldAgentLineNumber = engine->agentLineNumber;
157 engine->currentFrame = frame.callFrame();
158 QScriptValue value(engine->scriptValueFromJSCValue(value: frame.exception()));
159 engine->agentLineNumber = value.property(name: QLatin1String("lineNumber")).toInt32();
160 q_ptr->exceptionThrow(scriptId: sourceID, exception: value, hasHandler);
161 engine->agentLineNumber = oldAgentLineNumber;
162 engine->currentFrame = oldFrame;
163 engine->setCurrentException(value);
164};
165
166void QScriptEngineAgentPrivate::exceptionCatch(const JSC::DebuggerCallFrame& frame, intptr_t sourceID)
167{
168 JSC::CallFrame *oldFrame = engine->currentFrame;
169 engine->currentFrame = frame.callFrame();
170 QScriptValue value(engine->scriptValueFromJSCValue(value: frame.exception()));
171 q_ptr->exceptionCatch(scriptId: sourceID, exception: value);
172 engine->currentFrame = oldFrame;
173 engine->clearCurrentException();
174}
175
176void QScriptEngineAgentPrivate::atStatement(const JSC::DebuggerCallFrame& frame, intptr_t sourceID, int lineno/*, int column*/)
177{
178 QScript::UStringSourceProviderWithFeedback *source = engine->loadedScripts.value(akey: sourceID);
179 if (!source) {
180 // QTBUG-6108: We don't have the source for this script, so ignore.
181 return;
182 }
183// column = source->columnNumberFromOffset(column);
184 int column = 1;
185 JSC::CallFrame *oldFrame = engine->currentFrame;
186 int oldAgentLineNumber = engine->agentLineNumber;
187 engine->currentFrame = frame.callFrame();
188 engine->agentLineNumber = lineno;
189 q_ptr->positionChange(scriptId: sourceID, lineNumber: lineno, columnNumber: column);
190 engine->currentFrame = oldFrame;
191 engine->agentLineNumber = oldAgentLineNumber;
192}
193
194void QScriptEngineAgentPrivate::functionExit(const JSC::JSValue& returnValue, intptr_t sourceID)
195{
196 QScriptValue result = engine->scriptValueFromJSCValue(value: returnValue);
197 q_ptr->functionExit(scriptId: sourceID, returnValue: result);
198 q_ptr->contextPop();
199}
200
201void QScriptEngineAgentPrivate::evaluateStop(const JSC::JSValue& returnValue, intptr_t sourceID)
202{
203 QScriptValue result = engine->scriptValueFromJSCValue(value: returnValue);
204 q_ptr->functionExit(scriptId: sourceID, returnValue: result);
205}
206
207void QScriptEngineAgentPrivate::didReachBreakpoint(const JSC::DebuggerCallFrame& frame,
208 intptr_t sourceID, int lineno/*, int column*/)
209{
210 if (q_ptr->supportsExtension(extension: QScriptEngineAgent::DebuggerInvocationRequest)) {
211 QScript::UStringSourceProviderWithFeedback *source = engine->loadedScripts.value(akey: sourceID);
212 if (!source) {
213 // QTBUG-6108: We don't have the source for this script, so ignore.
214 return;
215 }
216// column = source->columnNumberFromOffset(column);
217 int column = 1;
218 JSC::CallFrame *oldFrame = engine->currentFrame;
219 int oldAgentLineNumber = engine->agentLineNumber;
220 engine->currentFrame = frame.callFrame();
221 engine->agentLineNumber = lineno;
222 QList<QVariant> args;
223 args << qint64(sourceID) << lineno << column;
224 q_ptr->extension(extension: QScriptEngineAgent::DebuggerInvocationRequest, argument: args);
225 engine->currentFrame = oldFrame;
226 engine->agentLineNumber = oldAgentLineNumber;
227 }
228};
229
230/*!
231 Constructs a QScriptEngineAgent object for the given \a engine.
232
233 The engine takes ownership of the agent.
234
235 Call QScriptEngine::setAgent() to make this agent the active
236 agent.
237*/
238QScriptEngineAgent::QScriptEngineAgent(QScriptEngine *engine)
239 : d_ptr(new QScriptEngineAgentPrivate())
240{
241 d_ptr->q_ptr = this;
242 d_ptr->engine = QScriptEnginePrivate::get(q: engine);
243 d_ptr->engine->ownedAgents.append(t: this);
244}
245
246/*!
247 \internal
248*/
249QScriptEngineAgent::QScriptEngineAgent(QScriptEngineAgentPrivate &dd, QScriptEngine *engine)
250 : d_ptr(&dd)
251{
252 d_ptr->q_ptr = this;
253 d_ptr->engine = QScriptEnginePrivate::get(q: engine);
254}
255
256/*!
257 Destroys this QScriptEngineAgent.
258*/
259QScriptEngineAgent::~QScriptEngineAgent()
260{
261 d_ptr->engine->agentDeleted(agent: this); //### TODO: Can this throw?
262}
263
264/*!
265
266 This function is called when the engine has parsed a script and has
267 associated it with the given \a id. The id can be used to identify
268 this particular script in subsequent event notifications.
269
270 \a program, \a fileName and \a baseLineNumber are the original
271 arguments to the QScriptEngine::evaluate() call that triggered this
272 event.
273
274 This function is called just before the script is about to be
275 evaluated.
276
277 You can reimplement this function to record information about the
278 script; for example, by retaining the script text, you can obtain
279 the line of text corresponding to a line number in a subsequent
280 call to positionChange().
281
282 The default implementation does nothing.
283
284 \sa scriptUnload()
285*/
286void QScriptEngineAgent::scriptLoad(qint64 id, const QString &program,
287 const QString &fileName, int baseLineNumber)
288{
289 Q_UNUSED(id);
290 Q_UNUSED(program);
291 Q_UNUSED(fileName);
292 Q_UNUSED(baseLineNumber);
293}
294
295/*!
296 This function is called when the engine has discarded the script
297 identified by the given \a id.
298
299 You can reimplement this function to clean up any resources you have
300 associated with the script.
301
302 The default implementation does nothing.
303
304 \sa scriptLoad()
305*/
306void QScriptEngineAgent::scriptUnload(qint64 id)
307{
308 Q_UNUSED(id);
309}
310
311/*!
312 This function is called when a new script context has been pushed.
313
314 The default implementation does nothing.
315
316 \sa contextPop(), functionEntry()
317*/
318void QScriptEngineAgent::contextPush()
319{
320}
321
322/*!
323 This function is called when the current script context is about to
324 be popped.
325
326 The default implementation does nothing.
327
328 \sa contextPush(), functionExit()
329*/
330void QScriptEngineAgent::contextPop()
331{
332}
333
334/*!
335 This function is called when a script function is called in the
336 engine. If the script function is not a native Qt Script function,
337 it resides in the script identified by \a scriptId; otherwise, \a
338 scriptId is -1.
339
340 This function is called just before execution of the script function
341 begins. You can obtain the QScriptContext associated with the
342 function call with QScriptEngine::currentContext(). The arguments
343 passed to the function are available.
344
345 Reimplement this function to handle this event. For example, a
346 debugger implementation could reimplement this function (and
347 functionExit()) to keep track of the call stack and provide
348 step-over functionality.
349
350 The default implementation does nothing.
351
352 \sa functionExit(), positionChange(), QScriptEngine::currentContext()
353*/
354void QScriptEngineAgent::functionEntry(qint64 scriptId)
355{
356 Q_UNUSED(scriptId);
357}
358
359/*!
360 This function is called when the currently executing script function
361 is about to return. If the script function is not a native Qt Script
362 function, it resides in the script identified by \a scriptId;
363 otherwise, \a scriptId is -1. The \a returnValue is the value that
364 the script function will return.
365
366 This function is called just before the script function returns.
367 You can still access the QScriptContext associated with the
368 script function call with QScriptEngine::currentContext().
369
370 If the engine's
371 \l{QScriptEngine::hasUncaughtException()}{hasUncaughtException}()
372 function returns true, the script function is exiting due to an
373 exception; otherwise, the script function is returning normally.
374
375 Reimplement this function to handle this event; typically you will
376 then also want to reimplement functionEntry().
377
378 The default implementation does nothing.
379
380 \sa functionEntry(), QScriptEngine::hasUncaughtException()
381*/
382void QScriptEngineAgent::functionExit(qint64 scriptId,
383 const QScriptValue &returnValue)
384{
385 Q_UNUSED(scriptId);
386 Q_UNUSED(returnValue);
387}
388
389/*!
390 This function is called when the engine is about to execute a new
391 statement in the script identified by \a scriptId. The statement
392 begins on the line and column specified by \a lineNumber
393 This event is not generated for native Qt Script functions.
394
395 Reimplement this function to handle this event. For example, a
396 debugger implementation could reimplement this function to provide
397 line-by-line stepping, and a profiler implementation could use it to
398 count the number of times each statement is executed.
399
400 The default implementation does nothing.
401
402 \note \a columnNumber is undefined
403
404 \sa scriptLoad(), functionEntry()
405*/
406void QScriptEngineAgent::positionChange(qint64 scriptId,
407 int lineNumber, int columnNumber)
408{
409 Q_UNUSED(scriptId);
410 Q_UNUSED(lineNumber);
411 Q_UNUSED(columnNumber);
412}
413
414/*!
415 This function is called when the given \a exception has occurred in
416 the engine, in the script identified by \a scriptId. If the
417 exception was thrown by a native Qt Script function, \a scriptId is
418 -1.
419
420 If \a hasHandler is true, there is a \c{catch} or \c{finally} block
421 that will handle the exception. If \a hasHandler is false, there is
422 no handler for the exception.
423
424 Reimplement this function if you want to handle this event. For
425 example, a debugger can notify the user when an uncaught exception
426 occurs (i.e. \a hasHandler is false).
427
428 The default implementation does nothing.
429
430 \sa exceptionCatch()
431*/
432void QScriptEngineAgent::exceptionThrow(qint64 scriptId,
433 const QScriptValue &exception,
434 bool hasHandler)
435{
436 Q_UNUSED(scriptId);
437 Q_UNUSED(exception);
438 Q_UNUSED(hasHandler);
439}
440
441/*!
442 This function is called when the given \a exception is about to be
443 caught, in the script identified by \a scriptId.
444
445 Reimplement this function if you want to handle this event.
446
447 The default implementation does nothing.
448
449 \sa exceptionThrow()
450*/
451void QScriptEngineAgent::exceptionCatch(qint64 scriptId,
452 const QScriptValue &exception)
453{
454 Q_UNUSED(scriptId);
455 Q_UNUSED(exception);
456}
457
458#if 0
459/*
460 This function is called when a property of the given \a object has
461 been added, changed or removed.
462
463 Reimplement this function if you want to handle this event.
464
465 The default implementation does nothing.
466*/
467void QScriptEngineAgent::propertyChange(qint64 scriptId,
468 const QScriptValue &object,
469 const QString &propertyName,
470 PropertyChange change)
471{
472 Q_UNUSED(scriptId);
473 Q_UNUSED(object);
474 Q_UNUSED(propertyName);
475 Q_UNUSED(change);
476}
477#endif
478
479/*!
480 Returns true if the QScriptEngineAgent supports the given \a
481 extension; otherwise, false is returned. By default, no extensions
482 are supported.
483
484 \sa extension()
485*/
486bool QScriptEngineAgent::supportsExtension(Extension extension) const
487{
488 Q_UNUSED(extension);
489 return false;
490}
491
492/*!
493 This virtual function can be reimplemented in a QScriptEngineAgent
494 subclass to provide support for extensions. The optional \a argument
495 can be provided as input to the \a extension; the result must be
496 returned in the form of a QVariant. You can call supportsExtension()
497 to check if an extension is supported by the QScriptEngineAgent. By
498 default, no extensions are supported, and this function returns an
499 invalid QVariant.
500
501 If you implement the DebuggerInvocationRequest extension, Qt Script
502 will call this function when a \c{debugger} statement is encountered
503 in a script. The \a argument is a QVariantList containing three
504 items: The first item is the scriptId (a qint64), the second item is
505 the line number (an int), and the third item is the column number
506 (an int).
507
508 \sa supportsExtension()
509*/
510QVariant QScriptEngineAgent::extension(Extension extension,
511 const QVariant &argument)
512{
513 Q_UNUSED(extension);
514 Q_UNUSED(argument);
515 return QVariant();
516}
517
518/*!
519 Returns the QScriptEngine that this agent is associated with.
520*/
521QScriptEngine *QScriptEngineAgent::engine() const
522{
523 Q_D(const QScriptEngineAgent);
524 return QScriptEnginePrivate::get(d: d->engine);
525}
526
527QT_END_NAMESPACE
528

source code of qtscript/src/script/api/qscriptengineagent.cpp