1/****************************************************************************
2**
3** Copyright (C) 2018 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtSCriptTools 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 "qscriptdebuggercommandexecutor_p.h"
41
42#include "qscriptdebuggerbackend_p.h"
43#include "qscriptdebuggercommand_p.h"
44#include "qscriptdebuggerresponse_p.h"
45#include "qscriptdebuggervalue_p.h"
46#include "qscriptdebuggervalueproperty_p.h"
47#include "qscriptbreakpointdata_p.h"
48#include "qscriptobjectsnapshot_p.h"
49#include "qscriptdebuggerobjectsnapshotdelta_p.h"
50
51#include <QtCore/qstringlist.h>
52#include <QtScript/qscriptengine.h>
53#include <QtScript/qscriptcontextinfo.h>
54#include <QtScript/qscriptvalueiterator.h>
55#include <QtCore/qdebug.h>
56
57#include <algorithm>
58
59Q_DECLARE_METATYPE(QScriptScriptsDelta)
60Q_DECLARE_METATYPE(QScriptDebuggerValueProperty)
61Q_DECLARE_METATYPE(QScriptDebuggerValuePropertyList)
62Q_DECLARE_METATYPE(QScriptDebuggerObjectSnapshotDelta)
63
64QT_BEGIN_NAMESPACE
65
66/*!
67 \since 4.5
68 \class QScriptDebuggerCommandExecutor
69 \internal
70
71 \brief The QScriptDebuggerCommandExecutor applies debugger commands to a back-end.
72
73 The execute() function takes a command (typically produced by a
74 QScriptDebuggerFrontend) and applies it to a QScriptDebuggerBackend.
75
76 \sa QScriptDebuggerCommmand
77*/
78
79class QScriptDebuggerCommandExecutorPrivate
80{
81public:
82 QScriptDebuggerCommandExecutorPrivate();
83 ~QScriptDebuggerCommandExecutorPrivate();
84};
85
86QScriptDebuggerCommandExecutorPrivate::QScriptDebuggerCommandExecutorPrivate()
87{
88}
89
90QScriptDebuggerCommandExecutorPrivate::~QScriptDebuggerCommandExecutorPrivate()
91{
92}
93
94QScriptDebuggerCommandExecutor::QScriptDebuggerCommandExecutor()
95 : d_ptr(new QScriptDebuggerCommandExecutorPrivate())
96{
97}
98
99QScriptDebuggerCommandExecutor::~QScriptDebuggerCommandExecutor()
100{
101}
102
103static bool isPrefixOf(const QString &prefix, const QString &what)
104{
105 return ((what.length() > prefix.length())
106 && what.startsWith(s: prefix));
107}
108
109/*!
110 Applies the given \a command to the given \a backend.
111*/
112QScriptDebuggerResponse QScriptDebuggerCommandExecutor::execute(
113 QScriptDebuggerBackend *backend,
114 const QScriptDebuggerCommand &command)
115{
116 QScriptDebuggerResponse response;
117 switch (command.type()) {
118 case QScriptDebuggerCommand::None:
119 break;
120
121 case QScriptDebuggerCommand::Interrupt:
122 backend->interruptEvaluation();
123 break;
124
125 case QScriptDebuggerCommand::Continue:
126 if (backend->engine()->isEvaluating()) {
127 backend->continueEvalution();
128 response.setAsync(true);
129 }
130 break;
131
132 case QScriptDebuggerCommand::StepInto: {
133 QVariant attr = command.attribute(attribute: QScriptDebuggerCommand::StepCount);
134 int count = attr.isValid() ? attr.toInt() : 1;
135 backend->stepInto(count);
136 response.setAsync(true);
137 } break;
138
139 case QScriptDebuggerCommand::StepOver: {
140 QVariant attr = command.attribute(attribute: QScriptDebuggerCommand::StepCount);
141 int count = attr.isValid() ? attr.toInt() : 1;
142 backend->stepOver(count);
143 response.setAsync(true);
144 } break;
145
146 case QScriptDebuggerCommand::StepOut:
147 backend->stepOut();
148 response.setAsync(true);
149 break;
150
151 case QScriptDebuggerCommand::RunToLocation:
152 backend->runToLocation(fileName: command.fileName(), lineNumber: command.lineNumber());
153 response.setAsync(true);
154 break;
155
156 case QScriptDebuggerCommand::RunToLocationByID:
157 backend->runToLocation(scriptId: command.scriptId(), lineNumber: command.lineNumber());
158 response.setAsync(true);
159 break;
160
161 case QScriptDebuggerCommand::ForceReturn: {
162 int contextIndex = command.contextIndex();
163 QScriptDebuggerValue value = command.scriptValue();
164 QScriptEngine *engine = backend->engine();
165 QScriptValue realValue = value.toScriptValue(engine);
166 backend->returnToCaller(contextIndex, value: realValue);
167 response.setAsync(true);
168 } break;
169
170 case QScriptDebuggerCommand::Resume:
171 backend->resume();
172 response.setAsync(true);
173 break;
174
175 case QScriptDebuggerCommand::SetBreakpoint: {
176 QScriptBreakpointData data = command.breakpointData();
177 if (!data.isValid())
178 data = QScriptBreakpointData(command.fileName(), command.lineNumber());
179 int id = backend->setBreakpoint(data);
180 response.setResult(id);
181 } break;
182
183 case QScriptDebuggerCommand::DeleteBreakpoint: {
184 int id = command.breakpointId();
185 if (!backend->deleteBreakpoint(id))
186 response.setError(QScriptDebuggerResponse::InvalidBreakpointID);
187 } break;
188
189 case QScriptDebuggerCommand::DeleteAllBreakpoints:
190 backend->deleteAllBreakpoints();
191 break;
192
193 case QScriptDebuggerCommand::GetBreakpoints: {
194 QScriptBreakpointMap bps = backend->breakpoints();
195 if (!bps.isEmpty())
196 response.setResult(bps);
197 } break;
198
199 case QScriptDebuggerCommand::GetBreakpointData: {
200 int id = command.breakpointId();
201 QScriptBreakpointData data = backend->breakpointData(id);
202 if (data.isValid())
203 response.setResult(data);
204 else
205 response.setError(QScriptDebuggerResponse::InvalidBreakpointID);
206 } break;
207
208 case QScriptDebuggerCommand::SetBreakpointData: {
209 int id = command.breakpointId();
210 QScriptBreakpointData data = command.breakpointData();
211 if (!backend->setBreakpointData(id, data))
212 response.setError(QScriptDebuggerResponse::InvalidBreakpointID);
213 } break;
214
215 case QScriptDebuggerCommand::GetScripts: {
216 QScriptScriptMap scripts = backend->scripts();
217 if (!scripts.isEmpty())
218 response.setResult(scripts);
219 } break;
220
221 case QScriptDebuggerCommand::GetScriptData: {
222 qint64 id = command.scriptId();
223 QScriptScriptData data = backend->scriptData(id);
224 if (data.isValid())
225 response.setResult(data);
226 else
227 response.setError(QScriptDebuggerResponse::InvalidScriptID);
228 } break;
229
230 case QScriptDebuggerCommand::ScriptsCheckpoint:
231 backend->scriptsCheckpoint();
232 response.setResult(QVariant::fromValue(value: backend->scriptsDelta()));
233 break;
234
235 case QScriptDebuggerCommand::GetScriptsDelta:
236 response.setResult(QVariant::fromValue(value: backend->scriptsDelta()));
237 break;
238
239 case QScriptDebuggerCommand::ResolveScript:
240 response.setResult(backend->resolveScript(fileName: command.fileName()));
241 break;
242
243 case QScriptDebuggerCommand::GetBacktrace:
244 response.setResult(backend->backtrace());
245 break;
246
247 case QScriptDebuggerCommand::GetContextCount:
248 response.setResult(backend->contextCount());
249 break;
250
251 case QScriptDebuggerCommand::GetContextState: {
252 QScriptContext *ctx = backend->context(index: command.contextIndex());
253 if (ctx)
254 response.setResult(static_cast<int>(ctx->state()));
255 else
256 response.setError(QScriptDebuggerResponse::InvalidContextIndex);
257 } break;
258
259 case QScriptDebuggerCommand::GetContextID: {
260 int idx = command.contextIndex();
261 if ((idx >= 0) && (idx < backend->contextCount()))
262 response.setResult(backend->contextIds()[idx]);
263 else
264 response.setError(QScriptDebuggerResponse::InvalidContextIndex);
265 } break;
266
267 case QScriptDebuggerCommand::GetContextInfo: {
268 QScriptContext *ctx = backend->context(index: command.contextIndex());
269 if (ctx)
270 response.setResult(QScriptContextInfo(ctx));
271 else
272 response.setError(QScriptDebuggerResponse::InvalidContextIndex);
273 } break;
274
275 case QScriptDebuggerCommand::GetThisObject: {
276 QScriptContext *ctx = backend->context(index: command.contextIndex());
277 if (ctx)
278 response.setResult(ctx->thisObject());
279 else
280 response.setError(QScriptDebuggerResponse::InvalidContextIndex);
281 } break;
282
283 case QScriptDebuggerCommand::GetActivationObject: {
284 QScriptContext *ctx = backend->context(index: command.contextIndex());
285 if (ctx)
286 response.setResult(ctx->activationObject());
287 else
288 response.setError(QScriptDebuggerResponse::InvalidContextIndex);
289 } break;
290
291 case QScriptDebuggerCommand::GetScopeChain: {
292 QScriptContext *ctx = backend->context(index: command.contextIndex());
293 if (ctx) {
294 QScriptDebuggerValueList dest;
295 QScriptValueList src = ctx->scopeChain();
296 for (int i = 0; i < src.size(); ++i)
297 dest.append(t: src.at(i));
298 response.setResult(dest);
299 } else {
300 response.setError(QScriptDebuggerResponse::InvalidContextIndex);
301 }
302 } break;
303
304 case QScriptDebuggerCommand::ContextsCheckpoint: {
305 response.setResult(QVariant::fromValue(value: backend->contextsCheckpoint()));
306 } break;
307
308 case QScriptDebuggerCommand::GetPropertyExpressionValue: {
309 QScriptContext *ctx = backend->context(index: command.contextIndex());
310 int lineNumber = command.lineNumber();
311 QVariant attr = command.attribute(attribute: QScriptDebuggerCommand::UserAttribute);
312 QStringList path = attr.toStringList();
313 if (!ctx || path.isEmpty())
314 break;
315 QScriptContextInfo ctxInfo(ctx);
316 if (ctx->callee().isValid()
317 && ((lineNumber < ctxInfo.functionStartLineNumber())
318 || (lineNumber > ctxInfo.functionEndLineNumber()))) {
319 break;
320 }
321 QScriptValueList objects;
322 int pathIndex = 0;
323 if (path.at(i: 0) == QLatin1String("this")) {
324 objects.append(t: ctx->thisObject());
325 ++pathIndex;
326 } else {
327 objects << ctx->scopeChain();
328 }
329 for (int i = 0; i < objects.size(); ++i) {
330 QScriptValue val = objects.at(i);
331 for (int j = pathIndex; val.isValid() && (j < path.size()); ++j) {
332 val = val.property(name: path.at(i: j));
333 }
334 if (val.isValid()) {
335 bool hadException = (ctx->state() == QScriptContext::ExceptionState);
336 QString str = val.toString();
337 if (!hadException && backend->engine()->hasUncaughtException())
338 backend->engine()->clearExceptions();
339 response.setResult(str);
340 break;
341 }
342 }
343 } break;
344
345 case QScriptDebuggerCommand::GetCompletions: {
346 QScriptContext *ctx = backend->context(index: command.contextIndex());
347 QVariant attr = command.attribute(attribute: QScriptDebuggerCommand::UserAttribute);
348 QStringList path = attr.toStringList();
349 if (!ctx || path.isEmpty())
350 break;
351 QScriptValueList objects;
352 QString prefix = path.last();
353 QSet<QString> matches;
354 if (path.size() > 1) {
355 const QString &topLevelIdent = path.at(i: 0);
356 QScriptValue obj;
357 if (topLevelIdent == QLatin1String("this")) {
358 obj = ctx->thisObject();
359 } else {
360 QScriptValueList scopeChain;
361 scopeChain = ctx->scopeChain();
362 for (int i = 0; i < scopeChain.size(); ++i) {
363 QScriptValue oo = scopeChain.at(i).property(name: topLevelIdent);
364 if (oo.isObject()) {
365 obj = oo;
366 break;
367 }
368 }
369 }
370 for (int i = 1; obj.isObject() && (i < path.size()-1); ++i)
371 obj = obj.property(name: path.at(i));
372 if (obj.isValid())
373 objects.append(t: obj);
374 } else {
375 objects << ctx->scopeChain();
376 QStringList keywords;
377 keywords.append(t: QString::fromLatin1(str: "this"));
378 keywords.append(t: QString::fromLatin1(str: "true"));
379 keywords.append(t: QString::fromLatin1(str: "false"));
380 keywords.append(t: QString::fromLatin1(str: "null"));
381 for (int i = 0; i < keywords.size(); ++i) {
382 const QString &kwd = keywords.at(i);
383 if (isPrefixOf(prefix, what: kwd))
384 matches.insert(value: kwd);
385 }
386 }
387
388 for (int i = 0; i < objects.size(); ++i) {
389 QScriptValue obj = objects.at(i);
390 while (obj.isObject()) {
391 QScriptValueIterator it(obj);
392 while (it.hasNext()) {
393 it.next();
394 QString propertyName = it.name();
395 if (isPrefixOf(prefix, what: propertyName))
396 matches.insert(value: propertyName);
397 }
398 obj = obj.prototype();
399 }
400 }
401 QStringList matchesList = matches.toList();
402 std::stable_sort(first: matchesList.begin(), last: matchesList.end());
403 response.setResult(matchesList);
404 } break;
405
406 case QScriptDebuggerCommand::NewScriptObjectSnapshot: {
407 int id = backend->newScriptObjectSnapshot();
408 response.setResult(id);
409 } break;
410
411 case QScriptDebuggerCommand::ScriptObjectSnapshotCapture: {
412 int id = command.snapshotId();
413 QScriptObjectSnapshot *snap = backend->scriptObjectSnapshot(id);
414 Q_ASSERT(snap != 0);
415 QScriptDebuggerValue object = command.scriptValue();
416 Q_ASSERT(object.type() == QScriptDebuggerValue::ObjectValue);
417 QScriptEngine *engine = backend->engine();
418 QScriptValue realObject = object.toScriptValue(engine);
419 Q_ASSERT(realObject.isObject());
420 QScriptObjectSnapshot::Delta delta = snap->capture(object: realObject);
421 QScriptDebuggerObjectSnapshotDelta result;
422 result.removedProperties = delta.removedProperties;
423 bool didIgnoreExceptions = backend->ignoreExceptions();
424 backend->setIgnoreExceptions(true);
425 for (int i = 0; i < delta.changedProperties.size(); ++i) {
426 const QScriptValueProperty &src = delta.changedProperties.at(i);
427 bool hadException = engine->hasUncaughtException();
428 QString str = src.value().toString();
429 if (!hadException && engine->hasUncaughtException())
430 engine->clearExceptions();
431 QScriptDebuggerValueProperty dest(src.name(), src.value(), str, src.flags());
432 result.changedProperties.append(t: dest);
433 }
434 for (int j = 0; j < delta.addedProperties.size(); ++j) {
435 const QScriptValueProperty &src = delta.addedProperties.at(i: j);
436 bool hadException = engine->hasUncaughtException();
437 QString str = src.value().toString();
438 if (!hadException && engine->hasUncaughtException())
439 engine->clearExceptions();
440 QScriptDebuggerValueProperty dest(src.name(), src.value(), str, src.flags());
441 result.addedProperties.append(t: dest);
442 }
443 backend->setIgnoreExceptions(didIgnoreExceptions);
444 response.setResult(QVariant::fromValue(value: result));
445 } break;
446
447 case QScriptDebuggerCommand::DeleteScriptObjectSnapshot: {
448 int id = command.snapshotId();
449 backend->deleteScriptObjectSnapshot(id);
450 } break;
451
452 case QScriptDebuggerCommand::NewScriptValueIterator: {
453 QScriptDebuggerValue object = command.scriptValue();
454 Q_ASSERT(object.type() == QScriptDebuggerValue::ObjectValue);
455 QScriptEngine *engine = backend->engine();
456 QScriptValue realObject = object.toScriptValue(engine);
457 Q_ASSERT(realObject.isObject());
458 int id = backend->newScriptValueIterator(object: realObject);
459 response.setResult(id);
460 } break;
461
462 case QScriptDebuggerCommand::GetPropertiesByIterator: {
463 int id = command.iteratorId();
464 int count = 1000;
465 QScriptValueIterator *it = backend->scriptValueIterator(id);
466 Q_ASSERT(it != 0);
467 QScriptDebuggerValuePropertyList props;
468 for (int i = 0; (i < count) && it->hasNext(); ++i) {
469 it->next();
470 QString name = it->name();
471 QScriptValue value = it->value();
472 QString valueAsString = value.toString();
473 QScriptValue::PropertyFlags flags = it->flags();
474 QScriptDebuggerValueProperty prp(name, value, valueAsString, flags);
475 props.append(t: prp);
476 }
477 response.setResult(props);
478 } break;
479
480 case QScriptDebuggerCommand::DeleteScriptValueIterator: {
481 int id = command.iteratorId();
482 backend->deleteScriptValueIterator(id);
483 } break;
484
485 case QScriptDebuggerCommand::Evaluate: {
486 int contextIndex = command.contextIndex();
487 QString program = command.program();
488 QString fileName = command.fileName();
489 int lineNumber = command.lineNumber();
490 backend->evaluate(contextIndex, program, fileName, lineNumber);
491 response.setAsync(true);
492 } break;
493
494 case QScriptDebuggerCommand::ScriptValueToString: {
495 QScriptDebuggerValue value = command.scriptValue();
496 QScriptEngine *engine = backend->engine();
497 QScriptValue realValue = value.toScriptValue(engine);
498 response.setResult(realValue.toString());
499 } break;
500
501 case QScriptDebuggerCommand::SetScriptValueProperty: {
502 QScriptDebuggerValue object = command.scriptValue();
503 QScriptEngine *engine = backend->engine();
504 QScriptValue realObject = object.toScriptValue(engine);
505 QScriptDebuggerValue value = command.subordinateScriptValue();
506 QScriptValue realValue = value.toScriptValue(engine);
507 QString name = command.name();
508 realObject.setProperty(name, value: realValue);
509 } break;
510
511 case QScriptDebuggerCommand::ClearExceptions:
512 backend->engine()->clearExceptions();
513 break;
514
515 case QScriptDebuggerCommand::UserCommand:
516 case QScriptDebuggerCommand::MaxUserCommand:
517 break;
518 }
519 return response;
520}
521
522QT_END_NAMESPACE
523

source code of qtscript/src/scripttools/debugging/qscriptdebuggercommandexecutor.cpp