1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the examples of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:BSD$ |
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 | ** BSD License Usage |
18 | ** Alternatively, you may use this file under the terms of the BSD license |
19 | ** as follows: |
20 | ** |
21 | ** "Redistribution and use in source and binary forms, with or without |
22 | ** modification, are permitted provided that the following conditions are |
23 | ** met: |
24 | ** * Redistributions of source code must retain the above copyright |
25 | ** notice, this list of conditions and the following disclaimer. |
26 | ** * Redistributions in binary form must reproduce the above copyright |
27 | ** notice, this list of conditions and the following disclaimer in |
28 | ** the documentation and/or other materials provided with the |
29 | ** distribution. |
30 | ** * Neither the name of The Qt Company Ltd nor the names of its |
31 | ** contributors may be used to endorse or promote products derived |
32 | ** from this software without specific prior written permission. |
33 | ** |
34 | ** |
35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." |
46 | ** |
47 | ** $QT_END_LICENSE$ |
48 | ** |
49 | ****************************************************************************/ |
50 | |
51 | #include "scriptdebugger.h" |
52 | #include "scriptbreakpointmanager.h" |
53 | |
54 | #include <QtScript/QScriptEngine> |
55 | #include <QtScript/QScriptEngineAgent> |
56 | #include <QtScript/QScriptContextInfo> |
57 | #include <QtScript/QScriptValueIterator> |
58 | #include <QtCore/QTextStream> |
59 | #include <QtCore/QStack> |
60 | |
61 | static QString safeValueToString(const QScriptValue &value) |
62 | { |
63 | if (value.isObject()) |
64 | return QLatin1String("[object Object]" ); |
65 | else |
66 | return value.toString(); |
67 | } |
68 | |
69 | class ScriptInfo; |
70 | class ScriptBreakpointManager; |
71 | |
72 | class ScriptDebuggerPrivate |
73 | : public QScriptEngineAgent |
74 | { |
75 | Q_DECLARE_PUBLIC(ScriptDebugger) |
76 | public: |
77 | enum Mode { |
78 | Run, |
79 | StepInto, |
80 | StepOver |
81 | }; |
82 | |
83 | ScriptDebuggerPrivate(QScriptEngine *engine); |
84 | ~ScriptDebuggerPrivate(); |
85 | |
86 | // QScriptEngineAgent interface |
87 | void scriptLoad(qint64 id, const QString &program, |
88 | const QString &fileName, int lineNumber); |
89 | void scriptUnload(qint64 id); |
90 | |
91 | void positionChange(qint64 scriptId, |
92 | int lineNumber, int columnNumber); |
93 | |
94 | void functionEntry(qint64 scriptId); |
95 | void functionExit(qint64 scriptId, |
96 | const QScriptValue &returnValue); |
97 | |
98 | void exceptionThrow(qint64 scriptId, |
99 | const QScriptValue &exception, bool hasHandler); |
100 | |
101 | |
102 | void interactive(); |
103 | bool executeCommand(const QString &command, const QStringList &args); |
104 | |
105 | void setMode(Mode mode); |
106 | Mode mode() const; |
107 | |
108 | int frameCount() const; |
109 | void setCurrentFrameIndex(int index); |
110 | int currentFrameIndex() const; |
111 | |
112 | QScriptContext *frameContext(int index) const; |
113 | QScriptContext *currentFrameContext() const; |
114 | |
115 | ScriptInfo *scriptInfo(QScriptContext *context) const; |
116 | |
117 | int listLineNumber() const; |
118 | void setListLineNumber(int lineNumber); |
119 | |
120 | QString readLine(); |
121 | void output(const QString &text); |
122 | void message(const QString &text); |
123 | void errorMessage(const QString &text); |
124 | |
125 | // attributes |
126 | QTextStream *m_defaultInputStream; |
127 | QTextStream *m_defaultOutputStream; |
128 | QTextStream *m_defaultErrorStream; |
129 | QTextStream *m_inputStream; |
130 | QTextStream *m_outputStream; |
131 | QTextStream *m_errorStream; |
132 | |
133 | ScriptBreakpointManager *m_bpManager; |
134 | Mode m_mode; |
135 | QMap<qint64, ScriptInfo*> m_scripts; |
136 | QMap<QScriptContext*, QStack<qint64> > m_contextProgramIds; |
137 | |
138 | QString m_lastInteractiveCommand; |
139 | QString m_commandPrefix; |
140 | int m_stepDepth; |
141 | int m_currentFrameIndex; |
142 | int m_listLineNumber; |
143 | |
144 | ScriptDebugger *q_ptr; |
145 | }; |
146 | |
147 | class ScriptInfo |
148 | { |
149 | public: |
150 | ScriptInfo(const QString &code, const QString &fileName, int lineNumber) |
151 | : m_code(code), m_fileName(fileName), m_lineNumber(lineNumber) |
152 | { } |
153 | |
154 | inline QString code() const |
155 | { return m_code; } |
156 | inline QString fileName() const |
157 | { return m_fileName; } |
158 | inline int lineNumber() const |
159 | { return m_lineNumber; } |
160 | |
161 | QString lineText(int lineNumber); |
162 | QMap<int, int> m_lineOffsets; |
163 | |
164 | private: |
165 | int lineOffset(int lineNumber); |
166 | |
167 | QString m_code; |
168 | QString m_fileName; |
169 | int m_lineNumber; |
170 | }; |
171 | |
172 | int ScriptInfo::lineOffset(int lineNumber) |
173 | { |
174 | QMap<int, int>::const_iterator it = m_lineOffsets.constFind(akey: lineNumber); |
175 | if (it != m_lineOffsets.constEnd()) |
176 | return it.value(); |
177 | |
178 | int offset; |
179 | it = m_lineOffsets.constFind(akey: lineNumber - 1); |
180 | if (it != m_lineOffsets.constEnd()) { |
181 | offset = it.value(); |
182 | offset = m_code.indexOf(c: QLatin1Char('\n'), from: offset); |
183 | if (offset != -1) |
184 | ++offset; |
185 | m_lineOffsets.insert(akey: lineNumber, avalue: offset); |
186 | } else { |
187 | int index; |
188 | it = m_lineOffsets.lowerBound(akey: lineNumber); |
189 | if (it != m_lineOffsets.constBegin()) |
190 | --it; |
191 | if (it != m_lineOffsets.constBegin()) { |
192 | index = it.key(); |
193 | offset = it.value(); |
194 | } else { |
195 | index = m_lineNumber; |
196 | offset = 0; |
197 | } |
198 | int j = index; |
199 | for ( ; j < lineNumber; ++j) { |
200 | m_lineOffsets.insert(akey: j, avalue: offset); |
201 | offset = m_code.indexOf(c: QLatin1Char('\n'), from: offset); |
202 | if (offset == -1) |
203 | break; |
204 | ++offset; |
205 | } |
206 | m_lineOffsets.insert(akey: j, avalue: offset); |
207 | } |
208 | return offset; |
209 | } |
210 | |
211 | QString ScriptInfo::lineText(int lineNumber) |
212 | { |
213 | int startOffset = lineOffset(lineNumber); |
214 | if (startOffset == -1) |
215 | return QString(); |
216 | int endOffset = lineOffset(lineNumber: lineNumber + 1); |
217 | if (endOffset == -1) |
218 | return m_code.mid(position: startOffset); |
219 | else |
220 | return m_code.mid(position: startOffset, n: endOffset - startOffset - 1); |
221 | } |
222 | |
223 | |
224 | |
225 | ScriptDebuggerPrivate::ScriptDebuggerPrivate(QScriptEngine *engine) |
226 | : QScriptEngineAgent(engine), m_mode(Run) |
227 | { |
228 | m_commandPrefix = QLatin1String("." ); |
229 | m_bpManager = new ScriptBreakpointManager; |
230 | m_defaultInputStream = new QTextStream(stdin); |
231 | m_defaultOutputStream = new QTextStream(stdout); |
232 | m_defaultErrorStream = new QTextStream(stderr); |
233 | m_inputStream = m_defaultInputStream; |
234 | m_outputStream = m_defaultOutputStream; |
235 | m_errorStream = m_defaultErrorStream; |
236 | } |
237 | |
238 | ScriptDebuggerPrivate::~ScriptDebuggerPrivate() |
239 | { |
240 | delete m_defaultInputStream; |
241 | delete m_defaultOutputStream; |
242 | delete m_defaultErrorStream; |
243 | delete m_bpManager; |
244 | qDeleteAll(c: m_scripts); |
245 | } |
246 | |
247 | QString ScriptDebuggerPrivate::readLine() |
248 | { |
249 | return m_inputStream->readLine(); |
250 | } |
251 | |
252 | void ScriptDebuggerPrivate::output(const QString &text) |
253 | { |
254 | *m_outputStream << text; |
255 | } |
256 | |
257 | void ScriptDebuggerPrivate::message(const QString &text) |
258 | { |
259 | *m_outputStream << text << endl; |
260 | m_outputStream->flush(); |
261 | } |
262 | |
263 | void ScriptDebuggerPrivate::errorMessage(const QString &text) |
264 | { |
265 | *m_errorStream << text << endl; |
266 | m_errorStream->flush(); |
267 | } |
268 | |
269 | void ScriptDebuggerPrivate::setMode(Mode mode) |
270 | { |
271 | m_mode = mode; |
272 | } |
273 | |
274 | ScriptDebuggerPrivate::Mode ScriptDebuggerPrivate::mode() const |
275 | { |
276 | return m_mode; |
277 | } |
278 | |
279 | QScriptContext *ScriptDebuggerPrivate::frameContext(int index) const |
280 | { |
281 | QScriptContext *ctx = engine()->currentContext(); |
282 | for (int i = 0; i < index; ++i) { |
283 | ctx = ctx->parentContext(); |
284 | if (!ctx) |
285 | break; |
286 | } |
287 | return ctx; |
288 | } |
289 | |
290 | int ScriptDebuggerPrivate::currentFrameIndex() const |
291 | { |
292 | return m_currentFrameIndex; |
293 | } |
294 | |
295 | void ScriptDebuggerPrivate::setCurrentFrameIndex(int index) |
296 | { |
297 | m_currentFrameIndex = index; |
298 | m_listLineNumber = -1; |
299 | } |
300 | |
301 | int ScriptDebuggerPrivate::listLineNumber() const |
302 | { |
303 | return m_listLineNumber; |
304 | } |
305 | |
306 | void ScriptDebuggerPrivate::setListLineNumber(int lineNumber) |
307 | { |
308 | m_listLineNumber = lineNumber; |
309 | } |
310 | |
311 | QScriptContext *ScriptDebuggerPrivate::currentFrameContext() const |
312 | { |
313 | return frameContext(index: currentFrameIndex()); |
314 | } |
315 | |
316 | int ScriptDebuggerPrivate::frameCount() const |
317 | { |
318 | int count = 0; |
319 | QScriptContext *ctx = engine()->currentContext(); |
320 | while (ctx) { |
321 | ++count; |
322 | ctx = ctx->parentContext(); |
323 | } |
324 | return count; |
325 | } |
326 | |
327 | ScriptInfo *ScriptDebuggerPrivate::scriptInfo(QScriptContext *context) const |
328 | { |
329 | QStack<qint64> pids = m_contextProgramIds.value(akey: context); |
330 | if (pids.isEmpty()) |
331 | return 0; |
332 | return m_scripts.value(akey: pids.top()); |
333 | } |
334 | |
335 | void ScriptDebuggerPrivate::interactive() |
336 | { |
337 | setCurrentFrameIndex(0); |
338 | |
339 | QString qsdbgPrompt = QString::fromLatin1(str: "(qsdbg) " ); |
340 | QString dotPrompt = QString::fromLatin1(str: ".... " ); |
341 | QString prompt = qsdbgPrompt; |
342 | |
343 | QString code; |
344 | |
345 | forever { |
346 | |
347 | *m_outputStream << prompt; |
348 | m_outputStream->flush(); |
349 | |
350 | QString line = readLine(); |
351 | |
352 | if (code.isEmpty() && (line.isEmpty() || line.startsWith(s: m_commandPrefix))) { |
353 | if (line.isEmpty()) |
354 | line = m_lastInteractiveCommand; |
355 | else |
356 | m_lastInteractiveCommand = line; |
357 | |
358 | QStringList parts = line.split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts); |
359 | if (!parts.isEmpty()) { |
360 | QString command = parts.takeFirst().mid(position: 1); |
361 | if (executeCommand(command, args: parts)) |
362 | break; |
363 | } |
364 | |
365 | } else { |
366 | if (line.isEmpty()) |
367 | continue; |
368 | |
369 | code += line; |
370 | code += QLatin1Char('\n'); |
371 | |
372 | if (line.trimmed().isEmpty()) { |
373 | continue; |
374 | |
375 | } else if (! engine()->canEvaluate(program: code)) { |
376 | prompt = dotPrompt; |
377 | |
378 | } else { |
379 | setMode(Run); |
380 | QScriptValue result = engine()->evaluate(program: code, fileName: QLatin1String("typein" )); |
381 | |
382 | code.clear(); |
383 | prompt = qsdbgPrompt; |
384 | |
385 | if (! result.isUndefined()) { |
386 | errorMessage(text: result.toString()); |
387 | engine()->clearExceptions(); |
388 | } |
389 | } |
390 | } |
391 | } |
392 | } |
393 | |
394 | bool ScriptDebuggerPrivate::executeCommand(const QString &command, const QStringList &args) |
395 | { |
396 | if (command == QLatin1String("c" ) |
397 | || command == QLatin1String("continue" )) { |
398 | setMode(Run); |
399 | return true; |
400 | } else if (command == QLatin1String("s" ) |
401 | || command == QLatin1String("step" )) { |
402 | setMode(StepInto); |
403 | return true; |
404 | } else if (command == QLatin1String("n" ) |
405 | || command == QLatin1String("next" )) { |
406 | setMode(StepOver); |
407 | m_stepDepth = 0; |
408 | return true; |
409 | } else if (command == QLatin1String("f" ) |
410 | || command == QLatin1String("frame" )) { |
411 | bool ok = false; |
412 | int index = args.value(i: 0).toInt(ok: &ok); |
413 | if (ok) { |
414 | if (index < 0 || index >= frameCount()) { |
415 | errorMessage(text: "No such frame." ); |
416 | } else { |
417 | setCurrentFrameIndex(index); |
418 | QScriptContext *ctx = currentFrameContext(); |
419 | message(text: QString::fromLatin1(str: "#%0 %1" ).arg(a: index).arg(a: ctx->toString())); |
420 | } |
421 | } |
422 | } else if (command == QLatin1String("bt" ) |
423 | || command == QLatin1String("backtrace" )) { |
424 | QScriptContext *ctx = engine()->currentContext(); |
425 | int index = -1; |
426 | while (ctx) { |
427 | ++index; |
428 | QString line = ctx->toString(); |
429 | message(text: QString::fromLatin1(str: "#%0 %1" ).arg(a: index).arg(a: line)); |
430 | ctx = ctx->parentContext(); |
431 | } |
432 | } else if (command == QLatin1String("up" )) { |
433 | int index = currentFrameIndex() + 1; |
434 | if (index == frameCount()) { |
435 | errorMessage(text: QString::fromLatin1(str: "Initial frame selected; you cannot go up." )); |
436 | } else { |
437 | setCurrentFrameIndex(index); |
438 | QScriptContext *ctx = currentFrameContext(); |
439 | message(text: QString::fromLatin1(str: "#%0 %1" ).arg(a: index).arg(a: ctx->toString())); |
440 | } |
441 | } else if (command == QLatin1String("down" )) { |
442 | int index = currentFrameIndex() - 1; |
443 | if (index < 0) { |
444 | errorMessage(text: QString::fromLatin1(str: "Bottom (innermost) frame selected; you cannot go down." )); |
445 | } else { |
446 | setCurrentFrameIndex(index); |
447 | QScriptContext *ctx = currentFrameContext(); |
448 | message(text: QString::fromLatin1(str: "#%0 %1" ).arg(a: index).arg(a: ctx->toString())); |
449 | } |
450 | } else if (command == QLatin1String("b" ) |
451 | || command == QLatin1String("break" )) { |
452 | QString str = args.value(i: 0); |
453 | int colonIndex = str.indexOf(c: QLatin1Char(':')); |
454 | if (colonIndex != -1) { |
455 | // filename:line form |
456 | QString fileName = str.left(n: colonIndex); |
457 | int lineNumber = str.mid(position: colonIndex+1).toInt(); |
458 | int id = m_bpManager->setBreakpoint(fileName, lineNumber); |
459 | message(text: QString::fromLatin1(str: "Breakpoint %0 at %1, line %2." ).arg(a: id+1).arg(a: fileName).arg(a: lineNumber)); |
460 | } else { |
461 | // function |
462 | QScriptValue fun = engine()->globalObject().property(name: str); |
463 | if (fun.isFunction()) { |
464 | int id = m_bpManager->setBreakpoint(fun); |
465 | message(text: QString::fromLatin1(str: "Breakpoint %0 at %1()." ).arg(a: id+1).arg(a: str)); |
466 | } |
467 | } |
468 | } else if (command == QLatin1String("d" ) |
469 | || command == QLatin1String("delete" )) { |
470 | int id = args.value(i: 0).toInt() - 1; |
471 | m_bpManager->removeBreakpoint(id); |
472 | } else if (command == QLatin1String("disable" )) { |
473 | int id = args.value(i: 0).toInt() - 1; |
474 | m_bpManager->setBreakpointEnabled(id, enabled: false); |
475 | } else if (command == QLatin1String("enable" )) { |
476 | int id = args.value(i: 0).toInt() - 1; |
477 | m_bpManager->setBreakpointEnabled(id, enabled: true); |
478 | } else if (command == QLatin1String("list" )) { |
479 | QScriptContext *ctx = currentFrameContext(); |
480 | ScriptInfo *progInfo = scriptInfo(context: ctx); |
481 | if (!progInfo) { |
482 | errorMessage(text: "No source text available for this frame." ); |
483 | } else { |
484 | QScriptContextInfo ctxInfo(ctx); |
485 | bool ok; |
486 | int line = args.value(i: 0).toInt(ok: &ok); |
487 | if (ok) { |
488 | line = qMax(a: 1, b: line - 5); |
489 | } else { |
490 | line = listLineNumber(); |
491 | if (line == -1) |
492 | line = qMax(a: progInfo->lineNumber(), b: ctxInfo.lineNumber() - 5); |
493 | } |
494 | for (int i = line; i < line + 10; ++i) { |
495 | message(text: QString::fromLatin1(str: "%0\t%1" ).arg(a: i).arg(a: progInfo->lineText(lineNumber: i))); |
496 | } |
497 | setListLineNumber(line + 10); |
498 | } |
499 | } else if (command == QLatin1String("info" )) { |
500 | if (args.size() < 1) { |
501 | } else { |
502 | QString what = args.value(i: 0); |
503 | if (what == QLatin1String("locals" )) { |
504 | QScriptValueIterator it(currentFrameContext()->activationObject()); |
505 | while (it.hasNext()) { |
506 | it.next(); |
507 | QString line; |
508 | line.append(s: it.name()); |
509 | line.append(s: QLatin1String(" = " )); |
510 | line.append(s: safeValueToString(value: it.value())); |
511 | message(text: line); |
512 | } |
513 | } |
514 | } |
515 | } else if (command == QLatin1String("help" )) { |
516 | message(text: "continue - continue execution\n" |
517 | "step - step into statement\n" |
518 | "next - step over statement\n" |
519 | "list - show where you are\n" |
520 | "\n" |
521 | "break - set breakpoint\n" |
522 | "delete - remove breakpoint\n" |
523 | "disable - disable breakpoint\n" |
524 | "enable - enable breakpoint\n" |
525 | "\n" |
526 | "backtrace - show backtrace\n" |
527 | "up - one frame up\n" |
528 | "down - one frame down\n" |
529 | "frame - set frame\n" |
530 | "\n" |
531 | "info locals - show local variables" ); |
532 | } else { |
533 | errorMessage(text: QString::fromLatin1(str: "Undefined command \"%0\". Try \"help\"." ) |
534 | .arg(a: command)); |
535 | } |
536 | |
537 | return false; |
538 | } |
539 | |
540 | |
541 | // QScriptEngineAgent interface |
542 | |
543 | void ScriptDebuggerPrivate::scriptLoad(qint64 id, const QString &program, |
544 | const QString &fileName, int lineNumber) |
545 | { |
546 | ScriptInfo *info = new ScriptInfo(program, fileName, lineNumber); |
547 | m_scripts.insert(akey: id, avalue: info); |
548 | } |
549 | |
550 | void ScriptDebuggerPrivate::scriptUnload(qint64 id) |
551 | { |
552 | ScriptInfo *info = m_scripts.take(akey: id); |
553 | delete info; |
554 | } |
555 | |
556 | void ScriptDebuggerPrivate::functionEntry(qint64 scriptId) |
557 | { |
558 | if (scriptId != -1) { |
559 | QScriptContext *ctx = engine()->currentContext(); |
560 | QStack<qint64> ids = m_contextProgramIds.value(akey: ctx); |
561 | ids.push(t: scriptId); |
562 | m_contextProgramIds.insert(akey: ctx, avalue: ids); |
563 | } |
564 | |
565 | if (mode() == StepOver) |
566 | ++m_stepDepth; |
567 | } |
568 | |
569 | void ScriptDebuggerPrivate::functionExit(qint64 scriptId, |
570 | const QScriptValue &/*returnValue*/) |
571 | { |
572 | if (scriptId != -1) { |
573 | QScriptContext *ctx = engine()->currentContext(); |
574 | QStack<qint64> ids = m_contextProgramIds.value(akey: ctx); |
575 | Q_ASSERT(!ids.isEmpty()); |
576 | Q_ASSERT(ids.top() == scriptId); |
577 | ids.pop(); |
578 | m_contextProgramIds.insert(akey: ctx, avalue: ids); |
579 | } |
580 | |
581 | if (mode() == StepOver) |
582 | --m_stepDepth; |
583 | } |
584 | |
585 | void ScriptDebuggerPrivate::positionChange(qint64 scriptId, |
586 | int lineNumber, int /*columnNumber*/) |
587 | { |
588 | ScriptInfo *info = 0; |
589 | bool enterInteractiveMode = false; |
590 | |
591 | if (m_bpManager->hasBreakpoints()) { |
592 | // check if we hit a breakpoint |
593 | info = m_scripts.value(akey: scriptId); |
594 | QScriptContext *ctx = engine()->currentContext(); |
595 | QScriptContextInfo ctxInfo(ctx); |
596 | QScriptValue callee = ctx->callee(); |
597 | |
598 | // try fileName:lineNumber |
599 | int bpid = m_bpManager->findBreakpoint(fileName: info->fileName(), lineNumber); |
600 | if ((bpid != -1) && m_bpManager->isBreakpointEnabled(id: bpid)) { |
601 | message(text: QString::fromLatin1(str: "Breakpoint %0 at %1:%2" ) |
602 | .arg(a: bpid + 1).arg(a: info->fileName()).arg(a: lineNumber)); |
603 | if (m_bpManager->isBreakpointSingleShot(id: bpid)) |
604 | m_bpManager->removeBreakpoint(id: bpid); |
605 | } |
606 | if (bpid == -1) { |
607 | // try function |
608 | bpid = m_bpManager->findBreakpoint(function: callee); |
609 | if ((bpid != -1) && m_bpManager->isBreakpointEnabled(id: bpid)) { |
610 | message(text: QString::fromLatin1(str: "Breakpoint %0, %1()" ) |
611 | .arg(a: bpid + 1).arg(a: ctxInfo.functionName())); |
612 | if (m_bpManager->isBreakpointSingleShot(id: bpid)) |
613 | m_bpManager->removeBreakpoint(id: bpid); |
614 | } |
615 | } |
616 | if ((bpid == -1) && !ctxInfo.functionName().isEmpty()) { |
617 | // try functionName:fileName |
618 | bpid = m_bpManager->findBreakpoint(functionName: ctxInfo.functionName(), fileName: ctxInfo.fileName()); |
619 | if ((bpid != -1) && m_bpManager->isBreakpointEnabled(id: bpid)) { |
620 | message(text: QString::fromLatin1(str: "Breakpoint %0, %1():%2" ).arg(a: bpid + 1) |
621 | .arg(a: ctxInfo.functionName()).arg(a: ctxInfo.fileName())); |
622 | if (m_bpManager->isBreakpointSingleShot(id: bpid)) |
623 | m_bpManager->removeBreakpoint(id: bpid); |
624 | } |
625 | } |
626 | |
627 | enterInteractiveMode = (bpid != -1); |
628 | } |
629 | |
630 | switch (mode()) { |
631 | case Run: |
632 | break; |
633 | |
634 | case StepInto: |
635 | enterInteractiveMode = true; |
636 | break; |
637 | |
638 | case StepOver: |
639 | enterInteractiveMode = enterInteractiveMode || (m_stepDepth <= 0); |
640 | break; |
641 | } |
642 | |
643 | if (enterInteractiveMode) { |
644 | if (!info) |
645 | info = m_scripts.value(akey: scriptId); |
646 | Q_ASSERT(info); |
647 | message(text: QString::fromLatin1(str: "%0\t%1" ).arg(a: lineNumber).arg(a: info->lineText(lineNumber))); |
648 | interactive(); |
649 | } |
650 | } |
651 | |
652 | void ScriptDebuggerPrivate::exceptionThrow(qint64 /*scriptId*/, |
653 | const QScriptValue &exception, |
654 | bool hasHandler) |
655 | { |
656 | if (!hasHandler) { |
657 | errorMessage(text: QString::fromLatin1(str: "uncaught exception: %0" ).arg(a: exception.toString())); |
658 | QScriptContext *ctx = engine()->currentContext(); |
659 | int lineNumber = QScriptContextInfo(ctx).lineNumber(); |
660 | ScriptInfo *info = scriptInfo(context: ctx); |
661 | QString lineText = info ? info->lineText(lineNumber) : QString("(no source text available)" ); |
662 | message(text: QString::fromLatin1(str: "%0\t%1" ).arg(a: lineNumber).arg(a: lineText)); |
663 | interactive(); |
664 | } |
665 | } |
666 | |
667 | |
668 | |
669 | ScriptDebugger::ScriptDebugger(QScriptEngine *engine) |
670 | : d_ptr(new ScriptDebuggerPrivate(engine)) |
671 | { |
672 | d_ptr->q_ptr = this; |
673 | engine->setAgent(d_ptr); |
674 | } |
675 | |
676 | ScriptDebugger::ScriptDebugger(QScriptEngine *engine, ScriptDebuggerPrivate &dd) |
677 | : d_ptr(&dd) |
678 | { |
679 | d_ptr->q_ptr = this; |
680 | engine->setAgent(d_ptr); |
681 | } |
682 | |
683 | ScriptDebugger::~ScriptDebugger() |
684 | { |
685 | delete d_ptr; |
686 | d_ptr = 0; |
687 | } |
688 | |
689 | void ScriptDebugger::breakAtNextStatement() |
690 | { |
691 | Q_D(ScriptDebugger); |
692 | d->setMode(ScriptDebuggerPrivate::StepInto); |
693 | } |
694 | |
695 | void ScriptDebugger::setBreakpoint(const QString &fileName, int lineNumber) |
696 | { |
697 | Q_D(ScriptDebugger); |
698 | d->m_bpManager->setBreakpoint(fileName, lineNumber); |
699 | } |
700 | |
701 | void ScriptDebugger::setBreakpoint(const QString &functionName, const QString &fileName) |
702 | { |
703 | Q_D(ScriptDebugger); |
704 | d->m_bpManager->setBreakpoint(functionName, fileName); |
705 | } |
706 | |
707 | void ScriptDebugger::setBreakpoint(const QScriptValue &function) |
708 | { |
709 | Q_D(ScriptDebugger); |
710 | d->m_bpManager->setBreakpoint(function); |
711 | } |
712 | |
713 | QTextStream *ScriptDebugger::inputStream() const |
714 | { |
715 | Q_D(const ScriptDebugger); |
716 | return d->m_inputStream; |
717 | } |
718 | |
719 | void ScriptDebugger::setInputStream(QTextStream *inputStream) |
720 | { |
721 | Q_D(ScriptDebugger); |
722 | d->m_inputStream = inputStream; |
723 | } |
724 | |
725 | QTextStream *ScriptDebugger::outputStream() const |
726 | { |
727 | Q_D(const ScriptDebugger); |
728 | return d->m_outputStream; |
729 | } |
730 | |
731 | void ScriptDebugger::setOutputStream(QTextStream *outputStream) |
732 | { |
733 | Q_D(ScriptDebugger); |
734 | d->m_outputStream = outputStream; |
735 | } |
736 | |
737 | QTextStream *ScriptDebugger::errorStream() const |
738 | { |
739 | Q_D(const ScriptDebugger); |
740 | return d->m_errorStream; |
741 | } |
742 | |
743 | void ScriptDebugger::setErrorStream(QTextStream *errorStream) |
744 | { |
745 | Q_D(ScriptDebugger); |
746 | d->m_errorStream = errorStream; |
747 | } |
748 | |