aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Hausmann <simon.hausmann@qt.io>2018-03-09 16:40:38 +0100
committerSimon Hausmann <simon.hausmann@qt.io>2018-03-20 09:44:11 +0000
commit64e90f393146dadb382d154e7d67a9109ab2492a (patch)
treec1079ff6af2c9fca9357a9313fffe5590e30dfce
parent22b13921f8067f8a93164875a4ad59bed85b0400 (diff)
Fix QML data structure version checking for ahead-of-time generated files
We must also do version checking for QML and JS files that were compiled ahead of time and are embedded in resources. If the lookup for the original source code fails, then we must generate an appropriate error message. As an upside we get better error reporting when trying to load an empty file and Qt.include() now reports the error message in the statusText field. The error reporting for imported scripts was not changed as importing an empty script is (oddly) allowed. Task-number: QTBUG-66986 Change-Id: Ie0ef81af371a51ecf8c66ae7954d43f5cc6c12de Reviewed-by: Erik Verbruggen <erik.verbruggen@qt.io>
-rw-r--r--src/qml/compiler/qv4compilationunitmapper.cpp32
-rw-r--r--src/qml/compiler/qv4compilationunitmapper_p.h2
-rw-r--r--src/qml/compiler/qv4compilationunitmapper_unix.cpp2
-rw-r--r--src/qml/compiler/qv4compilationunitmapper_win.cpp2
-rw-r--r--src/qml/compiler/qv4compileddata.cpp38
-rw-r--r--src/qml/compiler/qv4compileddata_p.h2
-rw-r--r--src/qml/jsruntime/qv4include.cpp10
-rw-r--r--src/qml/jsruntime/qv4include_p.h3
-rw-r--r--src/qml/jsruntime/qv4script.cpp17
-rw-r--r--src/qml/jsruntime/qv4script_p.h2
-rw-r--r--src/qml/qml/qqmlmetatype.cpp35
-rw-r--r--src/qml/qml/qqmlmetatype_p.h12
-rw-r--r--src/qml/qml/qqmltypeloader.cpp38
-rw-r--r--src/qml/qml/qqmltypeloader_p.h5
-rw-r--r--src/qml/types/qquickworkerscript.cpp6
-rw-r--r--tests/auto/qml/qmlcachegen/qmlcachegen.pro2
-rw-r--r--tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp46
-rw-r--r--tests/auto/qml/qmlcachegen/versionchecks.qml4
-rw-r--r--tests/auto/qml/qqmlecmascript/data/include_callback.js2
-rw-r--r--tests/auto/qml/qqmllanguage/data/empty.errors.txt3
20 files changed, 204 insertions, 59 deletions
diff --git a/src/qml/compiler/qv4compilationunitmapper.cpp b/src/qml/compiler/qv4compilationunitmapper.cpp
index d94f7ac238..350f6f9485 100644
--- a/src/qml/compiler/qv4compilationunitmapper.cpp
+++ b/src/qml/compiler/qv4compilationunitmapper.cpp
@@ -59,36 +59,4 @@ CompilationUnitMapper::~CompilationUnitMapper()
close();
}
-bool CompilationUnitMapper::verifyHeader(const CompiledData::Unit *header, QDateTime sourceTimeStamp, QString *errorString)
-{
- if (strncmp(header->magic, CompiledData::magic_str, sizeof(header->magic))) {
- *errorString = QStringLiteral("Magic bytes in the header do not match");
- return false;
- }
-
- if (header->version != quint32(QV4_DATA_STRUCTURE_VERSION)) {
- *errorString = QString::fromUtf8("V4 data structure version mismatch. Found %1 expected %2").arg(header->version, 0, 16).arg(QV4_DATA_STRUCTURE_VERSION, 0, 16);
- return false;
- }
-
- if (header->qtVersion != quint32(QT_VERSION)) {
- *errorString = QString::fromUtf8("Qt version mismatch. Found %1 expected %2").arg(header->qtVersion, 0, 16).arg(QT_VERSION, 0, 16);
- return false;
- }
-
- if (header->sourceTimeStamp) {
- // Files from the resource system do not have any time stamps, so fall back to the application
- // executable.
- if (!sourceTimeStamp.isValid())
- sourceTimeStamp = QFileInfo(QCoreApplication::applicationFilePath()).lastModified();
-
- if (sourceTimeStamp.isValid() && sourceTimeStamp.toMSecsSinceEpoch() != header->sourceTimeStamp) {
- *errorString = QStringLiteral("QML source file has a different time stamp than cached file.");
- return false;
- }
- }
-
- return true;
-}
-
QT_END_NAMESPACE
diff --git a/src/qml/compiler/qv4compilationunitmapper_p.h b/src/qml/compiler/qv4compilationunitmapper_p.h
index b24f98df7c..80f914c141 100644
--- a/src/qml/compiler/qv4compilationunitmapper_p.h
+++ b/src/qml/compiler/qv4compilationunitmapper_p.h
@@ -72,8 +72,6 @@ public:
void close();
private:
- static bool verifyHeader(const QV4::CompiledData::Unit *header, QDateTime sourceTimeStamp, QString *errorString);
-
#if defined(Q_OS_UNIX)
size_t length;
#endif
diff --git a/src/qml/compiler/qv4compilationunitmapper_unix.cpp b/src/qml/compiler/qv4compilationunitmapper_unix.cpp
index 38dabc41cf..8348613888 100644
--- a/src/qml/compiler/qv4compilationunitmapper_unix.cpp
+++ b/src/qml/compiler/qv4compilationunitmapper_unix.cpp
@@ -73,7 +73,7 @@ CompiledData::Unit *CompilationUnitMapper::open(const QString &cacheFileName, co
return nullptr;
}
- if (!verifyHeader(&header, sourceTimeStamp, errorString))
+ if (!header.verifyHeader(sourceTimeStamp, errorString))
return nullptr;
// Data structure and qt version matched, so now we can access the rest of the file safely.
diff --git a/src/qml/compiler/qv4compilationunitmapper_win.cpp b/src/qml/compiler/qv4compilationunitmapper_win.cpp
index d7a93ae233..8b000021f8 100644
--- a/src/qml/compiler/qv4compilationunitmapper_win.cpp
+++ b/src/qml/compiler/qv4compilationunitmapper_win.cpp
@@ -87,7 +87,7 @@ CompiledData::Unit *CompilationUnitMapper::open(const QString &cacheFileName, co
return nullptr;
}
- if (!verifyHeader(&header, sourceTimeStamp, errorString))
+ if (!header.verifyHeader(sourceTimeStamp, errorString))
return nullptr;
const uint mappingFlags = header.flags & QV4::CompiledData::Unit::ContainsMachineCode
diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp
index cc11b250f3..6220c1bc11 100644
--- a/src/qml/compiler/qv4compileddata.cpp
+++ b/src/qml/compiler/qv4compileddata.cpp
@@ -755,6 +755,44 @@ void Unit::generateChecksum()
#endif
}
+bool Unit::verifyHeader(QDateTime expectedSourceTimeStamp, QString *errorString) const
+{
+#ifndef V4_BOOTSTRAP
+ if (strncmp(magic, CompiledData::magic_str, sizeof(magic))) {
+ *errorString = QStringLiteral("Magic bytes in the header do not match");
+ return false;
+ }
+
+ if (version != quint32(QV4_DATA_STRUCTURE_VERSION)) {
+ *errorString = QString::fromUtf8("V4 data structure version mismatch. Found %1 expected %2").arg(version, 0, 16).arg(QV4_DATA_STRUCTURE_VERSION, 0, 16);
+ return false;
+ }
+
+ if (qtVersion != quint32(QT_VERSION)) {
+ *errorString = QString::fromUtf8("Qt version mismatch. Found %1 expected %2").arg(qtVersion, 0, 16).arg(QT_VERSION, 0, 16);
+ return false;
+ }
+
+ if (sourceTimeStamp) {
+ // Files from the resource system do not have any time stamps, so fall back to the application
+ // executable.
+ if (!expectedSourceTimeStamp.isValid())
+ expectedSourceTimeStamp = QFileInfo(QCoreApplication::applicationFilePath()).lastModified();
+
+ if (expectedSourceTimeStamp.isValid() && expectedSourceTimeStamp.toMSecsSinceEpoch() != sourceTimeStamp) {
+ *errorString = QStringLiteral("QML source file has a different time stamp than cached file.");
+ return false;
+ }
+ }
+
+ return true;
+#else
+ Q_UNUSED(expectedSourceTimeStamp)
+ Q_UNUSED(errorString)
+ return false;
+#endif
+}
+
}
}
diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h
index e6c4225bbd..49360bc3f1 100644
--- a/src/qml/compiler/qv4compileddata_p.h
+++ b/src/qml/compiler/qv4compileddata_p.h
@@ -727,6 +727,8 @@ struct Unit
quint32_le padding;
+ bool verifyHeader(QDateTime expectedSourceTimeStamp, QString *errorString) const;
+
const Import *importAt(int idx) const {
return reinterpret_cast<const Import*>((reinterpret_cast<const char *>(this)) + offsetToImports + idx * sizeof(Import));
}
diff --git a/src/qml/jsruntime/qv4include.cpp b/src/qml/jsruntime/qv4include.cpp
index aaf5e3b857..d0d66c9b9a 100644
--- a/src/qml/jsruntime/qv4include.cpp
+++ b/src/qml/jsruntime/qv4include.cpp
@@ -92,7 +92,8 @@ QV4Include::~QV4Include()
#endif
}
-QV4::ReturnedValue QV4Include::resultValue(QV4::ExecutionEngine *v4, Status status)
+QV4::ReturnedValue QV4Include::resultValue(QV4::ExecutionEngine *v4, Status status,
+ const QString &statusText)
{
QV4::Scope scope(v4);
@@ -105,6 +106,8 @@ QV4::ReturnedValue QV4Include::resultValue(QV4::ExecutionEngine *v4, Status stat
o->put((s = v4->newString(QStringLiteral("NETWORK_ERROR"))), (v = QV4::Primitive::fromInt32(NetworkError)));
o->put((s = v4->newString(QStringLiteral("EXCEPTION"))), (v = QV4::Primitive::fromInt32(Exception)));
o->put((s = v4->newString(QStringLiteral("status"))), (v = QV4::Primitive::fromInt32(status)));
+ if (!statusText.isEmpty())
+ o->put((s = v4->newString(QStringLiteral("statusText"))), (v = v4->newString(statusText)));
return o.asReturnedValue();
}
@@ -227,7 +230,8 @@ QV4::ReturnedValue QV4Include::method_include(const QV4::FunctionObject *b, cons
} else {
QScopedPointer<QV4::Script> script;
- script.reset(QV4::Script::createFromFileOrCache(scope.engine, qmlcontext, localFile, url));
+ QString error;
+ script.reset(QV4::Script::createFromFileOrCache(scope.engine, qmlcontext, localFile, url, &error));
if (!script.isNull()) {
script->parse();
@@ -242,7 +246,7 @@ QV4::ReturnedValue QV4Include::method_include(const QV4::FunctionObject *b, cons
result = resultValue(scope.engine, Ok);
}
} else {
- result = resultValue(scope.engine, NetworkError);
+ result = resultValue(scope.engine, NetworkError, error);
}
callback(callbackFunction, result);
diff --git a/src/qml/jsruntime/qv4include_p.h b/src/qml/jsruntime/qv4include_p.h
index 8015722afc..70ccfbf223 100644
--- a/src/qml/jsruntime/qv4include_p.h
+++ b/src/qml/jsruntime/qv4include_p.h
@@ -88,7 +88,8 @@ private:
QV4::ReturnedValue result();
- static QV4::ReturnedValue resultValue(QV4::ExecutionEngine *v4, Status status = Loading);
+ static QV4::ReturnedValue resultValue(QV4::ExecutionEngine *v4, Status status = Loading,
+ const QString &statusText = QString());
static void callback(const QV4::Value &callback, const QV4::Value &status);
QV4::ExecutionEngine *v4;
diff --git a/src/qml/jsruntime/qv4script.cpp b/src/qml/jsruntime/qv4script.cpp
index 267c93952d..b4d9e11716 100644
--- a/src/qml/jsruntime/qv4script.cpp
+++ b/src/qml/jsruntime/qv4script.cpp
@@ -223,17 +223,28 @@ QQmlRefPointer<QV4::CompiledData::CompilationUnit> Script::precompile(QV4::Compi
return cg.generateCompilationUnit(/*generate unit data*/false);
}
-Script *Script::createFromFileOrCache(ExecutionEngine *engine, QmlContext *qmlContext, const QString &fileName, const QUrl &originalUrl)
+Script *Script::createFromFileOrCache(ExecutionEngine *engine, QmlContext *qmlContext, const QString &fileName, const QUrl &originalUrl, QString *error)
{
- if (const QV4::CompiledData::Unit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(originalUrl)) {
+ if (error)
+ error->clear();
+
+ QQmlMetaType::CachedUnitLookupError cacheError = QQmlMetaType::CachedUnitLookupError::NoError;
+ if (const QV4::CompiledData::Unit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(originalUrl, &cacheError)) {
QQmlRefPointer<QV4::CompiledData::CompilationUnit> jsUnit;
jsUnit.adopt(new QV4::CompiledData::CompilationUnit(cachedUnit));
return new QV4::Script(engine, qmlContext, jsUnit);
}
QFile f(fileName);
- if (!f.open(QIODevice::ReadOnly))
+ if (!f.open(QIODevice::ReadOnly)) {
+ if (error) {
+ if (cacheError == QQmlMetaType::CachedUnitLookupError::VersionMismatch)
+ *error = originalUrl.toString() + QString::fromUtf8(" was compiled ahead of time with an incompatible version of Qt and the original source code cannot be found. Please recompile");
+ else
+ *error = QString::fromUtf8("Error opening source file %1: %2").arg(originalUrl.toString()).arg(f.errorString());
+ }
return nullptr;
+ }
QByteArray data = f.readAll();
QString sourceCode = QString::fromUtf8(data);
diff --git a/src/qml/jsruntime/qv4script_p.h b/src/qml/jsruntime/qv4script_p.h
index cb03c6b064..b4ac150044 100644
--- a/src/qml/jsruntime/qv4script_p.h
+++ b/src/qml/jsruntime/qv4script_p.h
@@ -101,7 +101,7 @@ struct Q_QML_EXPORT Script {
QV4::Compiler::Module *module, Compiler::JSUnitGenerator *unitGenerator,
const QString &fileName, const QString &finalUrl, const QString &source,
QList<QQmlError> *reportedErrors = nullptr, QQmlJS::Directives *directivesCollector = nullptr);
- static Script *createFromFileOrCache(ExecutionEngine *engine, QmlContext *qmlContext, const QString &fileName, const QUrl &originalUrl);
+ static Script *createFromFileOrCache(ExecutionEngine *engine, QmlContext *qmlContext, const QString &fileName, const QUrl &originalUrl, QString *error);
static ReturnedValue evaluate(ExecutionEngine *engine, const QString &script, QmlContext *qmlContext);
};
diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp
index 7754f0fddc..8fda7f6f77 100644
--- a/src/qml/qml/qqmlmetatype.cpp
+++ b/src/qml/qml/qqmlmetatype.cpp
@@ -51,6 +51,7 @@
#include <QtCore/qbitarray.h>
#include <QtCore/qreadwritelock.h>
#include <QtCore/private/qmetaobject_p.h>
+#include <QtCore/qloggingcategory.h>
#include <qmetatype.h>
#include <qobjectdefs.h>
@@ -63,6 +64,8 @@
#include <ctype.h>
#include "qqmlcomponent.h"
+Q_DECLARE_LOGGING_CATEGORY(DBG_DISK_CACHE)
+
QT_BEGIN_NAMESPACE
struct QQmlMetaTypeData
@@ -2539,18 +2542,46 @@ QList<QQmlType> QQmlMetaType::qmlSingletonTypes()
return retn;
}
-const QV4::CompiledData::Unit *QQmlMetaType::findCachedCompilationUnit(const QUrl &uri)
+const QV4::CompiledData::Unit *QQmlMetaType::findCachedCompilationUnit(const QUrl &uri, CachedUnitLookupError *status)
{
QMutexLocker lock(metaTypeDataLock());
QQmlMetaTypeData *data = metaTypeData();
for (const auto lookup : qAsConst(data->lookupCachedQmlUnit)) {
- if (const QQmlPrivate::CachedQmlUnit *unit = lookup(uri))
+ if (const QQmlPrivate::CachedQmlUnit *unit = lookup(uri)) {
+ QString error;
+ if (!unit->qmlData->verifyHeader(QDateTime(), &error)) {
+ qCDebug(DBG_DISK_CACHE) << "Error loading pre-compiled file " << uri << ":" << error;
+ if (status)
+ *status = CachedUnitLookupError::VersionMismatch;
+ return nullptr;
+ }
+ if (status)
+ *status = CachedUnitLookupError::NoError;
return unit->qmlData;
+ }
}
+
+ if (status)
+ *status = CachedUnitLookupError::NoUnitFound;
+
return nullptr;
}
+void QQmlMetaType::prependCachedUnitLookupFunction(QQmlPrivate::QmlUnitCacheLookupFunction handler)
+{
+ QMutexLocker lock(metaTypeDataLock());
+ QQmlMetaTypeData *data = metaTypeData();
+ data->lookupCachedQmlUnit.prepend(handler);
+}
+
+void QQmlMetaType::removeCachedUnitLookupFunction(QQmlPrivate::QmlUnitCacheLookupFunction handler)
+{
+ QMutexLocker lock(metaTypeDataLock());
+ QQmlMetaTypeData *data = metaTypeData();
+ data->lookupCachedQmlUnit.removeAll(handler);
+}
+
/*!
Returns the pretty QML type name (e.g. 'Item' instead of 'QtQuickItem') for the given object.
*/
diff --git a/src/qml/qml/qqmlmetatype_p.h b/src/qml/qml/qqmlmetatype_p.h
index 07bef526ba..cd7afc8a01 100644
--- a/src/qml/qml/qqmlmetatype_p.h
+++ b/src/qml/qml/qqmlmetatype_p.h
@@ -133,7 +133,17 @@ public:
static QList<QQmlPrivate::AutoParentFunction> parentFunctions();
- static const QV4::CompiledData::Unit *findCachedCompilationUnit(const QUrl &uri);
+ enum class CachedUnitLookupError {
+ NoError,
+ NoUnitFound,
+ VersionMismatch
+ };
+
+ static const QV4::CompiledData::Unit *findCachedCompilationUnit(const QUrl &uri, CachedUnitLookupError *status);
+
+ // used by tst_qqmlcachegen.cpp
+ static void prependCachedUnitLookupFunction(QQmlPrivate::QmlUnitCacheLookupFunction handler);
+ static void removeCachedUnitLookupFunction(QQmlPrivate::QmlUnitCacheLookupFunction handler);
static bool namespaceContainsRegistrations(const QString &, int majorVersion);
diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp
index 2b778b0b63..5572fdad44 100644
--- a/src/qml/qml/qqmltypeloader.cpp
+++ b/src/qml/qml/qqmltypeloader.cpp
@@ -1259,6 +1259,7 @@ void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QByteArray &data)
QML_MEMORY_SCOPE_URL(blob->url());
QQmlDataBlob::SourceCodeData d;
d.inlineSourceCode = QString::fromUtf8(data);
+ d.hasInlineSourceCode = true;
setData(blob, d);
}
@@ -1670,9 +1671,11 @@ QQmlTypeData *QQmlTypeLoader::getType(const QUrl &url, Mode mode)
typeData = new QQmlTypeData(url, this);
// TODO: if (compiledData == 0), is it safe to omit this insertion?
m_typeCache.insert(url, typeData);
- if (const QV4::CompiledData::Unit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(typeData->url())) {
+ QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError;
+ if (const QV4::CompiledData::Unit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(typeData->url(), &error)) {
QQmlTypeLoader::loadWithCachedUnit(typeData, cachedUnit, mode);
} else {
+ typeData->setCachedUnitStatus(error);
QQmlTypeLoader::load(typeData, mode);
}
} else if ((mode == PreferSynchronous || mode == Synchronous) && QQmlFile::isSynchronous(url)) {
@@ -1727,9 +1730,11 @@ QQmlScriptBlob *QQmlTypeLoader::getScript(const QUrl &url)
scriptBlob = new QQmlScriptBlob(url, this);
m_scriptCache.insert(url, scriptBlob);
- if (const QV4::CompiledData::Unit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(scriptBlob->url())) {
+ QQmlMetaType::CachedUnitLookupError error;
+ if (const QV4::CompiledData::Unit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(scriptBlob->url(), &error)) {
QQmlTypeLoader::loadWithCachedUnit(scriptBlob, cachedUnit);
} else {
+ scriptBlob->setCachedUnitStatus(error);
QQmlTypeLoader::load(scriptBlob);
}
}
@@ -2428,8 +2433,13 @@ void QQmlTypeData::dataReceived(const SourceCodeData &data)
if (isError())
return;
- if (!m_backupSourceCode.exists()) {
- setError(QQmlTypeLoader::tr("No such file or directory"));
+ if (!m_backupSourceCode.exists() || m_backupSourceCode.isEmpty()) {
+ if (m_cachedUnitStatus == QQmlMetaType::CachedUnitLookupError::VersionMismatch)
+ setError(QQmlTypeLoader::tr("File was compiled ahead of time with an incompatible version of Qt and the original file cannot be found. Please recompile"));
+ else if (!m_backupSourceCode.exists())
+ setError(QQmlTypeLoader::tr("No such file or directory"));
+ else
+ setError(QQmlTypeLoader::tr("File is empty"));
return;
}
@@ -2981,6 +2991,13 @@ void QQmlScriptBlob::dataReceived(const SourceCodeData &data)
}
}
+ if (!data.exists()) {
+ if (m_cachedUnitStatus == QQmlMetaType::CachedUnitLookupError::VersionMismatch)
+ setError(QQmlTypeLoader::tr("File was compiled ahead of time with an incompatible version of Qt and the original file cannot be found. Please recompile"));
+ else
+ setError(QQmlTypeLoader::tr("No such file or directory"));
+ return;
+ }
QmlIR::Document irUnit(isDebugging());
@@ -3178,7 +3195,7 @@ void QQmlQmldirData::initializeFromCachedUnit(const QV4::CompiledData::Unit *)
QString QQmlDataBlob::SourceCodeData::readAll(QString *error) const
{
error->clear();
- if (!inlineSourceCode.isEmpty())
+ if (hasInlineSourceCode)
return inlineSourceCode;
QFile f(fileInfo.absoluteFilePath());
@@ -3205,7 +3222,7 @@ QString QQmlDataBlob::SourceCodeData::readAll(QString *error) const
QDateTime QQmlDataBlob::SourceCodeData::sourceTimeStamp() const
{
- if (!inlineSourceCode.isEmpty())
+ if (hasInlineSourceCode)
return QDateTime();
QDateTime timeStamp = fileInfo.lastModified();
@@ -3220,11 +3237,18 @@ QDateTime QQmlDataBlob::SourceCodeData::sourceTimeStamp() const
bool QQmlDataBlob::SourceCodeData::exists() const
{
- if (!inlineSourceCode.isEmpty())
+ if (hasInlineSourceCode)
return true;
return fileInfo.exists();
}
+bool QQmlDataBlob::SourceCodeData::isEmpty() const
+{
+ if (hasInlineSourceCode)
+ return inlineSourceCode.isEmpty();
+ return fileInfo.size() == 0;
+}
+
QT_END_NAMESPACE
#include "qqmltypeloader.moc"
diff --git a/src/qml/qml/qqmltypeloader_p.h b/src/qml/qml/qqmltypeloader_p.h
index 4665d342c1..5988632547 100644
--- a/src/qml/qml/qqmltypeloader_p.h
+++ b/src/qml/qml/qqmltypeloader_p.h
@@ -140,11 +140,13 @@ public:
QString readAll(QString *error) const;
QDateTime sourceTimeStamp() const;
bool exists() const;
+ bool isEmpty() const;
private:
friend class QQmlDataBlob;
friend class QQmlTypeLoader;
QString inlineSourceCode;
QFileInfo fileInfo;
+ bool hasInlineSourceCode = false;
};
protected:
@@ -271,6 +273,8 @@ public:
const QQmlImports &imports() const { return m_importCache; }
+ void setCachedUnitStatus(QQmlMetaType::CachedUnitLookupError status) { m_cachedUnitStatus = status; }
+
protected:
bool addImport(const QV4::CompiledData::Import *import, QList<QQmlError> *errors);
@@ -293,6 +297,7 @@ public:
QQmlImports m_importCache;
QHash<const QV4::CompiledData::Import*, int> m_unresolvedImports;
QList<QQmlQmldirData *> m_qmldirs;
+ QQmlMetaType::CachedUnitLookupError m_cachedUnitStatus = QQmlMetaType::CachedUnitLookupError::NoError;
};
QQmlTypeLoader(QQmlEngine *);
diff --git a/src/qml/types/qquickworkerscript.cpp b/src/qml/types/qquickworkerscript.cpp
index 80c2d3a4bc..ef92e4108d 100644
--- a/src/qml/types/qquickworkerscript.cpp
+++ b/src/qml/types/qquickworkerscript.cpp
@@ -397,9 +397,11 @@ void QQuickWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url)
QV4::Scoped<QV4::QmlContext> qmlContext(scope, getWorker(script));
Q_ASSERT(!!qmlContext);
- program.reset(QV4::Script::createFromFileOrCache(v4, qmlContext, fileName, url));
+ QString error;
+ program.reset(QV4::Script::createFromFileOrCache(v4, qmlContext, fileName, url, &error));
if (program.isNull()) {
- qWarning().nospace() << "WorkerScript: Cannot find source file " << url.toString();
+ if (!error.isEmpty())
+ qWarning().nospace() << error;
return;
}
diff --git a/tests/auto/qml/qmlcachegen/qmlcachegen.pro b/tests/auto/qml/qmlcachegen/qmlcachegen.pro
index a8b72224ba..bad912c781 100644
--- a/tests/auto/qml/qmlcachegen/qmlcachegen.pro
+++ b/tests/auto/qml/qmlcachegen/qmlcachegen.pro
@@ -8,4 +8,6 @@ workerscripts_test.files = worker.js worker.qml
workerscripts_test.prefix = /workerscripts
RESOURCES += workerscripts_test
+RESOURCES += versionchecks.qml
+
QT += core-private qml-private testlib
diff --git a/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp b/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp
index e36edca699..1c05005c90 100644
--- a/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp
+++ b/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp
@@ -33,6 +33,7 @@
#include <QProcess>
#include <QLibraryInfo>
#include <QSysInfo>
+#include <QLoggingCategory>
#include <private/qqmlcomponent_p.h>
class tst_qmlcachegen: public QObject
@@ -48,6 +49,7 @@ private slots:
void errorOnArgumentsInSignalHandler();
void aheadOfTimeCompilation();
void functionExpressions();
+ void versionChecksForAheadOfTimeUnits();
void workerScripts();
};
@@ -280,6 +282,50 @@ void tst_qmlcachegen::aheadOfTimeCompilation()
QCOMPARE(result.toInt(), 42);
}
+static QQmlPrivate::CachedQmlUnit *temporaryModifiedCachedUnit = nullptr;
+
+void tst_qmlcachegen::versionChecksForAheadOfTimeUnits()
+{
+ QVERIFY(QFile::exists(":/versionchecks.qml"));
+ QCOMPARE(QFileInfo(":/versionchecks.qml").size(), 0);
+
+ Q_ASSERT(!temporaryModifiedCachedUnit);
+ QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError;
+ const QV4::CompiledData::Unit *originalUnit = QQmlMetaType::findCachedCompilationUnit(QUrl("qrc:/versionchecks.qml"), &error);
+ QVERIFY(originalUnit);
+ QV4::CompiledData::Unit *tweakedUnit = (QV4::CompiledData::Unit *)malloc(originalUnit->unitSize);
+ memcpy(reinterpret_cast<void *>(tweakedUnit), reinterpret_cast<const void *>(originalUnit), originalUnit->unitSize);
+ tweakedUnit->version = QV4_DATA_STRUCTURE_VERSION - 1;
+ temporaryModifiedCachedUnit = new QQmlPrivate::CachedQmlUnit{tweakedUnit, nullptr, nullptr};
+
+ auto testHandler = [](const QUrl &url) -> const QQmlPrivate::CachedQmlUnit * {
+ if (url == QUrl("qrc:/versionchecks.qml"))
+ return temporaryModifiedCachedUnit;
+ return nullptr;
+ };
+ QQmlMetaType::prependCachedUnitLookupFunction(testHandler);
+
+ {
+ QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError;
+ QVERIFY(!QQmlMetaType::findCachedCompilationUnit(QUrl("qrc:/versionchecks.qml"), &error));
+ QCOMPARE(error, QQmlMetaType::CachedUnitLookupError::VersionMismatch);
+ }
+
+ {
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl("qrc:/versionchecks.qml"));
+ QCOMPARE(component.status(), QQmlComponent::Error);
+ QCOMPARE(component.errorString(), QString("qrc:/versionchecks.qml:-1 File was compiled ahead of time with an incompatible version of Qt and the original file cannot be found. Please recompile\n"));
+ }
+
+ Q_ASSERT(temporaryModifiedCachedUnit);
+ free(const_cast<QV4::CompiledData::Unit *>(temporaryModifiedCachedUnit->qmlData));
+ delete temporaryModifiedCachedUnit;
+ temporaryModifiedCachedUnit = nullptr;
+
+ QQmlMetaType::removeCachedUnitLookupFunction(testHandler);
+}
+
void tst_qmlcachegen::workerScripts()
{
QVERIFY(QFile::exists(":/workerscripts/worker.js"));
diff --git a/tests/auto/qml/qmlcachegen/versionchecks.qml b/tests/auto/qml/qmlcachegen/versionchecks.qml
new file mode 100644
index 0000000000..77d67e7da4
--- /dev/null
+++ b/tests/auto/qml/qmlcachegen/versionchecks.qml
@@ -0,0 +1,4 @@
+import QtQml 2.0
+QtObject {
+ property bool ok: true
+}
diff --git a/tests/auto/qml/qqmlecmascript/data/include_callback.js b/tests/auto/qml/qqmlecmascript/data/include_callback.js
index ea19eba300..7f3195bb1f 100644
--- a/tests/auto/qml/qqmlecmascript/data/include_callback.js
+++ b/tests/auto/qml/qqmlecmascript/data/include_callback.js
@@ -1,6 +1,6 @@
function go() {
var a = Qt.include("missing.js", function(o) { test2 = o.status == o.NETWORK_ERROR });
- test1 = a.status == a.NETWORK_ERROR
+ test1 = (a.status == a.NETWORK_ERROR) && (a.statusText.indexOf("Error opening source file") != -1);
var b = Qt.include("blank.js", function(o) { test4 = o.status == o.OK });
test3 = b.status == b.OK
diff --git a/tests/auto/qml/qqmllanguage/data/empty.errors.txt b/tests/auto/qml/qqmllanguage/data/empty.errors.txt
index 620db2bbba..ba685d78ae 100644
--- a/tests/auto/qml/qqmllanguage/data/empty.errors.txt
+++ b/tests/auto/qml/qqmllanguage/data/empty.errors.txt
@@ -1,2 +1 @@
-1:1:Expected token `numeric literal'
-1:1:Expected a qualified name id
+-1:-1:File is empty