1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQml 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 "qqmljavascriptexpression_p.h"
41
42#include <private/qqmlexpression_p.h>
43#include <private/qv4context_p.h>
44#include <private/qv4value_p.h>
45#include <private/qv4functionobject_p.h>
46#include <private/qv4script_p.h>
47#include <private/qv4errorobject_p.h>
48#include <private/qv4scopedvalue_p.h>
49#include <private/qv4jscall_p.h>
50#include <private/qqmlglobal_p.h>
51#include <private/qv4qobjectwrapper_p.h>
52#include <private/qqmlbuiltinfunctions_p.h>
53#include <private/qqmlsourcecoordinate_p.h>
54
55QT_BEGIN_NAMESPACE
56
57bool QQmlDelayedError::addError(QQmlEnginePrivate *e)
58{
59 if (!e) return false;
60
61 if (e->inProgressCreations == 0) return false; // Not in construction
62
63 if (prevError) return true; // Already in error chain
64
65 prevError = &e->erroredBindings;
66 nextError = e->erroredBindings;
67 e->erroredBindings = this;
68 if (nextError) nextError->prevError = &nextError;
69
70 return true;
71}
72
73void QQmlDelayedError::setErrorLocation(const QQmlSourceLocation &sourceLocation)
74{
75 m_error.setUrl(QUrl(sourceLocation.sourceFile));
76 m_error.setLine(qmlConvertSourceCoordinate<quint16, int>(n: sourceLocation.line));
77 m_error.setColumn(qmlConvertSourceCoordinate<quint16, int>(n: sourceLocation.column));
78}
79
80void QQmlDelayedError::setErrorDescription(const QString &description)
81{
82 m_error.setDescription(description);
83}
84
85void QQmlDelayedError::setErrorObject(QObject *object)
86{
87 m_error.setObject(object);
88}
89
90void QQmlDelayedError::catchJavaScriptException(QV4::ExecutionEngine *engine)
91{
92 m_error = engine->catchExceptionAsQmlError();
93}
94
95
96QQmlJavaScriptExpression::QQmlJavaScriptExpression()
97 : m_context(nullptr),
98 m_prevExpression(nullptr),
99 m_nextExpression(nullptr),
100 m_v4Function(nullptr)
101{
102}
103
104QQmlJavaScriptExpression::~QQmlJavaScriptExpression()
105{
106 if (m_prevExpression) {
107 *m_prevExpression = m_nextExpression;
108 if (m_nextExpression)
109 m_nextExpression->m_prevExpression = m_prevExpression;
110 }
111
112 clearActiveGuards();
113 clearError();
114 if (m_scopeObject.isT2()) // notify DeleteWatcher of our deletion.
115 m_scopeObject.asT2()->_s = nullptr;
116}
117
118void QQmlJavaScriptExpression::setNotifyOnValueChanged(bool v)
119{
120 activeGuards.setFlagValue(v);
121 if (!v)
122 clearActiveGuards();
123}
124
125void QQmlJavaScriptExpression::resetNotifyOnValueChanged()
126{
127 setNotifyOnValueChanged(false);
128}
129
130QQmlSourceLocation QQmlJavaScriptExpression::sourceLocation() const
131{
132 if (m_v4Function)
133 return m_v4Function->sourceLocation();
134 return QQmlSourceLocation();
135}
136
137void QQmlJavaScriptExpression::setContext(QQmlContextData *context)
138{
139 if (m_prevExpression) {
140 *m_prevExpression = m_nextExpression;
141 if (m_nextExpression)
142 m_nextExpression->m_prevExpression = m_prevExpression;
143 m_prevExpression = nullptr;
144 m_nextExpression = nullptr;
145 }
146
147 m_context = context;
148
149 if (context) {
150 m_nextExpression = context->expressions;
151 if (m_nextExpression)
152 m_nextExpression->m_prevExpression = &m_nextExpression;
153 m_prevExpression = &context->expressions;
154 context->expressions = this;
155 }
156}
157
158QV4::Function *QQmlJavaScriptExpression::function() const
159{
160 return m_v4Function;
161}
162
163void QQmlJavaScriptExpression::refresh()
164{
165}
166
167QV4::ReturnedValue QQmlJavaScriptExpression::evaluate(bool *isUndefined)
168{
169 QV4::ExecutionEngine *v4 = m_context->engine->handle();
170 QV4::Scope scope(v4);
171 QV4::JSCallData jsCall(scope);
172
173 return evaluate(callData: jsCall.callData(), isUndefined);
174}
175
176QV4::ReturnedValue QQmlJavaScriptExpression::evaluate(QV4::CallData *callData, bool *isUndefined)
177{
178 Q_ASSERT(m_context && m_context->engine);
179
180 QV4::Function *v4Function = function();
181 if (!v4Function) {
182 if (isUndefined)
183 *isUndefined = true;
184 return QV4::Encode::undefined();
185 }
186
187 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(e: m_context->engine);
188
189 // All code that follows must check with watcher before it accesses data members
190 // incase we have been deleted.
191 DeleteWatcher watcher(this);
192
193 Q_ASSERT(notifyOnValueChanged() || activeGuards.isEmpty());
194 QQmlPropertyCapture capture(m_context->engine, this, &watcher);
195
196 QQmlPropertyCapture *lastPropertyCapture = ep->propertyCapture;
197 ep->propertyCapture = notifyOnValueChanged() ? &capture : nullptr;
198
199
200 if (notifyOnValueChanged())
201 capture.guards.copyAndClearPrepend(o&: activeGuards);
202
203 QV4::ExecutionEngine *v4 = m_context->engine->handle();
204 callData->thisObject = v4->globalObject;
205 if (scopeObject()) {
206 QV4::ReturnedValue scope = QV4::QObjectWrapper::wrap(engine: v4, object: scopeObject());
207 if (QV4::Value::fromReturnedValue(val: scope).isObject())
208 callData->thisObject = scope;
209 }
210
211 Q_ASSERT(m_qmlScope.valueRef());
212 QV4::ReturnedValue res = v4Function->call(
213 thisObject: &(callData->thisObject.asValue<QV4::Value>()),
214 argv: callData->argValues<QV4::Value>(), argc: callData->argc(),
215 context: static_cast<QV4::ExecutionContext *>(m_qmlScope.valueRef()));
216 QV4::Scope scope(v4);
217 QV4::ScopedValue result(scope, res);
218
219 if (scope.hasException()) {
220 if (watcher.wasDeleted())
221 scope.engine->catchException(); // ignore exception
222 else
223 delayedError()->catchJavaScriptException(engine: scope.engine);
224 if (isUndefined)
225 *isUndefined = true;
226 } else {
227 if (isUndefined)
228 *isUndefined = result->isUndefined();
229
230 if (!watcher.wasDeleted() && hasDelayedError())
231 delayedError()->clearError();
232 }
233
234 if (capture.errorString) {
235 for (int ii = 0; ii < capture.errorString->count(); ++ii)
236 qWarning(msg: "%s", qPrintable(capture.errorString->at(ii)));
237 delete capture.errorString;
238 capture.errorString = nullptr;
239 }
240
241 while (QQmlJavaScriptExpressionGuard *g = capture.guards.takeFirst())
242 g->Delete();
243
244 if (!watcher.wasDeleted())
245 setTranslationsCaptured(capture.translationCaptured);
246
247 ep->propertyCapture = lastPropertyCapture;
248
249 return result->asReturnedValue();
250}
251
252void QQmlPropertyCapture::captureProperty(QQmlNotifier *n)
253{
254 if (watcher->wasDeleted())
255 return;
256
257 Q_ASSERT(expression);
258 // Try and find a matching guard
259 while (!guards.isEmpty() && !guards.first()->isConnected(notifier: n))
260 guards.takeFirst()->Delete();
261
262 QQmlJavaScriptExpressionGuard *g = nullptr;
263 if (!guards.isEmpty()) {
264 g = guards.takeFirst();
265 g->cancelNotify();
266 Q_ASSERT(g->isConnected(n));
267 } else {
268 g = QQmlJavaScriptExpressionGuard::New(e: expression, engine);
269 g->connect(notifier: n);
270 }
271
272 expression->activeGuards.prepend(v: g);
273}
274
275/*! \internal
276
277 \a n is in the signal index range (see QObjectPrivate::signalIndex()).
278*/
279void QQmlPropertyCapture::captureProperty(QObject *o, int c, int n, bool doNotify)
280{
281 if (watcher->wasDeleted())
282 return;
283
284 Q_ASSERT(expression);
285 if (n == -1) {
286 if (!errorString) {
287 errorString = new QStringList;
288 QString preamble = QLatin1String("QQmlExpression: Expression ") +
289 expression->expressionIdentifier() +
290 QLatin1String(" depends on non-NOTIFYable properties:");
291 errorString->append(t: preamble);
292 }
293
294 const QMetaObject *metaObj = o->metaObject();
295 QMetaProperty metaProp = metaObj->property(index: c);
296
297 QString error = QLatin1String(" ") +
298 QString::fromUtf8(str: metaObj->className()) +
299 QLatin1String("::") +
300 QString::fromUtf8(str: metaProp.name());
301 errorString->append(t: error);
302 } else {
303
304 // Try and find a matching guard
305 while (!guards.isEmpty() && !guards.first()->isConnected(source: o, sourceSignal: n))
306 guards.takeFirst()->Delete();
307
308 QQmlJavaScriptExpressionGuard *g = nullptr;
309 if (!guards.isEmpty()) {
310 g = guards.takeFirst();
311 g->cancelNotify();
312 Q_ASSERT(g->isConnected(o, n));
313 } else {
314 g = QQmlJavaScriptExpressionGuard::New(e: expression, engine);
315 g->connect(source: o, sourceSignal: n, engine, doNotify);
316 }
317
318 expression->activeGuards.prepend(v: g);
319 }
320}
321
322QQmlError QQmlJavaScriptExpression::error(QQmlEngine *engine) const
323{
324 Q_UNUSED(engine);
325
326 if (m_error)
327 return m_error->error();
328 else
329 return QQmlError();
330}
331
332QQmlDelayedError *QQmlJavaScriptExpression::delayedError()
333{
334 if (!m_error)
335 m_error = new QQmlDelayedError;
336 return m_error.data();
337}
338
339QV4::ReturnedValue
340QQmlJavaScriptExpression::evalFunction(QQmlContextData *ctxt, QObject *scopeObject,
341 const QString &code, const QString &filename, quint16 line)
342{
343 QQmlEngine *engine = ctxt->engine;
344 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(e: engine);
345
346 QV4::ExecutionEngine *v4 = engine->handle();
347 QV4::Scope scope(v4);
348
349 QV4::Scoped<QV4::QmlContext> qmlContext(scope, QV4::QmlContext::create(parent: v4->rootContext(), context: ctxt, scopeObject));
350 QV4::Script script(v4, qmlContext, /*parse as QML binding*/true, code, filename, line);
351 QV4::ScopedValue result(scope);
352 script.parse();
353 if (!v4->hasException)
354 result = script.run();
355 if (v4->hasException) {
356 QQmlError error = v4->catchExceptionAsQmlError();
357 if (error.description().isEmpty())
358 error.setDescription(QLatin1String("Exception occurred during function evaluation"));
359 if (error.line() == -1)
360 error.setLine(line);
361 if (error.url().isEmpty())
362 error.setUrl(QUrl::fromLocalFile(localfile: filename));
363 error.setObject(scopeObject);
364 ep->warning(error);
365 return QV4::Encode::undefined();
366 }
367 return result->asReturnedValue();
368}
369
370void QQmlJavaScriptExpression::createQmlBinding(QQmlContextData *ctxt, QObject *qmlScope,
371 const QString &code, const QString &filename, quint16 line)
372{
373 QQmlEngine *engine = ctxt->engine;
374 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(e: engine);
375
376 QV4::ExecutionEngine *v4 = engine->handle();
377 QV4::Scope scope(v4);
378
379 QV4::Scoped<QV4::QmlContext> qmlContext(scope, QV4::QmlContext::create(parent: v4->rootContext(), context: ctxt, scopeObject: qmlScope));
380 QV4::Script script(v4, qmlContext, /*parse as QML binding*/true, code, filename, line);
381 script.parse();
382 if (v4->hasException) {
383 QQmlDelayedError *error = delayedError();
384 error->catchJavaScriptException(engine: v4);
385 error->setErrorObject(qmlScope);
386 if (!error->addError(e: ep))
387 ep->warning(error->error());
388 return;
389 }
390 setupFunction(qmlContext, f: script.vmFunction);
391}
392
393void QQmlJavaScriptExpression::setupFunction(QV4::ExecutionContext *qmlContext, QV4::Function *f)
394{
395 if (!qmlContext || !f)
396 return;
397 m_qmlScope.set(engine: qmlContext->engine(), value: *qmlContext);
398 m_v4Function = f;
399 setCompilationUnit(m_v4Function->executableCompilationUnit());
400}
401
402void QQmlJavaScriptExpression::setCompilationUnit(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit)
403{
404 m_compilationUnit = compilationUnit;
405}
406
407void QQmlJavaScriptExpression::clearActiveGuards()
408{
409 while (QQmlJavaScriptExpressionGuard *g = activeGuards.takeFirst())
410 g->Delete();
411}
412
413void QQmlJavaScriptExpressionGuard_callback(QQmlNotifierEndpoint *e, void **)
414{
415 QQmlJavaScriptExpression *expression =
416 static_cast<QQmlJavaScriptExpressionGuard *>(e)->expression;
417
418 expression->expressionChanged();
419}
420
421QT_END_NAMESPACE
422

source code of qtdeclarative/src/qml/qml/qqmljavascriptexpression.cpp