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 | |
15 | Q_DECLARE_LOGGING_CATEGORY(DBG_DISK_CACHE) |
16 | Q_LOGGING_CATEGORY(DBG_DISK_CACHE, "qt.qml.diskcache" ) |
17 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | QQmlScriptBlob::QQmlScriptBlob(const QUrl &url, QQmlTypeLoader *loader) |
21 | : QQmlTypeLoader::Blob(url, JavaScriptFile, loader) |
22 | , m_isModule(url.path().endsWith(s: QLatin1String(".mjs" ))) |
23 | { |
24 | } |
25 | |
26 | QQmlScriptBlob::~QQmlScriptBlob() |
27 | { |
28 | } |
29 | |
30 | QQmlRefPointer<QQmlScriptData> QQmlScriptBlob::scriptData() const |
31 | { |
32 | return m_scriptData; |
33 | } |
34 | |
35 | void 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 | |
117 | void 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 | |
123 | void 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 | |
169 | QString QQmlScriptBlob::stringAt(int index) const |
170 | { |
171 | return m_scriptData->m_precompiledScript->stringAt(index); |
172 | } |
173 | |
174 | void 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 | |
185 | void 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 | */ |
238 | void 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 | |
249 | QT_END_NAMESPACE |
250 | |