// Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qv4compileddata_p.h" #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE namespace QV4 { namespace CompiledData { bool Unit::verifyHeader(QDateTime expectedSourceTimeStamp, QString *errorString) const { 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 (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; } /*! \internal This function creates a temporary key vector and sorts it to guarantuee a stable hash. This is used to calculate a check-sum on dependent meta-objects. */ bool ResolvedTypeReferenceMap::addToHash( QCryptographicHash *hash, QHash *checksums) const { std::vector keys (size()); int i = 0; for (auto it = constBegin(), end = constEnd(); it != end; ++it) { keys[i] = it.key(); ++i; } std::sort(keys.begin(), keys.end()); for (int key: keys) { if (!this->operator[](key)->addToHash(hash, checksums)) return false; } return true; } CompilationUnit::CompilationUnit( const Unit *unitData, const QString &fileName, const QString &finalUrlString) { setUnitData(unitData, nullptr, fileName, finalUrlString); } CompilationUnit::~CompilationUnit() { qDeleteAll(resolvedTypes); if (data) { if (data->qmlUnit() != qmlData) free(const_cast(qmlData)); qmlData = nullptr; if (!(data->flags & QV4::CompiledData::Unit::StaticData)) free(const_cast(data)); } data = nullptr; #if Q_BYTE_ORDER == Q_BIG_ENDIAN delete [] constants; constants = nullptr; #endif } QString CompilationUnit::localCacheFilePath(const QUrl &url) { static const QByteArray envCachePath = qgetenv("QML_DISK_CACHE_PATH"); const QString localSourcePath = QQmlFile::urlToLocalFileOrQrc(url); const QString cacheFileSuffix = QFileInfo(localSourcePath + QLatin1Char('c')).completeSuffix(); QCryptographicHash fileNameHash(QCryptographicHash::Sha1); fileNameHash.addData(localSourcePath.toUtf8()); QString directory = envCachePath.isEmpty() ? QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1String("/qmlcache/") : QString::fromLocal8Bit(envCachePath) + QLatin1String("/"); QDir::root().mkpath(directory); return directory + QString::fromUtf8(fileNameHash.result().toHex()) + QLatin1Char('.') + cacheFileSuffix; } bool CompilationUnit::loadFromDisk( const QUrl &url, const QDateTime &sourceTimeStamp, QString *errorString) { if (!QQmlFile::isLocalFile(url)) { *errorString = QStringLiteral("File has to be a local file."); return false; } const QString sourcePath = QQmlFile::urlToLocalFileOrQrc(url); auto cacheFile = std::make_unique(); const QStringList cachePaths = { sourcePath + QLatin1Char('c'), localCacheFilePath(url) }; for (const QString &cachePath : cachePaths) { Unit *mappedUnit = cacheFile->get(cachePath, sourceTimeStamp, errorString); if (!mappedUnit) continue; const Unit *oldData = unitData(); const Unit * const oldDataPtr = (oldData && !(oldData->flags & Unit::StaticData)) ? oldData : nullptr; auto dataPtrRevert = qScopeGuard([this, oldData](){ setUnitData(oldData); }); setUnitData(mappedUnit); if (mappedUnit->sourceFileIndex != 0) { if (mappedUnit->sourceFileIndex >= mappedUnit->stringTableSize + dynamicStrings.size()) { *errorString = QStringLiteral("QML source file index is invalid."); continue; } if (sourcePath != QQmlFile::urlToLocalFileOrQrc(stringAt(mappedUnit->sourceFileIndex))) { *errorString = QStringLiteral("QML source file has moved to a different location."); continue; } } dataPtrRevert.dismiss(); free(const_cast(oldDataPtr)); backingFile = std::move(cacheFile); return true; } return false; } bool CompilationUnit::saveToDisk(const QUrl &unitUrl, QString *errorString) { if (unitData()->sourceTimeStamp == 0) { *errorString = QStringLiteral("Missing time stamp for source file"); return false; } if (!QQmlFile::isLocalFile(unitUrl)) { *errorString = QStringLiteral("File has to be a local file."); return false; } return SaveableUnitPointer(unitData()).saveToDisk( [&unitUrl, errorString](const char *data, quint32 size) { const QString cachePath = localCacheFilePath(unitUrl); if (SaveableUnitPointer::writeDataToFile( cachePath, data, size, errorString)) { CompilationUnitMapper::invalidate(cachePath); return true; } return false; }); } QStringList CompilationUnit::moduleRequests() const { QStringList requests; requests.reserve(data->moduleRequestTableSize); for (uint i = 0; i < data->moduleRequestTableSize; ++i) requests << stringAt(data->moduleRequestTable()[i]); return requests; } ResolvedTypeReference *CompilationUnit::resolvedType(QMetaType type) const { for (ResolvedTypeReference *ref : std::as_const(resolvedTypes)) { if (ref->type().typeId() == type) return ref; } return nullptr; } void CompiledData::CompilationUnit::finalizeCompositeType(const QQmlType &type) { // Add to type registry of composites if (propertyCaches.needsVMEMetaObject(/*root object*/0)) { // qmlType is only valid for types that have references to themselves. if (type.isValid()) { qmlType = type; } else { qmlType = QQmlMetaType::findCompositeType( url(), this, (unitData()->flags & CompiledData::Unit::IsSingleton) ? QQmlMetaType::Singleton : QQmlMetaType::NonSingleton); } QQmlMetaType::registerInternalCompositeType(this); } else { const QV4::CompiledData::Object *obj = objectAt(/*root object*/0); auto *typeRef = resolvedTypes.value(obj->inheritedTypeNameIndex); Q_ASSERT(typeRef); if (const auto compilationUnit = typeRef->compilationUnit()) qmlType = compilationUnit->qmlType; else qmlType = typeRef->type(); } } bool CompilationUnit::verifyChecksum(const DependentTypesHasher &dependencyHasher) const { if (!dependencyHasher) { for (size_t i = 0; i < sizeof(data->dependencyMD5Checksum); ++i) { if (data->dependencyMD5Checksum[i] != 0) return false; } return true; } const QByteArray checksum = dependencyHasher(); return checksum.size() == sizeof(data->dependencyMD5Checksum) && memcmp(data->dependencyMD5Checksum, checksum.constData(), sizeof(data->dependencyMD5Checksum)) == 0; } QQmlType CompilationUnit::qmlTypeForComponent(const QString &inlineComponentName) const { if (inlineComponentName.isEmpty()) return qmlType; return inlineComponentData[inlineComponentName].qmlType; } } // namespace CompiledData } // namespace QV4 QT_END_NAMESPACE