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 | |
50 | Q_DECLARE_LOGGING_CATEGORY(DBG_DISK_CACHE) |
51 | Q_LOGGING_CATEGORY(DBG_DISK_CACHE, "qt.qml.diskcache" ) |
52 | |
53 | QT_BEGIN_NAMESPACE |
54 | |
55 | QQmlScriptBlob::QQmlScriptBlob(const QUrl &url, QQmlTypeLoader *loader) |
56 | : QQmlTypeLoader::Blob(url, JavaScriptFile, loader) |
57 | , m_isModule(url.path().endsWith(s: QLatin1String(".mjs" ))) |
58 | { |
59 | } |
60 | |
61 | QQmlScriptBlob::~QQmlScriptBlob() |
62 | { |
63 | } |
64 | |
65 | QQmlRefPointer<QQmlScriptData> QQmlScriptBlob::scriptData() const |
66 | { |
67 | return m_scriptData; |
68 | } |
69 | |
70 | void 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 | |
152 | void QQmlScriptBlob::initializeFromCachedUnit(const QV4::CompiledData::Unit *unit) |
153 | { |
154 | initializeFromCompilationUnit(unit: QV4::ExecutableCompilationUnit::create( |
155 | compilationUnit: QV4::CompiledData::CompilationUnit(unit, urlString(), finalUrlString()))); |
156 | } |
157 | |
158 | void 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 | |
204 | QString QQmlScriptBlob::stringAt(int index) const |
205 | { |
206 | return m_scriptData->m_precompiledScript->stringAt(index); |
207 | } |
208 | |
209 | void 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 | |
220 | void 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 | |
264 | QT_END_NAMESPACE |
265 | |