Fixing the autotest for other platforms, hopefully...
[qt-netbsd.git] / src / scripttools / debugging / qscriptdebuggeragent.cpp
blob8bd8941eb18d9f519e1899e79f4242a9975e608b
1 /****************************************************************************
2 **
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the QtSCriptTools module of the Qt Toolkit.
8 **
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
14 ** this package.
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.
38 ** $QT_END_LICENSE$
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>
50 QT_BEGIN_NAMESPACE
52 /*!
53 \since 4.5
54 \class QScriptDebuggerAgent
55 \internal
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)
78 if (!q)
79 return 0;
80 return q->d_func();
84 /*!
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
87 \a backend.
89 QScriptDebuggerAgent::QScriptDebuggerAgent(
90 QScriptDebuggerBackendPrivate *backend, QScriptEngine *engine)
91 : QScriptEngineAgent(*new QScriptDebuggerAgentPrivate, engine)
93 Q_D(QScriptDebuggerAgent);
94 d->backend = backend;
96 QScriptContext *ctx = engine->currentContext();
97 while (ctx) {
98 d->scriptIdStack.append(QList<qint64>());
99 d->contextIdStack.append(d->nextContextId);
100 ++d->nextContextId;
101 ctx = ctx->parentContext();
106 Destroys this QScriptDebuggerAgent.
108 QScriptDebuggerAgent::~QScriptDebuggerAgent()
110 Q_D(QScriptDebuggerAgent);
111 if (d->backend)
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())
138 d->stepDepth = 0;
139 else
140 d->stepDepth = -1;
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())
155 d->stepDepth = 0;
156 else
157 d->stepDepth = -1;
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
230 scriptId = -1;
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);
243 } else {
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);
250 resolved = true;
251 break;
254 if (!resolved)
255 d->unresolvedBreakpoints[fileName].append(id);
258 d->breakpoints.insert(id, data);
260 return id;
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))
272 return false;
273 d->breakpoints.remove(id);
274 bool found = false;
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) {
283 lst.removeAt(i);
284 found = true;
285 break;
288 if (lst.isEmpty())
289 it = d->resolvedBreakpoints.erase(it);
290 else
291 ++it;
294 if (!found) {
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) {
302 lst.removeAt(i);
303 found = true;
304 break;
307 if (lst.isEmpty())
308 it = d->unresolvedBreakpoints.erase(it);
309 else
310 ++it;
313 return found;
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))
346 return false;
347 d->breakpoints[id] = data;
348 return true;
352 Returns all breakpoints.
354 QScriptBreakpointMap QScriptDebuggerAgent::breakpoints() const
356 Q_D(const QScriptDebuggerAgent);
357 return d->breakpoints;
361 Returns all scripts.
363 QScriptScriptMap QScriptDebuggerAgent::scripts() const
365 Q_D(const QScriptDebuggerAgent);
366 return d->scripts;
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
393 were removed.
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)
415 return it.key();
417 return -1;
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))
433 break;
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);
444 d->backend = 0;
448 \reimp
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);
465 if (!lst.isEmpty())
466 d->resolvedBreakpoints.insert(id, lst);
471 \reimp
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);
487 if (!lst.isEmpty())
488 d->unresolvedBreakpoints.insert(fileName, lst);
493 \reimp
495 void QScriptDebuggerAgent::contextPush()
497 Q_D(QScriptDebuggerAgent);
498 d->scriptIdStack.append(QList<qint64>());
499 d->contextIdStack.prepend(d->nextContextId);
500 ++d->nextContextId;
504 \reimp
506 void QScriptDebuggerAgent::contextPop()
508 Q_D(QScriptDebuggerAgent);
509 d->scriptIdStack.removeLast();
510 d->contextIdStack.removeFirst();
514 \reimp
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)) {
523 ++d->stepDepth;
528 \reimp
530 void QScriptDebuggerAgent::functionExit(qint64 scriptId,
531 const QScriptValue &returnValue)
533 Q_UNUSED(scriptId);
534 Q_D(QScriptDebuggerAgent);
535 QList<qint64> &ids = d->scriptIdStack.last();
536 ids.removeLast();
537 if (d->state == QScriptDebuggerAgentPrivate::SteppingOverState) {
538 --d->stepDepth;
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();
554 \reimp
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();
568 } else {
569 d->processEventsTimer.start();
574 // check breakpoints
576 QList<int> lst = d->resolvedBreakpoints.value(scriptId);
577 for (int i = 0; i < lst.size(); ++i) {
578 int id = lst.at(i);
579 QScriptBreakpointData &data = d->breakpoints[id];
580 if (!data.isEnabled())
581 continue;
582 if (data.lineNumber() != lineNumber)
583 continue;
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(
590 data.condition(),
591 QString::fromLatin1("Breakpoint %0 condition checker").arg(id));
592 if (!ret.isError())
593 d->state = was;
594 if (!ret.toBoolean())
595 continue;
597 if (!data.hit())
598 continue;
599 d->hitBreakpointId = id;
600 d->state = QScriptDebuggerAgentPrivate::BreakpointState;
604 switch (d->state) {
605 case QScriptDebuggerAgentPrivate::NoState:
606 case QScriptDebuggerAgentPrivate::SteppingOutState:
607 case QScriptDebuggerAgentPrivate::ReturningByForceState:
608 // Do nothing
609 break;
611 case QScriptDebuggerAgentPrivate::SteppingIntoState:
612 if (--d->stepCount == 0) {
613 d->state = QScriptDebuggerAgentPrivate::NoState;
614 if (d->backend)
615 d->backend->stepped(scriptId, lineNumber, columnNumber, QScriptValue());
617 break;
619 case QScriptDebuggerAgentPrivate::SteppingOverState:
620 if ((d->stepDepth > 0) || (--d->stepCount != 0))
621 break;
622 // fallthrough
623 case QScriptDebuggerAgentPrivate::SteppedOverState:
624 d->state = QScriptDebuggerAgentPrivate::NoState;
625 if (d->backend)
626 d->backend->stepped(scriptId, lineNumber, columnNumber, d->stepResult);
627 break;
629 case QScriptDebuggerAgentPrivate::SteppedOutState:
630 d->state = QScriptDebuggerAgentPrivate::NoState;
631 if (d->backend)
632 d->backend->stepped(scriptId, lineNumber, columnNumber, d->stepResult);
633 break;
635 case QScriptDebuggerAgentPrivate::RunningToLocationState:
636 if (((lineNumber == d->targetLineNumber) || (d->targetLineNumber == -1))
637 && (scriptId == d->targetScriptId)) {
638 d->state = QScriptDebuggerAgentPrivate::NoState;
639 if (d->backend)
640 d->backend->locationReached(scriptId, lineNumber, columnNumber);
642 break;
644 case QScriptDebuggerAgentPrivate::InterruptingState:
645 d->state = QScriptDebuggerAgentPrivate::NoState;
646 if (d->backend)
647 d->backend->interrupted(scriptId, lineNumber, columnNumber);
648 break;
650 case QScriptDebuggerAgentPrivate::BreakpointState:
651 d->state = QScriptDebuggerAgentPrivate::NoState;
652 if (d->backend)
653 d->backend->breakpoint(scriptId, lineNumber, columnNumber, d->hitBreakpointId);
654 if (d->breakpoints.value(d->hitBreakpointId).isSingleShot())
655 deleteBreakpoint(d->hitBreakpointId);
656 break;
658 case QScriptDebuggerAgentPrivate::ReturnedByForceState:
659 d->state = QScriptDebuggerAgentPrivate::NoState;
660 if (d->backend)
661 d->backend->forcedReturn(scriptId, lineNumber, columnNumber, d->returnValue);
662 break;
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
668 // Q_ASSERT(false);
669 break;
674 \reimp
676 void QScriptDebuggerAgent::exceptionThrow(qint64 scriptId,
677 const QScriptValue &exception,
678 bool hasHandler)
680 Q_D(QScriptDebuggerAgent);
681 if (d->state == QScriptDebuggerAgentPrivate::ReturningByForceState) {
682 // we threw this exception ourselves, so ignore it for now
683 // (see functionExit()).
684 return;
686 if (d->backend)
687 d->backend->exception(scriptId, exception, hasHandler);
691 \reimp
693 void QScriptDebuggerAgent::exceptionCatch(qint64 scriptId,
694 const QScriptValue &exception)
696 Q_UNUSED(scriptId);
697 Q_UNUSED(exception);
701 \reimp
703 bool QScriptDebuggerAgent::supportsExtension(Extension extension) const
705 return (extension == DebuggerInvocationRequest);
709 \reimp
711 QVariant QScriptDebuggerAgent::extension(Extension extension,
712 const QVariant &argument)
714 Q_UNUSED(extension);
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;
722 if (d->backend) {
723 d->backend->debuggerInvocationRequest(
724 scriptId, lineNumber, columnNumber);
726 return QVariant();
729 QT_END_NAMESPACE