1/****************************************************************************
2**
3** Copyright (C) 2019 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 <private/qqmlengine_p.h>
41#include <private/qqmlirbuilder_p.h>
42#include <private/qqmlscriptblob_p.h>
43#include <private/qqmlscriptdata_p.h>
44#include <private/qqmlsourcecoordinate_p.h>
45#include <private/qv4runtimecodegen_p.h>
46#include <private/qv4script_p.h>
47
48#include <QtCore/qloggingcategory.h>
49
50Q_DECLARE_LOGGING_CATEGORY(DBG_DISK_CACHE)
51Q_LOGGING_CATEGORY(DBG_DISK_CACHE, "qt.qml.diskcache")
52
53QT_BEGIN_NAMESPACE
54
55QQmlScriptBlob::QQmlScriptBlob(const QUrl &url, QQmlTypeLoader *loader)
56 : QQmlTypeLoader::Blob(url, JavaScriptFile, loader)
57 , m_isModule(url.path().endsWith(s: QLatin1String(".mjs")))
58{
59}
60
61QQmlScriptBlob::~QQmlScriptBlob()
62{
63}
64
65QQmlRefPointer<QQmlScriptData> QQmlScriptBlob::scriptData() const
66{
67 return m_scriptData;
68}
69
70void QQmlScriptBlob::dataReceived(const SourceCodeData &data)
71{
72 if (diskCacheEnabled()) {
73 QQmlRefPointer<QV4::ExecutableCompilationUnit> unit
74 = QV4::ExecutableCompilationUnit::create();
75 QString error;
76 if (unit->loadFromDisk(url: url(), sourceTimeStamp: data.sourceTimeStamp(), errorString: &error)) {
77 initializeFromCompilationUnit(unit);
78 return;
79 } else {
80 qCDebug(DBG_DISK_CACHE()) << "Error loading" << urlString() << "from disk cache:" << error;
81 }
82 }
83
84 if (!data.exists()) {
85 if (m_cachedUnitStatus == QQmlMetaType::CachedUnitLookupError::VersionMismatch)
86 setError(QQmlTypeLoader::tr(sourceText: "File was compiled ahead of time with an incompatible version of Qt and the original file cannot be found. Please recompile"));
87 else
88 setError(QQmlTypeLoader::tr(sourceText: "No such file or directory"));
89 return;
90 }
91
92 QString error;
93 QString source = data.readAll(error: &error);
94 if (!error.isEmpty()) {
95 setError(error);
96 return;
97 }
98
99 QV4::CompiledData::CompilationUnit unit;
100
101 if (m_isModule) {
102 QList<QQmlJS::DiagnosticMessage> diagnostics;
103 unit = QV4::Compiler::Codegen::compileModule(debugMode: isDebugging(), url: urlString(), sourceCode: source,
104 sourceTimeStamp: data.sourceTimeStamp(), diagnostics: &diagnostics);
105 QList<QQmlError> errors = QQmlEnginePrivate::qmlErrorFromDiagnostics(fileName: urlString(), diagnosticMessages: diagnostics);
106 if (!errors.isEmpty()) {
107 setError(errors);
108 return;
109 }
110 } else {
111 QmlIR::Document irUnit(isDebugging());
112
113 irUnit.jsModule.sourceTimeStamp = data.sourceTimeStamp();
114
115 QmlIR::ScriptDirectivesCollector collector(&irUnit);
116 irUnit.jsParserEngine.setDirectives(&collector);
117
118 QList<QQmlError> errors;
119 irUnit.javaScriptCompilationUnit = QV4::Script::precompile(
120 module: &irUnit.jsModule, jsEngine: &irUnit.jsParserEngine, unitGenerator: &irUnit.jsGenerator, fileName: urlString(), finalUrl: finalUrlString(),
121 source, reportedErrors: &errors, contextType: QV4::Compiler::ContextType::ScriptImportedByQML);
122
123 source.clear();
124 if (!errors.isEmpty()) {
125 setError(errors);
126 return;
127 }
128
129 QmlIR::QmlUnitGenerator qmlGenerator;
130 qmlGenerator.generate(output&: irUnit);
131 unit = std::move(irUnit.javaScriptCompilationUnit);
132 }
133
134 auto executableUnit = QV4::ExecutableCompilationUnit::create(compilationUnit: std::move(unit));
135
136 if (diskCacheEnabled()) {
137 QString errorString;
138 if (executableUnit->saveToDisk(unitUrl: url(), errorString: &errorString)) {
139 QString error;
140 if (!executableUnit->loadFromDisk(url: url(), sourceTimeStamp: data.sourceTimeStamp(), errorString: &error)) {
141 // ignore error, keep using the in-memory compilation unit.
142 }
143 } else {
144 qCDebug(DBG_DISK_CACHE()) << "Error saving cached version of"
145 << executableUnit->fileName() << "to disk:" << errorString;
146 }
147 }
148
149 initializeFromCompilationUnit(unit: executableUnit);
150}
151
152void QQmlScriptBlob::initializeFromCachedUnit(const QV4::CompiledData::Unit *unit)
153{
154 initializeFromCompilationUnit(unit: QV4::ExecutableCompilationUnit::create(
155 compilationUnit: QV4::CompiledData::CompilationUnit(unit, urlString(), finalUrlString())));
156}
157
158void QQmlScriptBlob::done()
159{
160 if (isError())
161 return;
162
163 // Check all script dependencies for errors
164 for (int ii = 0; ii < m_scripts.count(); ++ii) {
165 const ScriptReference &script = m_scripts.at(i: ii);
166 Q_ASSERT(script.script->isCompleteOrError());
167 if (script.script->isError()) {
168 QList<QQmlError> errors = script.script->errors();
169 QQmlError error;
170 error.setUrl(url());
171 error.setLine(qmlConvertSourceCoordinate<quint32, int>(n: script.location.line));
172 error.setColumn(qmlConvertSourceCoordinate<quint32, int>(n: script.location.column));
173 error.setDescription(QQmlTypeLoader::tr(sourceText: "Script %1 unavailable").arg(a: script.script->urlString()));
174 errors.prepend(t: error);
175 setError(errors);
176 return;
177 }
178 }
179
180 if (!m_isModule) {
181 m_scriptData->typeNameCache.adopt(other: new QQmlTypeNameCache(m_importCache));
182
183 QSet<QString> ns;
184
185 for (int scriptIndex = 0; scriptIndex < m_scripts.count(); ++scriptIndex) {
186 const ScriptReference &script = m_scripts.at(i: scriptIndex);
187
188 m_scriptData->scripts.append(t: script.script);
189
190 if (!script.nameSpace.isNull()) {
191 if (!ns.contains(value: script.nameSpace)) {
192 ns.insert(value: script.nameSpace);
193 m_scriptData->typeNameCache->add(name: script.nameSpace);
194 }
195 }
196 m_scriptData->typeNameCache->add(name: script.qualifier, sciptIndex: scriptIndex, nameSpace: script.nameSpace);
197 }
198
199 m_importCache.populateCache(cache: m_scriptData->typeNameCache.data());
200 }
201 m_scripts.clear();
202}
203
204QString QQmlScriptBlob::stringAt(int index) const
205{
206 return m_scriptData->m_precompiledScript->stringAt(index);
207}
208
209void QQmlScriptBlob::scriptImported(const QQmlRefPointer<QQmlScriptBlob> &blob, const QV4::CompiledData::Location &location, const QString &qualifier, const QString &nameSpace)
210{
211 ScriptReference ref;
212 ref.script = blob;
213 ref.location = location;
214 ref.qualifier = qualifier;
215 ref.nameSpace = nameSpace;
216
217 m_scripts << ref;
218}
219
220void QQmlScriptBlob::initializeFromCompilationUnit(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit)
221{
222 Q_ASSERT(!m_scriptData);
223 m_scriptData.adopt(other: new QQmlScriptData());
224 m_scriptData->url = finalUrl();
225 m_scriptData->urlString = finalUrlString();
226 m_scriptData->m_precompiledScript = unit;
227
228 m_importCache.setBaseUrl(url: finalUrl(), urlString: finalUrlString());
229
230 QQmlRefPointer<QV4::ExecutableCompilationUnit> script = m_scriptData->m_precompiledScript;
231
232 if (!m_isModule) {
233 QList<QQmlError> errors;
234 for (quint32 i = 0, count = script->importCount(); i < count; ++i) {
235 const QV4::CompiledData::Import *import = script->importAt(index: i);
236 if (!addImport(import, errors: &errors)) {
237 Q_ASSERT(errors.size());
238 QQmlError error(errors.takeFirst());
239 error.setUrl(m_importCache.baseUrl());
240 error.setLine(import->location.line);
241 error.setColumn(import->location.column);
242 errors.prepend(t: error); // put it back on the list after filling out information.
243 setError(errors);
244 return;
245 }
246 }
247 }
248
249 auto *v4 = QQmlEnginePrivate::getV4Engine(e: typeLoader()->engine());
250
251 v4->injectModule(moduleUnit: unit);
252
253 for (const QString &request: unit->moduleRequests()) {
254 if (v4->moduleForUrl(url: QUrl(request), referrer: unit.data()))
255 continue;
256
257 const QUrl absoluteRequest = unit->finalUrl().resolved(relative: QUrl(request));
258 QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(unNormalizedUrl: absoluteRequest);
259 addDependency(blob.data());
260 scriptImported(blob, /* ### */location: QV4::CompiledData::Location(), /*qualifier*/QString(), /*namespace*/nameSpace: QString());
261 }
262}
263
264QT_END_NAMESPACE
265

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