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 | |
55 | QT_BEGIN_NAMESPACE |
56 | |
57 | bool 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 | |
73 | void 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 | |
80 | void QQmlDelayedError::setErrorDescription(const QString &description) |
81 | { |
82 | m_error.setDescription(description); |
83 | } |
84 | |
85 | void QQmlDelayedError::setErrorObject(QObject *object) |
86 | { |
87 | m_error.setObject(object); |
88 | } |
89 | |
90 | void QQmlDelayedError::catchJavaScriptException(QV4::ExecutionEngine *engine) |
91 | { |
92 | m_error = engine->catchExceptionAsQmlError(); |
93 | } |
94 | |
95 | |
96 | QQmlJavaScriptExpression::QQmlJavaScriptExpression() |
97 | : m_context(nullptr), |
98 | m_prevExpression(nullptr), |
99 | m_nextExpression(nullptr), |
100 | m_v4Function(nullptr) |
101 | { |
102 | } |
103 | |
104 | QQmlJavaScriptExpression::~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 | |
118 | void QQmlJavaScriptExpression::setNotifyOnValueChanged(bool v) |
119 | { |
120 | activeGuards.setFlagValue(v); |
121 | if (!v) |
122 | clearActiveGuards(); |
123 | } |
124 | |
125 | void QQmlJavaScriptExpression::resetNotifyOnValueChanged() |
126 | { |
127 | setNotifyOnValueChanged(false); |
128 | } |
129 | |
130 | QQmlSourceLocation QQmlJavaScriptExpression::sourceLocation() const |
131 | { |
132 | if (m_v4Function) |
133 | return m_v4Function->sourceLocation(); |
134 | return QQmlSourceLocation(); |
135 | } |
136 | |
137 | void 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 | |
158 | QV4::Function *QQmlJavaScriptExpression::function() const |
159 | { |
160 | return m_v4Function; |
161 | } |
162 | |
163 | void QQmlJavaScriptExpression::refresh() |
164 | { |
165 | } |
166 | |
167 | QV4::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 | |
176 | QV4::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 | |
252 | void 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 | */ |
279 | void 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 | |
322 | QQmlError 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 | |
332 | QQmlDelayedError *QQmlJavaScriptExpression::delayedError() |
333 | { |
334 | if (!m_error) |
335 | m_error = new QQmlDelayedError; |
336 | return m_error.data(); |
337 | } |
338 | |
339 | QV4::ReturnedValue |
340 | QQmlJavaScriptExpression::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 | |
370 | void 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 | |
393 | void 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 | |
402 | void QQmlJavaScriptExpression::setCompilationUnit(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) |
403 | { |
404 | m_compilationUnit = compilationUnit; |
405 | } |
406 | |
407 | void QQmlJavaScriptExpression::clearActiveGuards() |
408 | { |
409 | while (QQmlJavaScriptExpressionGuard *g = activeGuards.takeFirst()) |
410 | g->Delete(); |
411 | } |
412 | |
413 | void QQmlJavaScriptExpressionGuard_callback(QQmlNotifierEndpoint *e, void **) |
414 | { |
415 | QQmlJavaScriptExpression *expression = |
416 | static_cast<QQmlJavaScriptExpressionGuard *>(e)->expression; |
417 | |
418 | expression->expressionChanged(); |
419 | } |
420 | |
421 | QT_END_NAMESPACE |
422 | |