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