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

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