1 /****************************************************************************
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtSCriptTools module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
40 ****************************************************************************/
42 #include "qscriptdebuggeragent_p.h"
43 #include "qscriptdebuggeragent_p_p.h"
44 #include "qscriptdebuggerbackend_p_p.h"
46 #include <QtCore/qcoreapplication.h>
47 #include <QtCore/qset.h>
48 #include <QtScript/qscriptengine.h>
54 \class QScriptDebuggerAgent
57 This class implements a state machine that uses the low-level events
58 reported by the QScriptEngineAgent interface to implement debugging-
59 specific functionality such as stepping and breakpoints. It is used
60 internally by the QScriptDebuggerBackend class.
63 QScriptDebuggerAgentPrivate::QScriptDebuggerAgentPrivate()
64 : state(NoState
), stepDepth(0), stepCount(0),
65 targetScriptId(-1), targetLineNumber(-1), returnCounter(0),
66 nextBreakpointId(1), hitBreakpointId(0),
67 nextContextId(0), statementCounter(0)
71 QScriptDebuggerAgentPrivate::~QScriptDebuggerAgentPrivate()
75 QScriptDebuggerAgentPrivate
*QScriptDebuggerAgentPrivate::get(
76 QScriptDebuggerAgent
*q
)
85 Constructs a new agent for the given \a engine. The agent will
86 report debugging-related events (e.g. step completion) to the given
89 QScriptDebuggerAgent::QScriptDebuggerAgent(
90 QScriptDebuggerBackendPrivate
*backend
, QScriptEngine
*engine
)
91 : QScriptEngineAgent(*new QScriptDebuggerAgentPrivate
, engine
)
93 Q_D(QScriptDebuggerAgent
);
96 QScriptContext
*ctx
= engine
->currentContext();
98 d
->scriptIdStack
.append(QList
<qint64
>());
99 d
->contextIdStack
.append(d
->nextContextId
);
101 ctx
= ctx
->parentContext();
106 Destroys this QScriptDebuggerAgent.
108 QScriptDebuggerAgent::~QScriptDebuggerAgent()
110 Q_D(QScriptDebuggerAgent
);
112 d
->backend
->agentDestroyed(this);
116 Instructs the agent to perform a "step into" operation. This
117 function returns immediately. The agent will report step completion
118 at a later time, i.e. when script statements are evaluted.
120 void QScriptDebuggerAgent::enterStepIntoMode(int count
)
122 Q_D(QScriptDebuggerAgent
);
123 d
->state
= QScriptDebuggerAgentPrivate::SteppingIntoState
;
124 d
->stepCount
= count
;
125 d
->stepResult
= QScriptValue();
129 Instructs the agent to perform a "step over" operation. This
130 function returns immediately. The agent will report step completion
131 at a later time, i.e. when script statements are evaluted.
133 void QScriptDebuggerAgent::enterStepOverMode(int count
)
135 Q_D(QScriptDebuggerAgent
);
136 d
->state
= QScriptDebuggerAgentPrivate::SteppingOverState
;
137 if (engine()->isEvaluating())
141 d
->stepCount
= count
;
142 d
->stepResult
= QScriptValue();
146 Instructs the agent to perform a "step out" operation. This
147 function returns immediately. The agent will report step completion
148 at a later time, i.e. when script statements are evaluted.
150 void QScriptDebuggerAgent::enterStepOutMode()
152 Q_D(QScriptDebuggerAgent
);
153 d
->state
= QScriptDebuggerAgentPrivate::SteppingOutState
;
154 if (engine()->isEvaluating())
161 Instructs the agent to continue evaluation.
162 This function returns immediately.
164 void QScriptDebuggerAgent::enterContinueMode()
166 Q_D(QScriptDebuggerAgent
);
167 d
->state
= QScriptDebuggerAgentPrivate::NoState
;
171 Instructs the agent to interrupt evaluation.
172 This function returns immediately.
174 void QScriptDebuggerAgent::enterInterruptMode()
176 Q_D(QScriptDebuggerAgent
);
177 d
->state
= QScriptDebuggerAgentPrivate::InterruptingState
;
181 Instructs the agent to continue evaluation until the location
182 described by \a fileName and \a lineNumber is reached. This
183 function returns immediately.
185 void QScriptDebuggerAgent::enterRunToLocationMode(const QString
&fileName
, int lineNumber
)
187 Q_D(QScriptDebuggerAgent
);
188 d
->targetFileName
= fileName
;
189 d
->targetLineNumber
= lineNumber
;
190 d
->targetScriptId
= resolveScript(fileName
);
191 d
->state
= QScriptDebuggerAgentPrivate::RunningToLocationState
;
195 Instructs the agent to continue evaluation until the location
196 described by \a scriptId and \a lineNumber is reached. This
197 function returns immediately.
199 void QScriptDebuggerAgent::enterRunToLocationMode(qint64 scriptId
, int lineNumber
)
201 Q_D(QScriptDebuggerAgent
);
202 d
->targetScriptId
= scriptId
;
203 d
->targetFileName
= QString();
204 d
->targetLineNumber
= lineNumber
;
205 d
->state
= QScriptDebuggerAgentPrivate::RunningToLocationState
;
208 void QScriptDebuggerAgent::enterReturnByForceMode(int contextIndex
, const QScriptValue
&value
)
210 Q_D(QScriptDebuggerAgent
);
211 d
->returnCounter
= contextIndex
+ 1;
212 d
->returnValue
= QScriptValue();
213 d
->state
= QScriptDebuggerAgentPrivate::ReturningByForceState
;
214 // throw an exception; we will catch it when the proper frame is popped
215 engine()->currentContext()->throwValue(value
);
219 Sets a breakpoint defined by the given \a data.
220 Returns an integer that uniquely identifies the new breakpoint,
221 or -1 if setting the breakpoint failed.
223 int QScriptDebuggerAgent::setBreakpoint(const QScriptBreakpointData
&data
)
225 Q_D(QScriptDebuggerAgent
);
226 qint64 scriptId
= data
.scriptId();
227 if (scriptId
!= -1) {
228 if (!d
->scripts
.contains(scriptId
)) {
229 // that script has been unloaded, so invalidate the ID
231 const_cast<QScriptBreakpointData
&>(data
).setScriptId(-1);
232 } else if (data
.fileName().isEmpty()) {
233 QString fileName
= d
->scripts
[scriptId
].fileName();
234 const_cast<QScriptBreakpointData
&>(data
).setFileName(fileName
);
238 int id
= d
->nextBreakpointId
;
239 ++d
->nextBreakpointId
;
241 if (scriptId
!= -1) {
242 d
->resolvedBreakpoints
[scriptId
].append(id
);
244 QString fileName
= data
.fileName();
245 bool resolved
= false;
246 QScriptScriptMap::const_iterator it
;
247 for (it
= d
->scripts
.constBegin(); it
!= d
->scripts
.constEnd(); ++it
) {
248 if (it
.value().fileName() == fileName
) {
249 d
->resolvedBreakpoints
[it
.key()].append(id
);
255 d
->unresolvedBreakpoints
[fileName
].append(id
);
258 d
->breakpoints
.insert(id
, data
);
264 Deletes the breakpoint with the given \a id.
265 Returns true if the breakpoint was deleted, false if
266 no such breakpoint exists.
268 bool QScriptDebuggerAgent::deleteBreakpoint(int id
)
270 Q_D(QScriptDebuggerAgent
);
271 if (!d
->breakpoints
.contains(id
))
273 d
->breakpoints
.remove(id
);
276 QHash
<qint64
, QList
<int> >::iterator it
;
277 it
= d
->resolvedBreakpoints
.begin();
278 for ( ; !found
&& (it
!= d
->resolvedBreakpoints
.end()); ) {
279 QList
<int> &lst
= it
.value();
280 Q_ASSERT(!lst
.isEmpty());
281 for (int i
= 0; i
< lst
.size(); ++i
) {
282 if (lst
.at(i
) == id
) {
289 it
= d
->resolvedBreakpoints
.erase(it
);
295 QHash
<QString
, QList
<int> >::iterator it
;
296 it
= d
->unresolvedBreakpoints
.begin();
297 for ( ; !found
&& (it
!= d
->unresolvedBreakpoints
.end()); ) {
298 QList
<int> &lst
= it
.value();
299 Q_ASSERT(!lst
.isEmpty());
300 for (int i
= 0; i
< lst
.size(); ++i
) {
301 if (lst
.at(i
) == id
) {
308 it
= d
->unresolvedBreakpoints
.erase(it
);
317 Deletes all breakpoints.
319 void QScriptDebuggerAgent::deleteAllBreakpoints()
321 Q_D(QScriptDebuggerAgent
);
322 d
->breakpoints
.clear();
323 d
->resolvedBreakpoints
.clear();
324 d
->unresolvedBreakpoints
.clear();
328 Returns the data associated with the breakpoint with the given \a
331 QScriptBreakpointData
QScriptDebuggerAgent::breakpointData(int id
) const
333 Q_D(const QScriptDebuggerAgent
);
334 return d
->breakpoints
.value(id
);
338 Sets the data associated with the breakpoint with the given \a
341 bool QScriptDebuggerAgent::setBreakpointData(int id
,
342 const QScriptBreakpointData
&data
)
344 Q_D(QScriptDebuggerAgent
);
345 if (!d
->breakpoints
.contains(id
))
347 d
->breakpoints
[id
] = data
;
352 Returns all breakpoints.
354 QScriptBreakpointMap
QScriptDebuggerAgent::breakpoints() const
356 Q_D(const QScriptDebuggerAgent
);
357 return d
->breakpoints
;
363 QScriptScriptMap
QScriptDebuggerAgent::scripts() const
365 Q_D(const QScriptDebuggerAgent
);
370 Returns the data associated with the script with the given \a id.
372 QScriptScriptData
QScriptDebuggerAgent::scriptData(qint64 id
) const
374 Q_D(const QScriptDebuggerAgent
);
375 return d
->scripts
.value(id
);
379 Checkpoints the current scripts.
381 void QScriptDebuggerAgent::scriptsCheckpoint()
383 Q_D(QScriptDebuggerAgent
);
384 d
->previousCheckpointScripts
= d
->checkpointScripts
;
385 d
->checkpointScripts
= d
->scripts
;
389 Returns the difference between the current checkpoint and the
390 previous checkpoint. The first item in the pair is a list containing
391 the identifiers of the scripts that were added. The second item in
392 the pair is a list containing the identifiers of the scripts that
395 QPair
<QList
<qint64
>, QList
<qint64
> > QScriptDebuggerAgent::scriptsDelta() const
397 Q_D(const QScriptDebuggerAgent
);
398 QSet
<qint64
> prevSet
= d
->previousCheckpointScripts
.keys().toSet();
399 QSet
<qint64
> currSet
= d
->checkpointScripts
.keys().toSet();
400 QSet
<qint64
> addedScriptIds
= currSet
- prevSet
;
401 QSet
<qint64
> removedScriptIds
= prevSet
- currSet
;
402 return qMakePair(addedScriptIds
.toList(), removedScriptIds
.toList());
406 Returns the identifier of the script that has the given \a fileName,
407 or -1 if there is no such script.
409 qint64
QScriptDebuggerAgent::resolveScript(const QString
&fileName
) const
411 Q_D(const QScriptDebuggerAgent
);
412 QScriptScriptMap::const_iterator it
;
413 for (it
= d
->scripts
.constBegin(); it
!= d
->scripts
.constEnd(); ++it
) {
414 if (it
.value().fileName() == fileName
)
420 QList
<qint64
> QScriptDebuggerAgent::contextIds() const
422 Q_D(const QScriptDebuggerAgent
);
423 return d
->contextIdStack
;
426 QPair
<QList
<qint64
>, QList
<qint64
> > QScriptDebuggerAgent::contextsCheckpoint()
428 Q_D(QScriptDebuggerAgent
);
429 int i
= d
->checkpointContextIdStack
.size() - 1;
430 int j
= d
->contextIdStack
.size() - 1;
431 for ( ; (i
>= 0) && (j
>= 0); --i
, --j
) {
432 if (d
->checkpointContextIdStack
.at(i
) != d
->contextIdStack
.at(j
))
435 QList
<qint64
> removed
= d
->checkpointContextIdStack
.mid(0, i
+1);
436 QList
<qint64
> added
= d
->contextIdStack
.mid(0, j
+1);
437 d
->checkpointContextIdStack
= d
->contextIdStack
;
438 return qMakePair(removed
, added
);
441 void QScriptDebuggerAgent::nullifyBackendPointer()
443 Q_D(QScriptDebuggerAgent
);
450 void QScriptDebuggerAgent::scriptLoad(qint64 id
, const QString
&program
,
451 const QString
&fileName
, int baseLineNumber
)
453 Q_D(QScriptDebuggerAgent
);
454 QScriptScriptData data
= QScriptScriptData(program
, fileName
, baseLineNumber
);
455 d
->scripts
.insert(id
, data
);
457 if ((d
->state
== QScriptDebuggerAgentPrivate::RunningToLocationState
)
458 && (d
->targetScriptId
== -1)
459 && ((d
->targetFileName
== fileName
) || d
->targetFileName
.isEmpty())) {
460 d
->targetScriptId
= id
;
463 if (!fileName
.isEmpty()) {
464 QList
<int> lst
= d
->unresolvedBreakpoints
.take(fileName
);
466 d
->resolvedBreakpoints
.insert(id
, lst
);
473 void QScriptDebuggerAgent::scriptUnload(qint64 id
)
475 Q_D(QScriptDebuggerAgent
);
476 QScriptScriptData data
= d
->scripts
.take(id
);
477 QString fileName
= data
.fileName();
479 if ((d
->state
== QScriptDebuggerAgentPrivate::RunningToLocationState
)
480 && (d
->targetScriptId
== id
)) {
481 d
->targetScriptId
= -1;
482 d
->targetFileName
= fileName
;
485 if (!fileName
.isEmpty()) {
486 QList
<int> lst
= d
->resolvedBreakpoints
.take(id
);
488 d
->unresolvedBreakpoints
.insert(fileName
, lst
);
495 void QScriptDebuggerAgent::contextPush()
497 Q_D(QScriptDebuggerAgent
);
498 d
->scriptIdStack
.append(QList
<qint64
>());
499 d
->contextIdStack
.prepend(d
->nextContextId
);
506 void QScriptDebuggerAgent::contextPop()
508 Q_D(QScriptDebuggerAgent
);
509 d
->scriptIdStack
.removeLast();
510 d
->contextIdStack
.removeFirst();
516 void QScriptDebuggerAgent::functionEntry(qint64 scriptId
)
518 Q_D(QScriptDebuggerAgent
);
519 QList
<qint64
> &ids
= d
->scriptIdStack
.last();
520 ids
.append(scriptId
);
521 if ((d
->state
== QScriptDebuggerAgentPrivate::SteppingOverState
)
522 || (d
->state
== QScriptDebuggerAgentPrivate::SteppingOutState
)) {
530 void QScriptDebuggerAgent::functionExit(qint64 scriptId
,
531 const QScriptValue
&returnValue
)
534 Q_D(QScriptDebuggerAgent
);
535 QList
<qint64
> &ids
= d
->scriptIdStack
.last();
537 if (d
->state
== QScriptDebuggerAgentPrivate::SteppingOverState
) {
539 } else if (d
->state
== QScriptDebuggerAgentPrivate::SteppingOutState
) {
540 if (--d
->stepDepth
< 0) {
541 d
->stepResult
= returnValue
;
542 d
->state
= QScriptDebuggerAgentPrivate::SteppedOutState
;
544 } else if (d
->state
== QScriptDebuggerAgentPrivate::ReturningByForceState
) {
545 if (--d
->returnCounter
== 0) {
546 d
->returnValue
= returnValue
;
547 d
->state
= QScriptDebuggerAgentPrivate::ReturnedByForceState
;
548 engine()->clearExceptions();
556 void QScriptDebuggerAgent::positionChange(qint64 scriptId
,
557 int lineNumber
, int columnNumber
)
559 Q_D(QScriptDebuggerAgent
);
560 if (engine()->processEventsInterval() == -1) {
561 // see if it's time to call processEvents()
562 if ((++d
->statementCounter
% 25000) == 0) {
563 if (!d
->processEventsTimer
.isNull()) {
564 if (d
->processEventsTimer
.elapsed() > 30) {
565 QCoreApplication::processEvents();
566 d
->processEventsTimer
.restart();
569 d
->processEventsTimer
.start();
576 QList
<int> lst
= d
->resolvedBreakpoints
.value(scriptId
);
577 for (int i
= 0; i
< lst
.size(); ++i
) {
579 QScriptBreakpointData
&data
= d
->breakpoints
[id
];
580 if (!data
.isEnabled())
582 if (data
.lineNumber() != lineNumber
)
584 if (!data
.condition().isEmpty()) {
585 // ### careful, evaluate() can cause an exception
586 // ### disable callbacks in nested evaluate?
587 QScriptDebuggerAgentPrivate::State was
= d
->state
;
588 d
->state
= QScriptDebuggerAgentPrivate::NoState
;
589 QScriptValue ret
= engine()->evaluate(
591 QString::fromLatin1("Breakpoint %0 condition checker").arg(id
));
594 if (!ret
.toBoolean())
599 d
->hitBreakpointId
= id
;
600 d
->state
= QScriptDebuggerAgentPrivate::BreakpointState
;
605 case QScriptDebuggerAgentPrivate::NoState
:
606 case QScriptDebuggerAgentPrivate::SteppingOutState
:
607 case QScriptDebuggerAgentPrivate::ReturningByForceState
:
611 case QScriptDebuggerAgentPrivate::SteppingIntoState
:
612 if (--d
->stepCount
== 0) {
613 d
->state
= QScriptDebuggerAgentPrivate::NoState
;
615 d
->backend
->stepped(scriptId
, lineNumber
, columnNumber
, QScriptValue());
619 case QScriptDebuggerAgentPrivate::SteppingOverState
:
620 if ((d
->stepDepth
> 0) || (--d
->stepCount
!= 0))
623 case QScriptDebuggerAgentPrivate::SteppedOverState
:
624 d
->state
= QScriptDebuggerAgentPrivate::NoState
;
626 d
->backend
->stepped(scriptId
, lineNumber
, columnNumber
, d
->stepResult
);
629 case QScriptDebuggerAgentPrivate::SteppedOutState
:
630 d
->state
= QScriptDebuggerAgentPrivate::NoState
;
632 d
->backend
->stepped(scriptId
, lineNumber
, columnNumber
, d
->stepResult
);
635 case QScriptDebuggerAgentPrivate::RunningToLocationState
:
636 if (((lineNumber
== d
->targetLineNumber
) || (d
->targetLineNumber
== -1))
637 && (scriptId
== d
->targetScriptId
)) {
638 d
->state
= QScriptDebuggerAgentPrivate::NoState
;
640 d
->backend
->locationReached(scriptId
, lineNumber
, columnNumber
);
644 case QScriptDebuggerAgentPrivate::InterruptingState
:
645 d
->state
= QScriptDebuggerAgentPrivate::NoState
;
647 d
->backend
->interrupted(scriptId
, lineNumber
, columnNumber
);
650 case QScriptDebuggerAgentPrivate::BreakpointState
:
651 d
->state
= QScriptDebuggerAgentPrivate::NoState
;
653 d
->backend
->breakpoint(scriptId
, lineNumber
, columnNumber
, d
->hitBreakpointId
);
654 if (d
->breakpoints
.value(d
->hitBreakpointId
).isSingleShot())
655 deleteBreakpoint(d
->hitBreakpointId
);
658 case QScriptDebuggerAgentPrivate::ReturnedByForceState
:
659 d
->state
= QScriptDebuggerAgentPrivate::NoState
;
661 d
->backend
->forcedReturn(scriptId
, lineNumber
, columnNumber
, d
->returnValue
);
664 case QScriptDebuggerAgentPrivate::SteppedIntoState
:
665 case QScriptDebuggerAgentPrivate::ReachedLocationState
:
666 case QScriptDebuggerAgentPrivate::InterruptedState
:
667 // ### deal with the case when code is evaluated while we're already paused
676 void QScriptDebuggerAgent::exceptionThrow(qint64 scriptId
,
677 const QScriptValue
&exception
,
680 Q_D(QScriptDebuggerAgent
);
681 if (d
->state
== QScriptDebuggerAgentPrivate::ReturningByForceState
) {
682 // we threw this exception ourselves, so ignore it for now
683 // (see functionExit()).
687 d
->backend
->exception(scriptId
, exception
, hasHandler
);
693 void QScriptDebuggerAgent::exceptionCatch(qint64 scriptId
,
694 const QScriptValue
&exception
)
703 bool QScriptDebuggerAgent::supportsExtension(Extension extension
) const
705 return (extension
== DebuggerInvocationRequest
);
711 QVariant
QScriptDebuggerAgent::extension(Extension extension
,
712 const QVariant
&argument
)
715 Q_D(QScriptDebuggerAgent
);
716 Q_ASSERT(extension
== DebuggerInvocationRequest
);
717 QVariantList lst
= argument
.toList();
718 qint64 scriptId
= lst
.at(0).toLongLong();
719 int lineNumber
= lst
.at(1).toInt();
720 int columnNumber
= lst
.at(2).toInt();
721 d
->state
= QScriptDebuggerAgentPrivate::NoState
;
723 d
->backend
->debuggerInvocationRequest(
724 scriptId
, lineNumber
, columnNumber
);