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