Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmljavascriptexpression.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant
4
7
8#include <private/qqmlexpression_p.h>
9#include <private/qv4context_p.h>
10#include <private/qv4value_p.h>
11#include <private/qv4functionobject_p.h>
12#include <private/qv4script_p.h>
13#include <private/qv4errorobject_p.h>
14#include <private/qv4scopedvalue_p.h>
15#include <private/qv4jscall_p.h>
16#include <private/qqmlglobal_p.h>
17#include <private/qv4qobjectwrapper_p.h>
18#include <private/qqmlbuiltinfunctions_p.h>
19#include <private/qqmlsourcecoordinate_p.h>
20#include <private/qqmlabstractbinding_p.h>
21#include <private/qqmlpropertybinding_p.h>
22#include <private/qproperty_p.h>
23
25
26bool QQmlDelayedError::addError(QQmlEnginePrivate *e)
27{
28 if (!e) return false;
29
30 if (e->inProgressCreations == 0) return false; // Not in construction
31
32 if (prevError) return true; // Already in error chain
33
34 prevError = &e->erroredBindings;
35 nextError = e->erroredBindings;
36 e->erroredBindings = this;
37 if (nextError) nextError->prevError = &nextError;
38
39 return true;
40}
41
42void QQmlDelayedError::setErrorLocation(const QQmlSourceLocation &sourceLocation)
43{
44 m_error.setUrl(QUrl(sourceLocation.sourceFile));
45 m_error.setLine(qmlConvertSourceCoordinate<quint16, int>(sourceLocation.line));
46 m_error.setColumn(qmlConvertSourceCoordinate<quint16, int>(sourceLocation.column));
47}
48
49void QQmlDelayedError::setErrorDescription(const QString &description)
50{
51 m_error.setDescription(description);
52}
53
54void QQmlDelayedError::setErrorObject(QObject *object)
55{
56 m_error.setObject(object);
57}
58
59void QQmlDelayedError::catchJavaScriptException(QV4::ExecutionEngine *engine)
60{
61 m_error = engine->catchExceptionAsQmlError();
62}
63
64
65QQmlJavaScriptExpression::QQmlJavaScriptExpression()
66 : m_context(nullptr),
67 m_prevExpression(nullptr),
68 m_nextExpression(nullptr),
69 m_v4Function(nullptr)
70{
71}
72
73QQmlJavaScriptExpression::~QQmlJavaScriptExpression()
74{
75 if (m_prevExpression) {
76 *m_prevExpression = m_nextExpression;
77 if (m_nextExpression)
78 m_nextExpression->m_prevExpression = m_prevExpression;
79 }
80
81 while (qpropertyChangeTriggers) {
82 auto current = qpropertyChangeTriggers;
83 qpropertyChangeTriggers = current->next;
84 QRecyclePool<TriggerList>::Delete(current);
85 }
86
87 clearActiveGuards();
88 clearError();
89 if (m_scopeObject.isT2()) // notify DeleteWatcher of our deletion.
90 m_scopeObject.asT2()->_s = nullptr;
91}
92
93QString QQmlJavaScriptExpression::expressionIdentifier() const
94{
95 if (auto f = function()) {
96 QString url = f->sourceFile();
97 uint lineNumber = f->compiledFunction->location.line();
98 uint columnNumber = f->compiledFunction->location.column();
99 return url + QString::asprintf(":%u:%u", lineNumber, columnNumber);
100 }
101
102 return QStringLiteral("[native code]");
103}
104
105void QQmlJavaScriptExpression::setNotifyOnValueChanged(bool v)
106{
107 activeGuards.setTag(v ? NotifyOnValueChanged : NoGuardTag);
108 if (!v)
109 clearActiveGuards();
110}
111
112void QQmlJavaScriptExpression::resetNotifyOnValueChanged()
113{
114 setNotifyOnValueChanged(false);
115}
116
117QQmlSourceLocation QQmlJavaScriptExpression::sourceLocation() const
118{
119 if (m_v4Function)
120 return m_v4Function->sourceLocation();
121 return QQmlSourceLocation();
122}
123
124void QQmlJavaScriptExpression::setContext(const QQmlRefPointer<QQmlContextData> &context)
125{
126 if (m_prevExpression) {
127 *m_prevExpression = m_nextExpression;
128 if (m_nextExpression)
129 m_nextExpression->m_prevExpression = m_prevExpression;
130 m_prevExpression = nullptr;
131 m_nextExpression = nullptr;
132 }
133
134 m_context = context.data();
135
136 if (context)
137 context->addExpression(this);
138}
139
140void QQmlJavaScriptExpression::refresh()
141{
142}
143
144QV4::ReturnedValue QQmlJavaScriptExpression::evaluate(bool *isUndefined)
145{
146 QQmlEngine *qmlengine = engine();
147 if (!qmlengine) {
148 if (isUndefined)
149 *isUndefined = true;
150 return QV4::Encode::undefined();
151 }
152
153 QV4::Scope scope(qmlengine->handle());
154 QV4::JSCallArguments jsCall(scope);
155
156 return evaluate(jsCall.callData(scope), isUndefined);
157}
158
160{
162public:
167 {
172
175 }
176
178 {
179 if (capture.errorString) {
180 for (int ii = 0; ii < capture.errorString->size(); ++ii)
182 delete capture.errorString;
183 capture.errorString = nullptr;
184 }
185
187 g->Delete();
188
190 }
191
192 bool catchException(const QV4::Scope &scope) const
193 {
194 if (scope.hasException()) {
195 if (watcher.wasDeleted())
196 scope.engine->catchException(); // ignore exception
197 else
199 return true;
200 }
201
204 return false;
205 }
206
207private:
209 QQmlPropertyCapture capture;
210 QQmlEnginePrivate *ep;
211 QQmlPropertyCapture *lastPropertyCapture;
212};
213
214QV4::ReturnedValue QQmlJavaScriptExpression::evaluate(QV4::CallData *callData, bool *isUndefined)
215{
216 QQmlEngine *qmlEngine = engine();
217 QV4::Function *v4Function = function();
218 if (!v4Function || !qmlEngine) {
219 if (isUndefined)
220 *isUndefined = true;
221 return QV4::Encode::undefined();
222 }
223
224 // All code that follows must check with watcher before it accesses data members
225 // incase we have been deleted.
226 QQmlJavaScriptExpressionCapture capture(this, qmlEngine);
227
228 QV4::Scope scope(qmlEngine->handle());
229
230 if (QObject *thisObject = scopeObject()) {
231 callData->thisObject = QV4::QObjectWrapper::wrap(scope.engine, thisObject);
232 if (callData->thisObject.isNullOrUndefined())
233 callData->thisObject = scope.engine->globalObject;
234 } else {
235 callData->thisObject = scope.engine->globalObject;
236 }
237
238 Q_ASSERT(m_qmlScope.valueRef());
239 QV4::ScopedValue result(scope, v4Function->call(
240 &(callData->thisObject.asValue<QV4::Value>()),
241 callData->argValues<QV4::Value>(), callData->argc(),
242 static_cast<QV4::ExecutionContext *>(m_qmlScope.valueRef())));
243
244 if (capture.catchException(scope)) {
245 if (isUndefined)
246 *isUndefined = true;
247 } else if (isUndefined) {
248 *isUndefined = result->isUndefined();
249 }
250
251 return result->asReturnedValue();
252}
253
254bool QQmlJavaScriptExpression::evaluate(void **a, const QMetaType *types, int argc)
255{
256 // All code that follows must check with watcher before it accesses data members
257 // incase we have been deleted.
258 QQmlEngine *qmlEngine = engine();
259
260 // If there is no engine, we have no way to evaluate anything.
261 // This can happen on destruction.
262 if (!qmlEngine)
263 return false;
264
265 QQmlJavaScriptExpressionCapture capture(this, qmlEngine);
266
267 QV4::Scope scope(qmlEngine->handle());
268
269 Q_ASSERT(m_qmlScope.valueRef());
270 Q_ASSERT(function());
271 const bool resultIsDefined = function()->call(
272 scopeObject(), a, types, argc,
273 static_cast<QV4::ExecutionContext *>(m_qmlScope.valueRef()));
274
275 return !capture.catchException(scope) && resultIsDefined;
276}
277
278void QQmlPropertyCapture::captureProperty(QQmlNotifier *n)
279{
280 if (watcher->wasDeleted())
281 return;
282
283 Q_ASSERT(expression);
284 // Try and find a matching guard
285 while (!guards.isEmpty() && !guards.first()->isConnected(n))
286 guards.takeFirst()->Delete();
287
288 QQmlJavaScriptExpressionGuard *g = nullptr;
289 if (!guards.isEmpty()) {
290 g = guards.takeFirst();
291 g->cancelNotify();
292 Q_ASSERT(g->isConnected(n));
293 } else {
294 g = QQmlJavaScriptExpressionGuard::New(expression, engine);
295 g->connect(n);
296 }
297
298 expression->activeGuards.prepend(g);
299}
300
301/*! \internal
302
303 \a n is in the signal index range (see QObjectPrivate::signalIndex()).
304*/
305void QQmlPropertyCapture::captureProperty(QObject *o, int c, int n, bool doNotify)
306{
307 if (watcher->wasDeleted())
308 return;
309
310 Q_ASSERT(expression);
311
312 // If c < 0 we won't find any property. We better leave the metaobjects alone in that case.
313 // QQmlListModel expects us _not_ to trigger the creation of dynamic metaobjects from here.
314 if (c >= 0) {
315 const QQmlData *ddata = QQmlData::get(o, /*create=*/false);
316 const QMetaObject *metaObjectForBindable = nullptr;
317 if (auto const propCache = (ddata ? ddata->propertyCache.data() : nullptr)) {
318 Q_ASSERT(propCache->property(c));
319 if (propCache->property(c)->notifiesViaBindable())
320 metaObjectForBindable = propCache->metaObject();
321 } else {
322 const QMetaObject *m = o->metaObject();
323 if (m->property(c).isBindable())
324 metaObjectForBindable = m;
325 }
326 if (metaObjectForBindable) {
327 captureBindableProperty(o, metaObjectForBindable, c);
328 return;
329 }
330 }
331
332 captureNonBindableProperty(o, n, c, doNotify);
333}
334
335void QQmlPropertyCapture::captureProperty(
336 QObject *o, const QQmlPropertyCache *propertyCache, const QQmlPropertyData *propertyData,
337 bool doNotify)
338{
339 if (watcher->wasDeleted())
340 return;
341
342 Q_ASSERT(expression);
343
344 if (propertyData->notifiesViaBindable()) {
345 if (const QMetaObject *metaObjectForBindable = propertyCache->metaObject()) {
346 captureBindableProperty(o, metaObjectForBindable, propertyData->coreIndex());
347 return;
348 }
349 }
350
351 captureNonBindableProperty(o, propertyData->notifyIndex(), propertyData->coreIndex(), doNotify);
352}
353
354bool QQmlJavaScriptExpression::needsPropertyChangeTrigger(QObject *target, int propertyIndex)
355{
356 TriggerList **prev = &qpropertyChangeTriggers;
357 TriggerList *current = qpropertyChangeTriggers;
358 while (current) {
359 if (!current->target) {
360 *prev = current->next;
361 QRecyclePool<TriggerList>::Delete(current);
362 current = *prev;
363 } else if (current->target == target && current->propertyIndex == propertyIndex) {
364 return false; // already installed
365 } else {
366 prev = &current->next;
367 current = current->next;
368 }
369 }
370
371 return true;
372}
373
374void QQmlPropertyCapture::captureTranslation()
375{
376 // use a unique invalid index to avoid needlessly querying the metaobject for
377 // the correct index of of the translationLanguage property
378 int const invalidIndex = -2;
379 if (expression->needsPropertyChangeTrigger(engine, invalidIndex)) {
380 auto trigger = expression->allocatePropertyChangeTrigger(engine, invalidIndex);
381 trigger->setSource(QQmlEnginePrivate::get(engine)->translationLanguage);
382 }
383}
384
385void QQmlPropertyCapture::captureBindableProperty(
386 QObject *o, const QMetaObject *metaObjectForBindable, int c)
387{
388 // if the property is a QPropery, and we're binding to a QProperty
389 // the automatic capturing process already takes care of everything
390 if (!expression->mustCaptureBindableProperty())
391 return;
392
393 if (expression->needsPropertyChangeTrigger(o, c)) {
394 auto trigger = expression->allocatePropertyChangeTrigger(o, c);
395 QUntypedBindable bindable;
396 void *argv[] = { &bindable };
397 metaObjectForBindable->metacall(o, QMetaObject::BindableProperty, c, argv);
398 bindable.observe(trigger);
399 }
400}
401
402void QQmlPropertyCapture::captureNonBindableProperty(QObject *o, int n, int c, bool doNotify)
403{
404 if (n == -1) {
405 if (!errorString) {
406 errorString = new QStringList;
407 QString preamble = QLatin1String("QQmlExpression: Expression ") +
408 expression->expressionIdentifier() +
409 QLatin1String(" depends on non-bindable properties:");
410 errorString->append(preamble);
411 }
412
413 const QMetaProperty metaProp = o->metaObject()->property(c);
414 QString error = QLatin1String(" ") +
415 QString::fromUtf8(o->metaObject()->className()) +
416 QLatin1String("::") +
417 QString::fromUtf8(metaProp.name());
418 errorString->append(error);
419 } else {
420
421 // Try and find a matching guard
422 while (!guards.isEmpty() && !guards.first()->isConnected(o, n))
423 guards.takeFirst()->Delete();
424
425 QQmlJavaScriptExpressionGuard *g = nullptr;
426 if (!guards.isEmpty()) {
427 g = guards.takeFirst();
428 g->cancelNotify();
429 Q_ASSERT(g->isConnected(o, n));
430 } else {
431 g = QQmlJavaScriptExpressionGuard::New(expression, engine);
432 g->connect(o, n, engine, doNotify);
433 }
434
435 expression->activeGuards.prepend(g);
436 }
437}
438
439QQmlError QQmlJavaScriptExpression::error(QQmlEngine *engine) const
440{
441 Q_UNUSED(engine);
442
443 if (m_error)
444 return m_error->error();
445 else
446 return QQmlError();
447}
448
449QQmlDelayedError *QQmlJavaScriptExpression::delayedError()
450{
451 if (!m_error)
452 m_error = new QQmlDelayedError;
453 return m_error.data();
454}
455
456QV4::ReturnedValue
457QQmlJavaScriptExpression::evalFunction(
458 const QQmlRefPointer<QQmlContextData> &ctxt, QObject *scopeObject,
459 const QString &code, const QString &filename, quint16 line)
460{
461 QQmlEngine *engine = ctxt->engine();
462 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
463
464 QV4::ExecutionEngine *v4 = engine->handle();
465 QV4::Scope scope(v4);
466
467 QV4::Scoped<QV4::QmlContext> qmlContext(scope, QV4::QmlContext::create(v4->rootContext(), ctxt, scopeObject));
468 QV4::Script script(v4, qmlContext, /*parse as QML binding*/true, code, filename, line);
469 QV4::ScopedValue result(scope);
470 script.parse();
471 if (!v4->hasException)
472 result = script.run();
473 if (v4->hasException) {
474 QQmlError error = v4->catchExceptionAsQmlError();
475 if (error.description().isEmpty())
476 error.setDescription(QLatin1String("Exception occurred during function evaluation"));
477 if (error.line() == -1)
478 error.setLine(line);
479 if (error.url().isEmpty())
480 error.setUrl(QUrl::fromLocalFile(filename));
481 error.setObject(scopeObject);
482 ep->warning(error);
483 return QV4::Encode::undefined();
484 }
485 return result->asReturnedValue();
486}
487
488void QQmlJavaScriptExpression::createQmlBinding(
489 const QQmlRefPointer<QQmlContextData> &ctxt, QObject *qmlScope, const QString &code,
490 const QString &filename, quint16 line)
491{
492 QQmlEngine *engine = ctxt->engine();
493 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
494
495 QV4::ExecutionEngine *v4 = engine->handle();
496 QV4::Scope scope(v4);
497
498 QV4::Scoped<QV4::QmlContext> qmlContext(scope, QV4::QmlContext::create(v4->rootContext(), ctxt, qmlScope));
499 QV4::Script script(v4, qmlContext, /*parse as QML binding*/true, code, filename, line);
500 script.parse();
501 if (v4->hasException) {
502 QQmlDelayedError *error = delayedError();
503 error->catchJavaScriptException(v4);
504 error->setErrorObject(qmlScope);
505 if (!error->addError(ep))
506 ep->warning(error->error());
507 return;
508 }
509 setupFunction(qmlContext, script.function());
510}
511
512void QQmlJavaScriptExpression::setupFunction(QV4::ExecutionContext *qmlContext, QV4::Function *f)
513{
514 if (!qmlContext || !f)
515 return;
516 m_qmlScope.set(qmlContext->engine(), *qmlContext);
517 m_v4Function = f;
518 m_compilationUnit.reset(m_v4Function->executableCompilationUnit());
519}
520
521void QQmlJavaScriptExpression::setCompilationUnit(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit)
522{
523 m_compilationUnit = compilationUnit;
524}
525
526void QPropertyChangeTrigger::trigger(QPropertyObserver *observer, QUntypedPropertyData *) {
527 auto This = static_cast<QPropertyChangeTrigger *>(observer);
528 This->m_expression->expressionChanged();
529}
530
531QMetaProperty QPropertyChangeTrigger::property() const
532{
533 if (!target)
534 return {};
535 auto const mo = target->metaObject();
536 if (!mo)
537 return {};
538 return mo->property(propertyIndex);
539}
540
541QPropertyChangeTrigger *QQmlJavaScriptExpression::allocatePropertyChangeTrigger(QObject *target, int propertyIndex)
542{
543 auto trigger = QQmlEnginePrivate::get(engine())->qPropertyTriggerPool.New( this );
544 trigger->target = target;
545 trigger->propertyIndex = propertyIndex;
546 auto oldHead = qpropertyChangeTriggers;
547 trigger->next = oldHead;
548 qpropertyChangeTriggers = trigger;
549 return trigger;
550}
551
552void QQmlJavaScriptExpression::clearActiveGuards()
553{
554 while (QQmlJavaScriptExpressionGuard *g = activeGuards.takeFirst())
555 g->Delete();
556}
557
558void QQmlJavaScriptExpressionGuard_callback(QQmlNotifierEndpoint *e, void **)
559{
560 QQmlJavaScriptExpression *expression =
561 static_cast<QQmlJavaScriptExpressionGuard *>(e)->expression;
562
563 expression->expressionChanged();
564}
565
566QT_END_NAMESPACE
bool addError(QQmlEnginePrivate *)
void setErrorObject(QObject *object)
void catchJavaScriptException(QV4::ExecutionEngine *engine)
void setErrorDescription(const QString &description)
Combined button and popup list for selecting options.
void QQmlJavaScriptExpressionGuard_callback(QQmlNotifierEndpoint *e, void **)