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 | |
49 | QT_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 | |
132 | void 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 | |
141 | void QScriptEngineAgentPrivate::detach() |
142 | { |
143 | JSC::Debugger::detach(engine->originalGlobalObject()); |
144 | } |
145 | |
146 | void 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 | |
153 | void 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 | |
166 | void 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 | |
176 | void 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 | |
194 | void 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 | |
201 | void 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 | |
207 | void 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 | */ |
238 | QScriptEngineAgent::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 | */ |
249 | QScriptEngineAgent::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 | */ |
259 | QScriptEngineAgent::~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 | */ |
286 | void 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 | */ |
306 | void 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 | */ |
318 | void 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 | */ |
330 | void 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 | */ |
354 | void 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 | */ |
382 | void 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 | */ |
406 | void 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 | */ |
432 | void 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 | */ |
451 | void 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 | */ |
467 | void 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 | */ |
486 | bool 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 | */ |
510 | QVariant 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 | */ |
521 | QScriptEngine *QScriptEngineAgent::engine() const |
522 | { |
523 | Q_D(const QScriptEngineAgent); |
524 | return QScriptEnginePrivate::get(d: d->engine); |
525 | } |
526 | |
527 | QT_END_NAMESPACE |
528 | |