Revision control
Copy as Markdown
// Copyright (C) 2026 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
// Qt-Security score:significant reason:default
#include "qqmlpreviewdiff_p.h"
#include <private/qv4staticvalue_p.h>
#include <QtCore/qmap.h>
#include <QtCore/qscopedvaluerollback.h>
#include <QtCore/qset.h>
#include <cstring>
QT_BEGIN_NAMESPACE
// IMPORTANT:
//
// If you update the data structures in qv4compileddata_p.h, then you also need to
// update the comparison functions below. Only after making sure the comparisons
// are fine, bump the version in the static_assert.
static_assert(QV4_DATA_STRUCTURE_VERSION == 0x53);
namespace QV4::CompiledData {
using Constant = quint64_le;
using StringData = QString;
template <typename Data>
Data extractData(const Data &data)
{
return data;
}
static EnumHunk extractData(const Enum &e)
{
EnumHunk data;
data.data = e;
for (const EnumValue *v = e.enumValuesBegin(); v != e.enumValuesEnd(); ++v)
data.values.append(*v);
return data;
}
static SignalHunk extractData(const Signal &s)
{
SignalHunk data;
data.data = s;
for (quint32 i = 0; i < s.nParameters; ++i)
data.parameters.append(*s.parameterAt(i));
return data;
}
static FunctionHunk extractData(const Function &func)
{
FunctionHunk data;
data.data = func;
for (quint16 i = 0, end = func.nFormals; i != end; ++i)
data.formals.append(func.formalsTable()[i]);
for (quint16 i = 0, end = func.nLocals; i != end; ++i)
data.locals.append(func.localsTable()[i]);
for (quint16 i = 0, end = func.nLineAndStatementNumbers; i != end; ++i)
data.lineAndStatementNumbers.append(func.lineAndStatementNumberTable()[i]);
for (quint32 i = 0, end = func.nLabelInfos; i != end; ++i)
data.labelInfos.append(func.labelInfoTable()[i]);
data.code = QByteArray(func.code(), func.codeSize);
return data;
}
static ObjectHunk extractData(const Object &obj)
{
ObjectHunk data;
data.data = obj;
for (quint32 i = 0; i < obj.nFunctions; ++i)
data.functionIndices.append(extractData(obj.functionOffsetTable()[i]));
for (quint32 i = 0; i < obj.nBindings; ++i)
data.bindings.append(extractData(obj.bindingTable()[i]));
for (quint32 i = 0; i < obj.nProperties; ++i)
data.properties.append(extractData(obj.propertyTable()[i]));
for (quint32 i = 0; i < obj.nAliases; ++i)
data.aliases.append(extractData(obj.aliasTable()[i]));
for (quint32 i = 0; i < obj.nSignals; ++i)
data.sigs.append(extractData(*obj.signalAt(i)));
for (quint32 i = 0; i < obj.nEnums; ++i)
data.enums.append(extractData(*obj.enumAt(i)));
for (quint32 i = 0; i < obj.nNamedObjectsInComponent; ++i) {
data.namedObjectsInComponentIndices.append(
extractData(obj.namedObjectsInComponentTable()[i]));
}
for (quint32 i = 0; i < obj.nInlineComponents; ++i)
data.inlineComponents.append(extractData(obj.inlineComponentTable()[i]));
for (quint32 i = 0; i < obj.nRequiredPropertyExtraData; ++i)
data.requiredPropertyExtraData.append(extractData(obj.requiredPropertyExtraDataTable()[i]));
return data;
}
static UnitHunk extractData(const Unit &unit)
{
return unit;
}
static ClassHunk extractData(const Class &cls)
{
ClassHunk data;
data.data = cls;
const Method *methods = cls.methodTable();
for (quint32 i = 0, end = cls.nStaticMethods + cls.nMethods; i < end; ++i)
data.methods.append(methods[i]);
return data;
}
static TemplateObjectHunk extractData(const TemplateObject &to)
{
TemplateObjectHunk data;
data.size = to.size;
const quint32_le *strings = reinterpret_cast<const quint32_le *>(&to + 1);
for (quint32 i = 0; i < 2u * to.size; ++i)
data.strings.append(strings[i]);
return data;
}
static JsClassHunk extractData(const JSClass &jsClass)
{
JsClassHunk data;
data.nMembers = jsClass.nMembers;
const JSClassMember *members = reinterpret_cast<const JSClassMember *>(&jsClass + 1);
for (quint32 i = 0; i < jsClass.nMembers; ++i)
data.members.append(members[i]);
return data;
}
static BlockHunk extractData(const Block &block)
{
BlockHunk data;
data.data = block;
for (quint32 i = 0; i < block.nLocals; ++i)
data.locals.append(block.localsTable()[i]);
return data;
}
// location extraction
template <typename T, typename L = void>
struct LocationExtractor
{
static bool extract(const T &) { return false; }
};
template <typename T>
struct LocationExtractor<T, std::enable_if_t<std::is_same_v<decltype(T::location), Location>>>
{
static Location extract(const T &t) { return t.location; }
};
template <>
struct LocationExtractor<Binding, void>
{
static std::pair<Location, Location> extract(const Binding &binding)
{
return { binding.location, binding.valueLocation };
}
};
template <>
struct LocationExtractor<Alias, void>
{
static std::pair<Location, Location> extract(const Alias &alias)
{
return { alias.location(), alias.referenceLocation() };
}
};
template <>
struct LocationExtractor<Object, void>
{
static std::pair<Location, Location> extract(const Object &object)
{
return { object.location, object.locationOfIdProperty };
}
};
template <typename ElementT>
struct ComparisonTraits;
#define COMPARISON_TRAITS(Element) \
template <> \
struct ComparisonTraits<Element> \
{ \
static constexpr ChangeType Changed = ChangeType::Element ## Changed; \
static constexpr ChangeType LocationChanged = ChangeType::None; \
static constexpr ChangeType Removed = ChangeType::Element ## Removed; \
static constexpr ChangeType Added = ChangeType::Element ## Added; \
};
#define COMPARISON_TRAITS_WITH_LOCATION(Element) \
template <> \
struct ComparisonTraits<Element> \
{ \
static constexpr ChangeType Changed = ChangeType::Element ## Changed; \
static constexpr ChangeType LocationChanged = ChangeType::Element ## LocationChanged; \
static constexpr ChangeType Removed = ChangeType::Element ## Removed; \
static constexpr ChangeType Added = ChangeType::Element ## Added; \
};
COMPARISON_TRAITS_WITH_LOCATION(Object)
COMPARISON_TRAITS_WITH_LOCATION(Property)
COMPARISON_TRAITS_WITH_LOCATION(Alias)
COMPARISON_TRAITS_WITH_LOCATION(Enum)
COMPARISON_TRAITS_WITH_LOCATION(Signal)
COMPARISON_TRAITS_WITH_LOCATION(InlineComponent)
COMPARISON_TRAITS_WITH_LOCATION(Binding)
COMPARISON_TRAITS_WITH_LOCATION(Import)
COMPARISON_TRAITS_WITH_LOCATION(Function)
COMPARISON_TRAITS(RequiredPropertyExtraData)
COMPARISON_TRAITS(Constant)
COMPARISON_TRAITS(Lookup)
COMPARISON_TRAITS(RegExp)
COMPARISON_TRAITS(Class)
COMPARISON_TRAITS(TemplateObject)
COMPARISON_TRAITS(JSClass)
COMPARISON_TRAITS(Block)
COMPARISON_TRAITS(TranslationData)
COMPARISON_TRAITS(StringData)
template <typename AccessorFn>
struct IndexedElements
{
AccessorFn accessor;
quint32 count;
};
template <typename AccessorFn>
IndexedElements(AccessorFn, quint32) -> IndexedElements<AccessorFn>;
struct UnitDiffer
{
const Unit *const oldUnit;
const Unit *const newUnit;
QList<Change> changes;
int m_currentObjectIndex = -1;
template <typename ElementT, typename OldSource, typename NewSource, typename ContentEqFn>
void compareIndexedElements(OldSource oldSource, NewSource newSource, ContentEqFn contentEq)
{
using Traits = ComparisonTraits<ElementT>;
const auto &oldAccessor = oldSource.accessor;
const auto &newAccessor = newSource.accessor;
const quint32 oldCount = oldSource.count;
const quint32 newCount = newSource.count;
quint32 commonCount = qMin(oldCount, newCount);
// Compare elements at common positions
for (quint32 i = 0; i < commonCount; ++i) {
const ElementT &oldElem = oldAccessor(i);
const ElementT &newElem = newAccessor(i);
if (!contentEq(oldElem, newElem)) {
Change change;
change.objectIndex = m_currentObjectIndex;
change.index = i;
change.type = Traits::Changed;
change.data = extractData(newElem);
changes.append(change);
} else if (LocationExtractor<ElementT>::extract(oldElem)
!= LocationExtractor<ElementT>::extract(newElem)) {
Change change;
change.objectIndex = m_currentObjectIndex;
change.index = i;
change.type = Traits::LocationChanged;
change.data = extractData(newElem);
changes.append(change);
}
}
// Emit Removed for extra elements in old
for (quint32 i = commonCount; i < oldCount; ++i) {
Change change;
change.type = Traits::Removed;
change.objectIndex = m_currentObjectIndex;
change.index = i;
changes.append(change);
}
// Emit Added for extra elements in new
for (quint32 i = commonCount; i < newCount; ++i) {
Change change;
change.type = Traits::Added;
change.objectIndex = m_currentObjectIndex;
change.index = i;
change.data = extractData(newAccessor(i));
changes.append(change);
}
}
bool stringsEqual(uint oldName, uint newName) const
{
Q_ASSERT(oldName < oldUnit->stringTableSize);
Q_ASSERT(newName < newUnit->stringTableSize);
return oldName == newName
&& oldUnit->stringAtInternal(oldName) == newUnit->stringAtInternal(newName);
}
bool unitMetadataContentEqual() const
{
if (std::memcmp(oldUnit->magic, newUnit->magic, sizeof(oldUnit->magic)) != 0)
return false;
if (oldUnit->version != newUnit->version || oldUnit->reserved != newUnit->reserved
|| oldUnit->sourceTimeStamp != newUnit->sourceTimeStamp
|| oldUnit->unitSize != newUnit->unitSize) {
return false;
}
// sourceChecksum lives before md5Checksum and is therefore not covered by it.
if (std::memcmp(oldUnit->sourceChecksum, newUnit->sourceChecksum,
sizeof(oldUnit->sourceChecksum))
!= 0) {
return false;
}
// If anything below the checksum has changed, the checksum itself has changed, too
return std::memcmp(oldUnit->md5Checksum, newUnit->md5Checksum, sizeof(oldUnit->md5Checksum))
== 0;
}
bool objectContentEqual(const Object &oldObj, const Object &newObj) const
{
// Keep in sync with the Object struct in qv4compileddata_p.h
static_assert(sizeof(Object) == 84, "Update objectContentEqual when Object layout changes");
if (oldObj.nNamedObjectsInComponent != newObj.nNamedObjectsInComponent)
return false;
const quint32_le *oldNamed = oldObj.namedObjectsInComponentTable();
const quint32_le *newNamed = newObj.namedObjectsInComponentTable();
for (quint32 i = 0; i < oldObj.nNamedObjectsInComponent; ++i) {
if (oldNamed[i] != newNamed[i])
return false;
}
if (oldObj.nFunctions != newObj.nFunctions)
return false;
const quint32_le *oldFuncs = oldObj.functionOffsetTable();
const quint32_le *newFuncs = newObj.functionOffsetTable();
for (quint32 i = 0; i < oldObj.nFunctions; ++i) {
if (oldFuncs[i] != newFuncs[i])
return false;
}
return stringsEqual(oldObj.inheritedTypeNameIndex, newObj.inheritedTypeNameIndex)
&& stringsEqual(oldObj.idNameIndex, newObj.idNameIndex)
&& oldObj.flags() == newObj.flags()
&& oldObj.hasAliasAsDefaultProperty() == newObj.hasAliasAsDefaultProperty()
&& oldObj.objectId() == newObj.objectId()
&& oldObj.indexOfDefaultPropertyOrAlias == newObj.indexOfDefaultPropertyOrAlias;
}
bool propertyContentEqual(const Property &oldProp, const Property &newProp) const
{
// Keep in sync with the Property struct in qv4compileddata_p.h
static_assert(sizeof(Property) == 12, "Update propertyContentEqual when Property layout changes");
return stringsEqual(oldProp.nameIndex(), newProp.nameIndex())
&& (oldProp.isCommonType() == newProp.isCommonType()
&& (oldProp.isCommonType()
? oldProp.commonTypeOrTypeNameIndex()
== newProp.commonTypeOrTypeNameIndex()
: stringsEqual(oldProp.typeNameIndex(), newProp.typeNameIndex())))
&& oldProp.isReadOnly() == newProp.isReadOnly()
&& oldProp.isRequired() == newProp.isRequired()
&& oldProp.isList() == newProp.isList()
&& oldProp.isVirtual() == newProp.isVirtual()
&& oldProp.isOverride() == newProp.isOverride()
&& oldProp.isFinal() == newProp.isFinal();
}
// In addition to structural equality, also compare the actual name strings: the integer
// nameIndex may be the same while the string at that index changed (property rename).
bool aliasContentEqual(const Alias &oldAlias, const Alias &newAlias) const
{
// Keep in sync with the Alias struct in qv4compileddata_p.h
static_assert(sizeof(Alias) == 20, "Update aliasContentEqual when Alias layout changes");
return stringsEqual(oldAlias.idIndex(), newAlias.idIndex())
&& stringsEqual(oldAlias.nameIndex(), newAlias.nameIndex())
&& stringsEqual(oldAlias.propertyNameIndex(), newAlias.propertyNameIndex())
&& oldAlias.isReadOnly() == newAlias.isReadOnly();
}
bool enumContentEqual(const Enum &oldEnum, const Enum &newEnum) const
{
// Keep in sync with the Enum and EnumValue structs in qv4compileddata_p.h
static_assert(sizeof(Enum) == 12, "Update enumContentEqual when Enum layout changes");
static_assert(sizeof(EnumValue) == 8, "Update enumContentEqual when EnumValue layout changes");
if (!stringsEqual(oldEnum.nameIndex, newEnum.nameIndex)
|| oldEnum.nEnumValues != newEnum.nEnumValues) {
return false;
}
for (quint32 i = 0; i < oldEnum.nEnumValues; ++i) {
const EnumValue *oldValue = oldEnum.enumValueAt(i);
const EnumValue *newValue = newEnum.enumValueAt(i);
if (oldValue->value != newValue->value
|| !stringsEqual(oldValue->nameIndex, newValue->nameIndex)) {
return false;
}
}
return true;
}
bool signalContentEqual(const Signal &oldSig, const Signal &newSig) const
{
// Keep in sync with the Signal and Parameter structs in qv4compileddata_p.h
static_assert(sizeof(Signal) == 12, "Update signalContentEqual when Signal layout changes");
static_assert(sizeof(Parameter) == 8, "Update signalContentEqual when Parameter layout changes");
if (!stringsEqual(oldSig.nameIndex, newSig.nameIndex))
return false;
if (oldSig.nParameters != newSig.nParameters)
return false;
for (quint32 i = 0; i < oldSig.nParameters; ++i) {
const Parameter *oldP = oldSig.parameterAt(i);
const Parameter *newP = newSig.parameterAt(i);
if (!stringsEqual(oldP->nameIndex, newP->nameIndex)
|| !parameterTypeContentEqual(oldP->type, newP->type)) {
return false;
}
}
return true;
}
bool bindingContentEqual(const Binding &oldBinding, const Binding &newBinding) const
{
// Keep in sync with the Binding and TranslationData structs in qv4compileddata_p.h
static_assert(sizeof(Binding) == 24, "Update bindingContentEqual when Binding layout changes");
static_assert(sizeof(TranslationData) == 16, "Update bindingContentEqual when TranslationData layout changes");
if (oldBinding.type() != newBinding.type() || oldBinding.flags() != newBinding.flags())
return false;
if (!stringsEqual(oldBinding.propertyNameIndex, newBinding.propertyNameIndex))
return false;
switch (oldBinding.type()) {
case Binding::Type_Invalid:
return true;
case Binding::Type_Boolean:
return oldBinding.valueAsBoolean() == newBinding.valueAsBoolean();
case Binding::Type_Number: {
const uint oldIdx = oldBinding.value.constantValueIndex;
const uint newIdx = newBinding.value.constantValueIndex;
Q_ASSERT(oldIdx < oldUnit->constantTableSize);
Q_ASSERT(newIdx < newUnit->constantTableSize);
return oldIdx == newIdx && oldUnit->constants()[oldIdx] == newUnit->constants()[newIdx];
}
case Binding::Type_String:
return stringsEqual(oldBinding.stringIndex, newBinding.stringIndex);
case Binding::Type_Null:
return true;
case Binding::Type_Translation:
case Binding::Type_TranslationById: {
const auto &oldTrans = oldUnit->translations()[oldBinding.value.translationDataIndex];
const auto &newTrans = newUnit->translations()[newBinding.value.translationDataIndex];
// contextIndex uses NoContextIndex as sentinel; compare by
// resolved string only when both are valid indices.
const bool oldHasCtx = oldTrans.contextIndex != TranslationData::NoContextIndex;
const bool newHasCtx = newTrans.contextIndex != TranslationData::NoContextIndex;
if (oldHasCtx != newHasCtx)
return false;
if (oldHasCtx && !stringsEqual(oldTrans.contextIndex, newTrans.contextIndex))
return false;
return stringsEqual(oldBinding.stringIndex, newBinding.stringIndex)
&& stringsEqual(oldTrans.stringIndex, newTrans.stringIndex)
&& stringsEqual(oldTrans.commentIndex, newTrans.commentIndex)
&& oldTrans.number == newTrans.number;
}
case Binding::Type_Script:
return oldBinding.value.compiledScriptIndex == newBinding.value.compiledScriptIndex;
case Binding::Type_Object:
case Binding::Type_AttachedProperty:
case Binding::Type_GroupedProperty:
return oldBinding.value.objectIndex == newBinding.value.objectIndex
&& objectContentEqual(
*oldUnit->qmlUnit()->objectAt(oldBinding.value.objectIndex),
*newUnit->qmlUnit()->objectAt(newBinding.value.objectIndex));
}
return false;
}
bool parameterTypeContentEqual(const ParameterType &oldType, const ParameterType &newType) const
{
// Keep in sync with the ParameterType struct in qv4compileddata_p.h
static_assert(sizeof(ParameterType) == 4, "Update parameterTypeContentEqual when ParameterType layout changes");
const bool indexIsCommonType = oldType.indexIsCommonType();
if (newType.indexIsCommonType() != indexIsCommonType
|| oldType.isList() != newType.isList()) {
return false;
}
return indexIsCommonType
? oldType.typeNameIndexOrCommonType() == newType.typeNameIndexOrCommonType()
: stringsEqual(oldType.typeNameIndexOrCommonType(),
newType.typeNameIndexOrCommonType());
}
bool functionContentEqual(const Function &oldFunc, const Function &newFunc) const
{
// Keep in sync with the Function struct in qv4compileddata_p.h
static_assert(sizeof(Function) == 56, "Update functionContentEqual when Function layout changes");
if (oldFunc.nestedFunctionIndex != newFunc.nestedFunctionIndex
|| !stringsEqual(oldFunc.nameIndex, newFunc.nameIndex) || oldFunc.flags != newFunc.flags
|| oldFunc.nFormals != newFunc.nFormals || oldFunc.codeSize != newFunc.codeSize
|| !parameterTypeContentEqual(oldFunc.returnType, newFunc.returnType)) {
return false;
}
for (int i = 0, end = oldFunc.nFormals; i != end; ++i) {
const auto &oldFormal = oldFunc.formalsTable()[i];
const auto &newFormal = newFunc.formalsTable()[i];
if (!parameterTypeContentEqual(oldFormal.type, newFormal.type))
return false;
if (!stringsEqual(oldFormal.nameIndex, newFormal.nameIndex))
return false;
}
return std::memcmp(oldFunc.code(), newFunc.code(), oldFunc.codeSize) == 0;
}
bool translationContentEqual(const TranslationData &oldTrans,
const TranslationData &newTrans) const
{
// Keep in sync with the TranslationData struct in qv4compileddata_p.h
static_assert(sizeof(TranslationData) == 16, "Update translationContentEqual when TranslationData layout changes");
if (oldTrans.contextIndex != newTrans.contextIndex)
return false;
if (oldTrans.contextIndex != TranslationData::NoContextIndex
&& !stringsEqual(oldTrans.contextIndex, newTrans.contextIndex)) {
return false;
}
return stringsEqual(oldTrans.stringIndex, newTrans.stringIndex)
&& stringsEqual(oldTrans.commentIndex, newTrans.commentIndex)
&& oldTrans.number == newTrans.number;
}
bool classContentEqual(const Class &oldCls, const Class &newCls) const
{
// Keep in sync with the Class and Method structs in qv4compileddata_p.h
static_assert(sizeof(Class) == 24, "Update classContentEqual when Class layout changes");
static_assert(sizeof(Method) == 12, "Update classContentEqual when Method layout changes");
if (!stringsEqual(oldCls.nameIndex, newCls.nameIndex)
|| oldCls.scopeIndex != newCls.scopeIndex
|| oldCls.constructorFunction != newCls.constructorFunction
|| oldCls.nStaticMethods != newCls.nStaticMethods
|| oldCls.nMethods != newCls.nMethods) {
return false;
}
const Method *oldMethods = oldCls.methodTable();
const Method *newMethods = newCls.methodTable();
for (quint32 i = 0, end = oldCls.nStaticMethods + oldCls.nMethods; i < end; ++i) {
if (!stringsEqual(oldMethods[i].name, newMethods[i].name)
|| oldMethods[i].type != newMethods[i].type
|| oldMethods[i].function != newMethods[i].function) {
return false;
}
}
return true;
}
bool templateObjectContentEqual(const TemplateObject &oldTO, const TemplateObject &newTO) const
{
// Keep in sync with the TemplateObject struct in qv4compileddata_p.h
static_assert(sizeof(TemplateObject) == 4, "Update templateObjectContentEqual when TemplateObject layout changes");
if (oldTO.size != newTO.size)
return false;
const quint32_le *oldStrings = reinterpret_cast<const quint32_le *>(&oldTO + 1);
const quint32_le *newStrings = reinterpret_cast<const quint32_le *>(&newTO + 1);
for (quint32 i = 0; i < 2u * oldTO.size; ++i) {
if (!stringsEqual(oldStrings[i], newStrings[i]))
return false;
}
return true;
}
bool jsClassContentEqual(const JSClass &oldJC, const JSClass &newJC) const
{
// Keep in sync with the JSClass and JSClassMember structs in qv4compileddata_p.h
static_assert(sizeof(JSClass) == 4, "Update jsClassContentEqual when JSClass layout changes");
static_assert(sizeof(JSClassMember) == 4, "Update jsClassContentEqual when JSClassMember layout changes");
if (oldJC.nMembers != newJC.nMembers)
return false;
const JSClassMember *oldMembers = reinterpret_cast<const JSClassMember *>(&oldJC + 1);
const JSClassMember *newMembers = reinterpret_cast<const JSClassMember *>(&newJC + 1);
for (quint32 i = 0; i < oldJC.nMembers; ++i) {
if (!stringsEqual(oldMembers[i].nameOffset(), newMembers[i].nameOffset())
|| oldMembers[i].isAccessor() != newMembers[i].isAccessor()) {
return false;
}
}
return true;
}
bool blockContentEqual(const Block &oldBlock, const Block &newBlock) const
{
// Keep in sync with the Block struct in qv4compileddata_p.h
static_assert(sizeof(Block) == 12, "Update blockContentEqual when Block layout changes");
if (oldBlock.nLocals != newBlock.nLocals
|| oldBlock.sizeOfLocalTemporalDeadZone != newBlock.sizeOfLocalTemporalDeadZone) {
return false;
}
for (quint32 i = 0; i < oldBlock.nLocals; ++i) {
if (!stringsEqual(oldBlock.localsTable()[i], newBlock.localsTable()[i]))
return false;
}
return true;
}
// Per-object comparison methods
void compareObjectProperties(const Object *oldObj, const Object *newObj)
{
const Property *oldProps = oldObj->propertyTable();
const Property *newProps = newObj->propertyTable();
compareIndexedElements<Property>(
IndexedElements{ [oldProps](quint32 i) { return oldProps[i]; },
oldObj->nProperties },
IndexedElements{ [newProps](quint32 i) { return newProps[i]; },
newObj->nProperties },
[this](const Property &a, const Property &b) {
return propertyContentEqual(a, b);
});
}
void compareObjectAliases(const Object *oldObj, const Object *newObj)
{
const Alias *oldAliases = oldObj->aliasesBegin();
const Alias *newAliases = newObj->aliasesBegin();
compareIndexedElements<Alias>(
IndexedElements{ [oldAliases](quint32 i) { return oldAliases[i]; },
oldObj->nAliases },
IndexedElements{ [newAliases](quint32 i) { return newAliases[i]; },
newObj->nAliases },
[this](const Alias &a, const Alias &b) { return aliasContentEqual(a, b); });
}
void compareObjectEnums(const Object *oldObj, const Object *newObj)
{
compareIndexedElements<Enum>(
IndexedElements{ [oldObj](quint32 i) -> const Enum & { return *oldObj->enumAt(i); },
oldObj->nEnums },
IndexedElements{ [newObj](quint32 i) -> const Enum & { return *newObj->enumAt(i); },
newObj->nEnums },
[this](const Enum &a, const Enum &b) { return enumContentEqual(a, b); });
}
void compareObjectSignals(const Object *oldObj, const Object *newObj)
{
compareIndexedElements<Signal>(
IndexedElements{
[oldObj](quint32 i) -> const Signal & { return *oldObj->signalAt(i); },
oldObj->nSignals },
IndexedElements{
[newObj](quint32 i) -> const Signal & { return *newObj->signalAt(i); },
newObj->nSignals },
[this](const Signal &a, const Signal &b) { return signalContentEqual(a, b); });
}
void compareObjectInlineComponents(const Object *oldObj, const Object *newObj)
{
const InlineComponent *oldICs = oldObj->inlineComponentTable();
const InlineComponent *newICs = newObj->inlineComponentTable();
compareIndexedElements<InlineComponent>(
IndexedElements{ [oldICs](quint32 i) { return oldICs[i]; },
oldObj->nInlineComponents },
IndexedElements{ [newICs](quint32 i) { return newICs[i]; },
newObj->nInlineComponents },
[this](const InlineComponent &a, const InlineComponent &b) {
return a.objectIndex == b.objectIndex && stringsEqual(a.nameIndex, b.nameIndex);
});
}
void compareObjectRequiredPropertyExtraData(const Object *oldObj, const Object *newObj)
{
const RequiredPropertyExtraData *oldRPED = oldObj->requiredPropertyExtraDataTable();
const RequiredPropertyExtraData *newRPED = newObj->requiredPropertyExtraDataTable();
compareIndexedElements<RequiredPropertyExtraData>(
IndexedElements{ [oldRPED](quint32 i) { return oldRPED[i]; },
oldObj->nRequiredPropertyExtraData },
IndexedElements{ [newRPED](quint32 i) { return newRPED[i]; },
newObj->nRequiredPropertyExtraData },
[this](const RequiredPropertyExtraData &a, const RequiredPropertyExtraData &b) {
return stringsEqual(a.nameIndex, b.nameIndex);
});
}
void compareObjectBindings(const Object *oldObj, const Object *newObj)
{
const Binding *oldBindings = oldObj->bindingTable();
const Binding *newBindings = newObj->bindingTable();
compareIndexedElements<Binding>(
IndexedElements{ [oldBindings](quint32 i) { return oldBindings[i]; },
oldObj->nBindings },
IndexedElements{ [newBindings](quint32 i) { return newBindings[i]; },
newObj->nBindings },
[this](const Binding &a, const Binding &b) { return bindingContentEqual(a, b); });
}
CompilationUnitDiff diff()
{
const QmlUnit *oldQml = oldUnit->qmlUnit();
const QmlUnit *newQml = newUnit->qmlUnit();
if (!oldQml || !newQml)
return CompilationUnitDiff();
// Check for unit metadata changes (source file, timestamp, checksum, etc.)
if (!unitMetadataContentEqual()) {
Change change;
change.type = ChangeType::UnitMetadataChanged;
change.data = extractData(*newUnit);
changes.append(change);
}
compareIndexedElements<Object>(
IndexedElements{
[oldQml](quint32 i) -> const Object & { return *oldQml->objectAt(i); },
oldQml->nObjects },
IndexedElements{
[newQml](quint32 i) -> const Object & { return *newQml->objectAt(i); },
newQml->nObjects },
[this](const Object &a, const Object &b) { return objectContentEqual(a, b); });
// Compare details of overlapping objects only
const quint32 commonCount = std::min(oldQml->nObjects, newQml->nObjects);
for (quint32 objectIndex = 0; objectIndex < commonCount; ++objectIndex) {
QScopedValueRollback guard(m_currentObjectIndex, static_cast<int>(objectIndex));
const Object *oldObj = oldQml->objectAt(objectIndex);
const Object *newObj = newQml->objectAt(objectIndex);
compareObjectProperties(oldObj, newObj);
compareObjectAliases(oldObj, newObj);
compareObjectEnums(oldObj, newObj);
compareObjectSignals(oldObj, newObj);
compareObjectInlineComponents(oldObj, newObj);
compareObjectRequiredPropertyExtraData(oldObj, newObj);
compareObjectBindings(oldObj, newObj);
}
compareIndexedElements<quint64_le>(
IndexedElements{ [this](quint32 i) { return oldUnit->constants()[i]; },
oldUnit->constantTableSize },
IndexedElements{ [this](quint32 i) { return newUnit->constants()[i]; },
newUnit->constantTableSize },
[](const quint64_le &a, const quint64_le &b) { return a == b; });
compareIndexedElements<QString>(
IndexedElements{ [this](quint32 i) { return oldUnit->stringAtInternal(i); },
oldUnit->stringTableSize },
IndexedElements{ [this](quint32 i) { return newUnit->stringAtInternal(i); },
newUnit->stringTableSize },
[](const QString &a, const QString &b) { return a == b; });
compareIndexedElements<Lookup>(
IndexedElements{ [this](quint32 i) { return oldUnit->lookupTable()[i]; },
oldUnit->lookupTableSize },
IndexedElements{ [this](quint32 i) { return newUnit->lookupTable()[i]; },
newUnit->lookupTableSize },
[this](const Lookup &a, const Lookup &b) {
return a.type() == b.type() && a.mode() == b.mode()
&& stringsEqual(a.nameIndex(), b.nameIndex());
});
compareIndexedElements<Import>(
IndexedElements{ [oldQml](quint32 i) { return *oldQml->importAt(i); },
oldQml->nImports },
IndexedElements{ [newQml](quint32 i) { return *newQml->importAt(i); },
newQml->nImports },
[this](const Import &a, const Import &b) {
return a.type == b.type && stringsEqual(a.uriIndex, b.uriIndex)
&& stringsEqual(a.qualifierIndex, b.qualifierIndex)
&& a.version == b.version;
});
compareIndexedElements<Function>(IndexedElements{ [this](quint32 i) -> const Function & {
return *oldUnit->functionAt(i);
},
oldUnit->functionTableSize },
IndexedElements{ [this](quint32 i) -> const Function & {
return *newUnit->functionAt(i);
},
newUnit->functionTableSize },
[this](const Function &a, const Function &b) {
return functionContentEqual(a, b);
});
compareIndexedElements<TranslationData>(
IndexedElements{ [this](quint32 i) { return oldUnit->translations()[i]; },
oldUnit->translationTableSize },
IndexedElements{ [this](quint32 i) { return newUnit->translations()[i]; },
newUnit->translationTableSize },
[this](const TranslationData &a, const TranslationData &b) {
return translationContentEqual(a, b);
});
compareIndexedElements<RegExp>(
IndexedElements{
[this](quint32 i) -> const RegExp & { return *oldUnit->regexpAt(i); },
oldUnit->regexpTableSize },
IndexedElements{
[this](quint32 i) -> const RegExp & { return *newUnit->regexpAt(i); },
newUnit->regexpTableSize },
[this](const RegExp &a, const RegExp &b) {
return a.flags() == b.flags() && stringsEqual(a.stringIndex(), b.stringIndex());
});
compareIndexedElements<Class>(
IndexedElements{
[this](quint32 i) -> const Class & { return *oldUnit->classAt(i); },
oldUnit->classTableSize },
IndexedElements{
[this](quint32 i) -> const Class & { return *newUnit->classAt(i); },
newUnit->classTableSize },
[this](const Class &a, const Class &b) { return classContentEqual(a, b); });
compareIndexedElements<TemplateObject>(
IndexedElements{ [this](quint32 i) -> const TemplateObject & {
return *oldUnit->templateObjectAt(i);
},
oldUnit->templateObjectTableSize },
IndexedElements{ [this](quint32 i) -> const TemplateObject & {
return *newUnit->templateObjectAt(i);
},
newUnit->templateObjectTableSize },
[this](const TemplateObject &a, const TemplateObject &b) {
return templateObjectContentEqual(a, b);
});
{
auto jsClassFromUnit = [](const Unit *unit, quint32 idx) -> const JSClass & {
const quint32_le *offsetTable = reinterpret_cast<const quint32_le *>(
reinterpret_cast<const char *>(unit) + unit->offsetToJSClassTable);
return *reinterpret_cast<const JSClass *>(reinterpret_cast<const char *>(unit)
+ offsetTable[idx]);
};
compareIndexedElements<JSClass>(
IndexedElements{ [this, jsClassFromUnit](quint32 i) -> const JSClass & {
return jsClassFromUnit(oldUnit, i);
},
oldUnit->jsClassTableSize },
IndexedElements{ [this, jsClassFromUnit](quint32 i) -> const JSClass & {
return jsClassFromUnit(newUnit, i);
},
newUnit->jsClassTableSize },
[this](const JSClass &a, const JSClass &b) {
return jsClassContentEqual(a, b);
});
}
compareIndexedElements<Block>(
IndexedElements{
[this](quint32 i) -> const Block & { return *oldUnit->blockAt(i); },
oldUnit->blockTableSize },
IndexedElements{
[this](quint32 i) -> const Block & { return *newUnit->blockAt(i); },
newUnit->blockTableSize },
[this](const Block &a, const Block &b) { return blockContentEqual(a, b); });
return CompilationUnitDiff{ std::move(changes), true };
}
};
// NB: AddObject and RemoveObject are mutually exclusive since all objects are
// in a global order per compilation unit. The number of objects can either
// grow or shrink, but not both. Likewise AddBinding and RemoveBinding, but
// per object. Before adding bindings, all the objects need to be present.
// It's a good idea to remove stale bindings before they can cause any
// trouble. Therefore, we do that first, before changing or adding bindings.
enum Severity : quint8 {
Replace,
Rebuild,
AddObject,
RemoveBinding,
ChangeBinding,
AddBinding,
RemoveObject,
Ignore,
};
static Severity classifyChange(const Change &change)
{
switch (change.type) {
case QV4::CompiledData::ChangeType::UnitMetadataChanged:
case QV4::CompiledData::ChangeType::ObjectChanged:
case QV4::CompiledData::ChangeType::ImportAdded:
case QV4::CompiledData::ChangeType::ImportRemoved:
case QV4::CompiledData::ChangeType::ImportChanged:
case QV4::CompiledData::ChangeType::InlineComponentAdded:
case QV4::CompiledData::ChangeType::InlineComponentRemoved:
case QV4::CompiledData::ChangeType::InlineComponentChanged:
case QV4::CompiledData::ChangeType::Unknown:
return Replace;
case QV4::CompiledData::ChangeType::PropertyAdded:
case QV4::CompiledData::ChangeType::PropertyRemoved:
case QV4::CompiledData::ChangeType::PropertyChanged:
case QV4::CompiledData::ChangeType::AliasAdded:
case QV4::CompiledData::ChangeType::AliasRemoved:
case QV4::CompiledData::ChangeType::AliasChanged:
case QV4::CompiledData::ChangeType::FunctionAdded:
case QV4::CompiledData::ChangeType::FunctionRemoved:
case QV4::CompiledData::ChangeType::FunctionChanged:
case QV4::CompiledData::ChangeType::EnumAdded:
case QV4::CompiledData::ChangeType::EnumRemoved:
case QV4::CompiledData::ChangeType::EnumChanged:
case QV4::CompiledData::ChangeType::SignalAdded:
case QV4::CompiledData::ChangeType::SignalRemoved:
case QV4::CompiledData::ChangeType::SignalChanged:
case QV4::CompiledData::ChangeType::RequiredPropertyExtraDataAdded:
case QV4::CompiledData::ChangeType::RequiredPropertyExtraDataRemoved:
case QV4::CompiledData::ChangeType::RequiredPropertyExtraDataChanged:
return Rebuild;
case QV4::CompiledData::ChangeType::ObjectAdded:
return AddObject;
case QV4::CompiledData::ChangeType::BindingRemoved:
return RemoveBinding;
case QV4::CompiledData::ChangeType::BindingChanged:
return ChangeBinding;
case QV4::CompiledData::ChangeType::BindingAdded:
return AddBinding;
case QV4::CompiledData::ChangeType::ObjectRemoved:
return RemoveObject;
case QV4::CompiledData::ChangeType::AliasLocationChanged:
case QV4::CompiledData::ChangeType::BindingLocationChanged:
case QV4::CompiledData::ChangeType::EnumLocationChanged:
case QV4::CompiledData::ChangeType::FunctionLocationChanged:
case QV4::CompiledData::ChangeType::ImportLocationChanged:
case QV4::CompiledData::ChangeType::InlineComponentLocationChanged:
case QV4::CompiledData::ChangeType::ObjectLocationChanged:
case QV4::CompiledData::ChangeType::PropertyLocationChanged:
case QV4::CompiledData::ChangeType::SignalLocationChanged:
return Ignore;
case QV4::CompiledData::ChangeType::ConstantAdded:
case QV4::CompiledData::ChangeType::ConstantRemoved:
case QV4::CompiledData::ChangeType::ConstantChanged:
case QV4::CompiledData::ChangeType::StringDataAdded:
case QV4::CompiledData::ChangeType::StringDataRemoved:
case QV4::CompiledData::ChangeType::StringDataChanged:
case QV4::CompiledData::ChangeType::LookupAdded:
case QV4::CompiledData::ChangeType::LookupRemoved:
case QV4::CompiledData::ChangeType::LookupChanged:
case QV4::CompiledData::ChangeType::RegExpAdded:
case QV4::CompiledData::ChangeType::RegExpRemoved:
case QV4::CompiledData::ChangeType::RegExpChanged:
case QV4::CompiledData::ChangeType::ClassAdded:
case QV4::CompiledData::ChangeType::ClassRemoved:
case QV4::CompiledData::ChangeType::ClassChanged:
case QV4::CompiledData::ChangeType::TemplateObjectAdded:
case QV4::CompiledData::ChangeType::TemplateObjectRemoved:
case QV4::CompiledData::ChangeType::TemplateObjectChanged:
case QV4::CompiledData::ChangeType::JSClassAdded:
case QV4::CompiledData::ChangeType::JSClassRemoved:
case QV4::CompiledData::ChangeType::JSClassChanged:
case QV4::CompiledData::ChangeType::BlockAdded:
case QV4::CompiledData::ChangeType::BlockRemoved:
case QV4::CompiledData::ChangeType::BlockChanged:
case QV4::CompiledData::ChangeType::TranslationDataAdded:
case QV4::CompiledData::ChangeType::TranslationDataRemoved:
case QV4::CompiledData::ChangeType::TranslationDataChanged:
case QV4::CompiledData::ChangeType::None:
break;
}
return Ignore;
}
static void sortChanges(QSpan<Change> changes)
{
std::stable_sort(changes.begin(), changes.end(), [](const Change &a, const Change &b) {
return quint8(classifyChange(a)) < quint8(classifyChange(b));
});
}
CompilationUnitDiff diffCompilationUnits(const Unit *oldUnit, const Unit *newUnit)
{
auto diff = UnitDiffer{ oldUnit, newUnit, {} }.diff();
// Sort the changes so that they are easy to apply.
// We want the most severe changes first.
sortChanges(diff.changes);
return diff;
}
} // namespace QV4::CompiledData
QT_END_NAMESPACE