Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlpreviewdiff.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6
7#include <private/qv4staticvalue_p.h>
8
9#include <QtCore/qmap.h>
10#include <QtCore/qscopedvaluerollback.h>
11#include <QtCore/qset.h>
12
13#include <cstring>
14
15QT_BEGIN_NAMESPACE
16
17// IMPORTANT:
18//
19// If you update the data structures in qv4compileddata_p.h, then you also need to
20// update the comparison functions below. Only after making sure the comparisons
21// are fine, bump the version in the static_assert.
22static_assert(QV4_DATA_STRUCTURE_VERSION == 0x4f);
23
24namespace QV4::CompiledData {
25
28
29template <typename Data>
30Data extractData(const Data &data)
31{
32 return data;
33}
34
35static EnumHunk extractData(const Enum &e)
36{
37 EnumHunk data;
38 data.data = e;
39 for (const EnumValue *v = e.enumValuesBegin(); v != e.enumValuesEnd(); ++v)
40 data.values.append(*v);
41 return data;
42}
43
44static SignalHunk extractData(const Signal &s)
45{
46 SignalHunk data;
47 data.data = s;
48 for (quint32 i = 0; i < s.nParameters; ++i)
49 data.parameters.append(*s.parameterAt(i));
50 return data;
51}
52
53static FunctionHunk extractData(const Function &func)
54{
55 FunctionHunk data;
56 data.data = func;
57
58 for (quint16 i = 0, end = func.nFormals; i != end; ++i)
59 data.formals.append(func.formalsTable()[i]);
60
61 for (quint16 i = 0, end = func.nLocals; i != end; ++i)
62 data.locals.append(func.localsTable()[i]);
63
64 for (quint16 i = 0, end = func.nLineAndStatementNumbers; i != end; ++i)
65 data.lineAndStatementNumbers.append(func.lineAndStatementNumberTable()[i]);
66
67 for (quint32 i = 0, end = func.nLabelInfos; i != end; ++i)
68 data.labelInfos.append(func.labelInfoTable()[i]);
69
70 data.code = QByteArray(func.code(), func.codeSize);
71 return data;
72}
73
74static ObjectHunk extractData(const Object &obj)
75{
76 ObjectHunk data;
77 data.data = obj;
78
79 for (quint32 i = 0; i < obj.nFunctions; ++i)
80 data.functionIndices.append(extractData(obj.functionOffsetTable()[i]));
81
82 for (quint32 i = 0; i < obj.nBindings; ++i)
83 data.bindings.append(extractData(obj.bindingTable()[i]));
84
85 for (quint32 i = 0; i < obj.nProperties; ++i)
86 data.properties.append(extractData(obj.propertyTable()[i]));
87
88 for (quint32 i = 0; i < obj.nAliases; ++i)
89 data.aliases.append(extractData(obj.aliasTable()[i]));
90
91 for (quint32 i = 0; i < obj.nSignals; ++i)
92 data.sigs.append(extractData(*obj.signalAt(i)));
93
94 for (quint32 i = 0; i < obj.nEnums; ++i)
95 data.enums.append(extractData(*obj.enumAt(i)));
96
97 for (quint32 i = 0; i < obj.nNamedObjectsInComponent; ++i) {
98 data.namedObjectsInComponentIndices.append(
99 extractData(obj.namedObjectsInComponentTable()[i]));
100 }
101
102 for (quint32 i = 0; i < obj.nInlineComponents; ++i)
103 data.inlineComponents.append(extractData(obj.inlineComponentTable()[i]));
104
105 for (quint32 i = 0; i < obj.nRequiredPropertyExtraData; ++i)
106 data.requiredPropertyExtraData.append(extractData(obj.requiredPropertyExtraDataTable()[i]));
107
108 return data;
109}
110
111static UnitHunk extractData(const Unit &unit)
112{
113 return unit;
114}
115
116static ClassHunk extractData(const Class &cls)
117{
118 ClassHunk data;
119 data.data = cls;
120 const Method *methods = cls.methodTable();
121 for (quint32 i = 0, end = cls.nStaticMethods + cls.nMethods; i < end; ++i)
122 data.methods.append(methods[i]);
123 return data;
124}
125
126static TemplateObjectHunk extractData(const TemplateObject &to)
127{
129 data.size = to.size;
130 const quint32_le *strings = reinterpret_cast<const quint32_le *>(&to + 1);
131 for (quint32 i = 0; i < 2u * to.size; ++i)
132 data.strings.append(strings[i]);
133 return data;
134}
135
136static JsClassHunk extractData(const JSClass &jsClass)
137{
138 JsClassHunk data;
139 data.nMembers = jsClass.nMembers;
140 const JSClassMember *members = reinterpret_cast<const JSClassMember *>(&jsClass + 1);
141 for (quint32 i = 0; i < jsClass.nMembers; ++i)
142 data.members.append(members[i]);
143 return data;
144}
145
146static BlockHunk extractData(const Block &block)
147{
148 BlockHunk data;
149 data.data = block;
150 for (quint32 i = 0; i < block.nLocals; ++i)
151 data.locals.append(block.localsTable()[i]);
152 return data;
153}
154
155// location extraction
156
157template <typename T, typename L = void>
159{
160 static bool extract(const T &) { return false; }
161};
162
163template <typename T>
165{
166 static Location extract(const T &t) { return t.location; }
167};
168
169template <>
171{
173 {
175 }
176};
177
178template <>
180{
182 {
183 return { alias.location(), alias.referenceLocation() };
184 }
185};
186
187template <>
189{
191 {
193 }
194};
195
196template <typename ElementT>
198
199#define COMPARISON_TRAITS(Element) template
200 <> struct
201 ComparisonTraits<Element> \
202{
203 static constexpr ChangeType Changed = ChangeType::Element ## Changed;
204 static constexpr ChangeType LocationChanged = ChangeType::None;
205 static constexpr ChangeType Removed = ChangeType::Element ## Removed;
206 static constexpr ChangeType Added = ChangeType::Element ## Added; \
207};
208
209
210#define COMPARISON_TRAITS_WITH_LOCATION(Element) template
211 <> struct
212 ComparisonTraits<Element> \
213{
214 static constexpr ChangeType Changed = ChangeType::Element ## Changed;
215 static constexpr ChangeType LocationChanged = ChangeType::Element ## LocationChanged;
216 static constexpr ChangeType Removed = ChangeType::Element ## Removed;
217 static constexpr ChangeType Added = ChangeType::Element ## Added; \
218};
219
229
240
241template <typename AccessorFn>
243{
244 AccessorFn accessor;
246};
247
248template <typename AccessorFn>
249IndexedElements(AccessorFn, quint32) -> IndexedElements<AccessorFn>;
250
252{
253 const Unit *const oldUnit;
254 const Unit *const newUnit;
257
258 template <typename ElementT, typename OldSource, typename NewSource, typename ContentEqFn>
259 void compareIndexedElements(OldSource oldSource, NewSource newSource, ContentEqFn contentEq)
260 {
261 using Traits = ComparisonTraits<ElementT>;
262 const auto &oldAccessor = oldSource.accessor;
263 const auto &newAccessor = newSource.accessor;
264 const quint32 oldCount = oldSource.count;
265 const quint32 newCount = newSource.count;
266 quint32 commonCount = qMin(oldCount, newCount);
267
268 // Compare elements at common positions
269 for (quint32 i = 0; i < commonCount; ++i) {
270 const ElementT &oldElem = oldAccessor(i);
271 const ElementT &newElem = newAccessor(i);
272
273 if (!contentEq(oldElem, newElem)) {
274 Change change;
276 change.index = i;
277 change.type = Traits::Changed;
278 change.data = extractData(newElem);
279 changes.append(change);
280 } else if (LocationExtractor<ElementT>::extract(oldElem)
281 != LocationExtractor<ElementT>::extract(newElem)) {
282 Change change;
284 change.index = i;
285 change.type = Traits::LocationChanged;
286 change.data = extractData(newElem);
287 changes.append(change);
288 }
289 }
290
291 // Emit Removed for extra elements in old
292 for (quint32 i = commonCount; i < oldCount; ++i) {
293 Change change;
294 change.type = Traits::Removed;
296 change.index = i;
297 changes.append(change);
298 }
299
300 // Emit Added for extra elements in new
301 for (quint32 i = commonCount; i < newCount; ++i) {
302 Change change;
303 change.type = Traits::Added;
305 change.index = i;
306 change.data = extractData(newAccessor(i));
307 changes.append(change);
308 }
309 }
310
311 bool stringsEqual(uint oldName, uint newName) const
312 {
313 Q_ASSERT(oldName < oldUnit->stringTableSize);
314 Q_ASSERT(newName < newUnit->stringTableSize);
315 return oldName == newName
316 && oldUnit->stringAtInternal(oldName) == newUnit->stringAtInternal(newName);
317 }
318
320 {
321 if (std::memcmp(oldUnit->magic, newUnit->magic, sizeof(oldUnit->magic)) != 0)
322 return false;
323
324 if (oldUnit->version != newUnit->version || oldUnit->reserved != newUnit->reserved
325 || oldUnit->sourceTimeStamp != newUnit->sourceTimeStamp
326 || oldUnit->unitSize != newUnit->unitSize) {
327 return false;
328 }
329
330 // sourceChecksum lives before md5Checksum and is therefore not covered by it.
331 if (std::memcmp(oldUnit->sourceChecksum, newUnit->sourceChecksum,
332 sizeof(oldUnit->sourceChecksum))
333 != 0) {
334 return false;
335 }
336
337 // If anything below the checksum has changed, the checksum itself has changed, too
338 return std::memcmp(oldUnit->md5Checksum, newUnit->md5Checksum, sizeof(oldUnit->md5Checksum))
339 == 0;
340 }
341
342 bool objectContentEqual(const Object &oldObj, const Object &newObj) const
343 {
344 // Keep in sync with the Object struct in qv4compileddata_p.h
345 static_assert(sizeof(Object) == 84, "Update objectContentEqual when Object layout changes");
346 if (oldObj.nNamedObjectsInComponent != newObj.nNamedObjectsInComponent)
347 return false;
348
349 const quint32_le *oldNamed = oldObj.namedObjectsInComponentTable();
350 const quint32_le *newNamed = newObj.namedObjectsInComponentTable();
351 for (quint32 i = 0; i < oldObj.nNamedObjectsInComponent; ++i) {
352 if (oldNamed[i] != newNamed[i])
353 return false;
354 }
355
356 if (oldObj.nFunctions != newObj.nFunctions)
357 return false;
358
359 const quint32_le *oldFuncs = oldObj.functionOffsetTable();
360 const quint32_le *newFuncs = newObj.functionOffsetTable();
361 for (quint32 i = 0; i < oldObj.nFunctions; ++i) {
362 if (oldFuncs[i] != newFuncs[i])
363 return false;
364 }
365
366 return stringsEqual(oldObj.inheritedTypeNameIndex, newObj.inheritedTypeNameIndex)
367 && stringsEqual(oldObj.idNameIndex, newObj.idNameIndex)
368 && oldObj.flags() == newObj.flags()
369 && oldObj.hasAliasAsDefaultProperty() == newObj.hasAliasAsDefaultProperty()
370 && oldObj.objectId() == newObj.objectId()
371 && oldObj.indexOfDefaultPropertyOrAlias == newObj.indexOfDefaultPropertyOrAlias;
372 }
373
374 bool propertyContentEqual(const Property &oldProp, const Property &newProp) const
375 {
376 // Keep in sync with the Property struct in qv4compileddata_p.h
377 static_assert(sizeof(Property) == 12, "Update propertyContentEqual when Property layout changes");
378 return stringsEqual(oldProp.nameIndex(), newProp.nameIndex())
379 && (oldProp.isCommonType() == newProp.isCommonType()
380 && (oldProp.isCommonType()
381 ? oldProp.commonTypeOrTypeNameIndex()
382 == newProp.commonTypeOrTypeNameIndex()
383 : stringsEqual(oldProp.typeNameIndex(), newProp.typeNameIndex())))
384 && oldProp.isReadOnly() == newProp.isReadOnly()
385 && oldProp.isRequired() == newProp.isRequired()
386 && oldProp.isList() == newProp.isList()
387 && oldProp.isVirtual() == newProp.isVirtual()
388 && oldProp.isOverride() == newProp.isOverride()
389 && oldProp.isFinal() == newProp.isFinal();
390 }
391
392 // In addition to structural equality, also compare the actual name strings: the integer
393 // nameIndex may be the same while the string at that index changed (property rename).
394 bool aliasContentEqual(const Alias &oldAlias, const Alias &newAlias) const
395 {
396 // Keep in sync with the Alias struct in qv4compileddata_p.h
397 static_assert(sizeof(Alias) == 20, "Update aliasContentEqual when Alias layout changes");
398 return stringsEqual(oldAlias.idIndex(), newAlias.idIndex())
399 && stringsEqual(oldAlias.nameIndex(), newAlias.nameIndex())
400 && stringsEqual(oldAlias.propertyNameIndex(), newAlias.propertyNameIndex())
401 && oldAlias.isReadOnly() == newAlias.isReadOnly();
402 }
403
404 bool enumContentEqual(const Enum &oldEnum, const Enum &newEnum) const
405 {
406 // Keep in sync with the Enum and EnumValue structs in qv4compileddata_p.h
407 static_assert(sizeof(Enum) == 12, "Update enumContentEqual when Enum layout changes");
408 static_assert(sizeof(EnumValue) == 8, "Update enumContentEqual when EnumValue layout changes");
409 if (!stringsEqual(oldEnum.nameIndex, newEnum.nameIndex)
410 || oldEnum.nEnumValues != newEnum.nEnumValues) {
411 return false;
412 }
413
414 for (quint32 i = 0; i < oldEnum.nEnumValues; ++i) {
415 const EnumValue *oldValue = oldEnum.enumValueAt(i);
416 const EnumValue *newValue = newEnum.enumValueAt(i);
417 if (oldValue->value != newValue->value
418 || !stringsEqual(oldValue->nameIndex, newValue->nameIndex)) {
419 return false;
420 }
421 }
422
423 return true;
424 }
425
426 bool signalContentEqual(const Signal &oldSig, const Signal &newSig) const
427 {
428 // Keep in sync with the Signal and Parameter structs in qv4compileddata_p.h
429 static_assert(sizeof(Signal) == 12, "Update signalContentEqual when Signal layout changes");
430 static_assert(sizeof(Parameter) == 8, "Update signalContentEqual when Parameter layout changes");
431 if (!stringsEqual(oldSig.nameIndex, newSig.nameIndex))
432 return false;
433 if (oldSig.nParameters != newSig.nParameters)
434 return false;
435 for (quint32 i = 0; i < oldSig.nParameters; ++i) {
436 const Parameter *oldP = oldSig.parameterAt(i);
437 const Parameter *newP = newSig.parameterAt(i);
438 if (!stringsEqual(oldP->nameIndex, newP->nameIndex)
439 || !parameterTypeContentEqual(oldP->type, newP->type)) {
440 return false;
441 }
442 }
443 return true;
444 }
445
446 bool bindingContentEqual(const Binding &oldBinding, const Binding &newBinding) const
447 {
448 // Keep in sync with the Binding and TranslationData structs in qv4compileddata_p.h
449 static_assert(sizeof(Binding) == 24, "Update bindingContentEqual when Binding layout changes");
450 static_assert(sizeof(TranslationData) == 16, "Update bindingContentEqual when TranslationData layout changes");
451 if (oldBinding.type() != newBinding.type() || oldBinding.flags() != newBinding.flags())
452 return false;
453
454 if (!stringsEqual(oldBinding.propertyNameIndex, newBinding.propertyNameIndex))
455 return false;
456
457 switch (oldBinding.type()) {
458 case Binding::Type_Invalid:
459 return true;
460 case Binding::Type_Boolean:
461 return oldBinding.valueAsBoolean() == newBinding.valueAsBoolean();
462 case Binding::Type_Number: {
463 if (oldBinding.hasFlag(CompiledData::Binding::IsResolvedEnum))
464 return oldBinding.value.resolvedEnumValue == newBinding.value.resolvedEnumValue;
465 const uint oldIdx = oldBinding.value.constantValueIndex;
466 const uint newIdx = newBinding.value.constantValueIndex;
467 Q_ASSERT(oldIdx < oldUnit->constantTableSize);
468 Q_ASSERT(newIdx < newUnit->constantTableSize);
469 return oldIdx == newIdx && oldUnit->constants()[oldIdx] == newUnit->constants()[newIdx];
470 }
471 case Binding::Type_String:
472 return stringsEqual(oldBinding.stringIndex, newBinding.stringIndex);
473 case Binding::Type_Null:
474 return true;
475 case Binding::Type_Translation:
476 case Binding::Type_TranslationById: {
477 const auto &oldTrans = oldUnit->translations()[oldBinding.value.translationDataIndex];
478 const auto &newTrans = newUnit->translations()[newBinding.value.translationDataIndex];
479
480 // contextIndex uses NoContextIndex as sentinel; compare by
481 // resolved string only when both are valid indices.
482 const bool oldHasCtx = oldTrans.contextIndex != TranslationData::NoContextIndex;
483 const bool newHasCtx = newTrans.contextIndex != TranslationData::NoContextIndex;
484
485 if (oldHasCtx != newHasCtx)
486 return false;
487
488 if (oldHasCtx && !stringsEqual(oldTrans.contextIndex, newTrans.contextIndex))
489 return false;
490
491 return stringsEqual(oldBinding.stringIndex, newBinding.stringIndex)
492 && stringsEqual(oldTrans.stringIndex, newTrans.stringIndex)
493 && stringsEqual(oldTrans.commentIndex, newTrans.commentIndex)
494 && oldTrans.number == newTrans.number;
495 }
496 case Binding::Type_Script:
497 return oldBinding.value.compiledScriptIndex == newBinding.value.compiledScriptIndex;
498 case Binding::Type_Object:
499 case Binding::Type_AttachedProperty:
500 case Binding::Type_GroupProperty:
501 return oldBinding.value.objectIndex == newBinding.value.objectIndex
502 && objectContentEqual(
503 *oldUnit->qmlUnit()->objectAt(oldBinding.value.objectIndex),
504 *newUnit->qmlUnit()->objectAt(newBinding.value.objectIndex));
505 }
506
507 return false;
508 }
509
510 bool parameterTypeContentEqual(const ParameterType &oldType, const ParameterType &newType) const
511 {
512 // Keep in sync with the ParameterType struct in qv4compileddata_p.h
513 static_assert(sizeof(ParameterType) == 4, "Update parameterTypeContentEqual when ParameterType layout changes");
514 const bool indexIsCommonType = oldType.indexIsCommonType();
515 if (newType.indexIsCommonType() != indexIsCommonType
516 || oldType.isList() != newType.isList()) {
517 return false;
518 }
519
520 return indexIsCommonType
521 ? oldType.typeNameIndexOrCommonType() == newType.typeNameIndexOrCommonType()
522 : stringsEqual(oldType.typeNameIndexOrCommonType(),
523 newType.typeNameIndexOrCommonType());
524 }
525
526 bool functionContentEqual(const Function &oldFunc, const Function &newFunc) const
527 {
528 // Keep in sync with the Function struct in qv4compileddata_p.h
529 static_assert(sizeof(Function) == 56, "Update functionContentEqual when Function layout changes");
530 if (oldFunc.nestedFunctionIndex != newFunc.nestedFunctionIndex
531 || !stringsEqual(oldFunc.nameIndex, newFunc.nameIndex) || oldFunc.flags != newFunc.flags
532 || oldFunc.nFormals != newFunc.nFormals || oldFunc.codeSize != newFunc.codeSize
533 || !parameterTypeContentEqual(oldFunc.returnType, newFunc.returnType)) {
534 return false;
535 }
536
537 for (int i = 0, end = oldFunc.nFormals; i != end; ++i) {
538 const auto &oldFormal = oldFunc.formalsTable()[i];
539 const auto &newFormal = newFunc.formalsTable()[i];
540 if (!parameterTypeContentEqual(oldFormal.type, newFormal.type))
541 return false;
542 if (!stringsEqual(oldFormal.nameIndex, newFormal.nameIndex))
543 return false;
544 }
545
546 return std::memcmp(oldFunc.code(), newFunc.code(), oldFunc.codeSize) == 0;
547 }
548
549 bool translationContentEqual(const TranslationData &oldTrans,
550 const TranslationData &newTrans) const
551 {
552 // Keep in sync with the TranslationData struct in qv4compileddata_p.h
553 static_assert(sizeof(TranslationData) == 16, "Update translationContentEqual when TranslationData layout changes");
554 if (oldTrans.contextIndex != newTrans.contextIndex)
555 return false;
556 if (oldTrans.contextIndex != TranslationData::NoContextIndex
557 && !stringsEqual(oldTrans.contextIndex, newTrans.contextIndex)) {
558 return false;
559 }
560 return stringsEqual(oldTrans.stringIndex, newTrans.stringIndex)
561 && stringsEqual(oldTrans.commentIndex, newTrans.commentIndex)
562 && oldTrans.number == newTrans.number;
563 }
564
565 bool classContentEqual(const Class &oldCls, const Class &newCls) const
566 {
567 // Keep in sync with the Class and Method structs in qv4compileddata_p.h
568 static_assert(sizeof(Class) == 24, "Update classContentEqual when Class layout changes");
569 static_assert(sizeof(Method) == 12, "Update classContentEqual when Method layout changes");
570 if (!stringsEqual(oldCls.nameIndex, newCls.nameIndex)
571 || oldCls.scopeIndex != newCls.scopeIndex
572 || oldCls.constructorFunction != newCls.constructorFunction
573 || oldCls.nStaticMethods != newCls.nStaticMethods
574 || oldCls.nMethods != newCls.nMethods) {
575 return false;
576 }
577 const Method *oldMethods = oldCls.methodTable();
578 const Method *newMethods = newCls.methodTable();
579 for (quint32 i = 0, end = oldCls.nStaticMethods + oldCls.nMethods; i < end; ++i) {
580 if (!stringsEqual(oldMethods[i].name, newMethods[i].name)
581 || oldMethods[i].type != newMethods[i].type
582 || oldMethods[i].function != newMethods[i].function) {
583 return false;
584 }
585 }
586 return true;
587 }
588
589 bool templateObjectContentEqual(const TemplateObject &oldTO, const TemplateObject &newTO) const
590 {
591 // Keep in sync with the TemplateObject struct in qv4compileddata_p.h
592 static_assert(sizeof(TemplateObject) == 4, "Update templateObjectContentEqual when TemplateObject layout changes");
593 if (oldTO.size != newTO.size)
594 return false;
595 const quint32_le *oldStrings = reinterpret_cast<const quint32_le *>(&oldTO + 1);
596 const quint32_le *newStrings = reinterpret_cast<const quint32_le *>(&newTO + 1);
597 for (quint32 i = 0; i < 2u * oldTO.size; ++i) {
598 if (!stringsEqual(oldStrings[i], newStrings[i]))
599 return false;
600 }
601 return true;
602 }
603
604 bool jsClassContentEqual(const JSClass &oldJC, const JSClass &newJC) const
605 {
606 // Keep in sync with the JSClass and JSClassMember structs in qv4compileddata_p.h
607 static_assert(sizeof(JSClass) == 4, "Update jsClassContentEqual when JSClass layout changes");
608 static_assert(sizeof(JSClassMember) == 4, "Update jsClassContentEqual when JSClassMember layout changes");
609 if (oldJC.nMembers != newJC.nMembers)
610 return false;
611 const JSClassMember *oldMembers = reinterpret_cast<const JSClassMember *>(&oldJC + 1);
612 const JSClassMember *newMembers = reinterpret_cast<const JSClassMember *>(&newJC + 1);
613 for (quint32 i = 0; i < oldJC.nMembers; ++i) {
614 if (!stringsEqual(oldMembers[i].nameOffset(), newMembers[i].nameOffset())
615 || oldMembers[i].isAccessor() != newMembers[i].isAccessor()) {
616 return false;
617 }
618 }
619 return true;
620 }
621
622 bool blockContentEqual(const Block &oldBlock, const Block &newBlock) const
623 {
624 // Keep in sync with the Block struct in qv4compileddata_p.h
625 static_assert(sizeof(Block) == 12, "Update blockContentEqual when Block layout changes");
626 if (oldBlock.nLocals != newBlock.nLocals
627 || oldBlock.sizeOfLocalTemporalDeadZone != newBlock.sizeOfLocalTemporalDeadZone) {
628 return false;
629 }
630 for (quint32 i = 0; i < oldBlock.nLocals; ++i) {
631 if (!stringsEqual(oldBlock.localsTable()[i], newBlock.localsTable()[i]))
632 return false;
633 }
634 return true;
635 }
636
637 // Per-object comparison methods
638
639 void compareObjectProperties(const Object *oldObj, const Object *newObj)
640 {
641 const Property *oldProps = oldObj->propertyTable();
642 const Property *newProps = newObj->propertyTable();
643 compareIndexedElements<Property>(
644 IndexedElements{ [oldProps](quint32 i) { return oldProps[i]; },
645 oldObj->nProperties },
646 IndexedElements{ [newProps](quint32 i) { return newProps[i]; },
647 newObj->nProperties },
648 [this](const Property &a, const Property &b) {
649 return propertyContentEqual(a, b);
650 });
651 }
652
653 void compareObjectAliases(const Object *oldObj, const Object *newObj)
654 {
655 const Alias *oldAliases = oldObj->aliasesBegin();
656 const Alias *newAliases = newObj->aliasesBegin();
657 compareIndexedElements<Alias>(
658 IndexedElements{ [oldAliases](quint32 i) { return oldAliases[i]; },
659 oldObj->nAliases },
660 IndexedElements{ [newAliases](quint32 i) { return newAliases[i]; },
661 newObj->nAliases },
662 [this](const Alias &a, const Alias &b) { return aliasContentEqual(a, b); });
663 }
664
665 void compareObjectEnums(const Object *oldObj, const Object *newObj)
666 {
667 compareIndexedElements<Enum>(
668 IndexedElements{ [oldObj](quint32 i) -> const Enum & { return *oldObj->enumAt(i); },
669 oldObj->nEnums },
670 IndexedElements{ [newObj](quint32 i) -> const Enum & { return *newObj->enumAt(i); },
671 newObj->nEnums },
672 [this](const Enum &a, const Enum &b) { return enumContentEqual(a, b); });
673 }
674
675 void compareObjectSignals(const Object *oldObj, const Object *newObj)
676 {
677 compareIndexedElements<Signal>(
678 IndexedElements{
679 [oldObj](quint32 i) -> const Signal & { return *oldObj->signalAt(i); },
680 oldObj->nSignals },
681 IndexedElements{
682 [newObj](quint32 i) -> const Signal & { return *newObj->signalAt(i); },
683 newObj->nSignals },
684 [this](const Signal &a, const Signal &b) { return signalContentEqual(a, b); });
685 }
686
687 void compareObjectInlineComponents(const Object *oldObj, const Object *newObj)
688 {
689 const InlineComponent *oldICs = oldObj->inlineComponentTable();
690 const InlineComponent *newICs = newObj->inlineComponentTable();
691 compareIndexedElements<InlineComponent>(
692 IndexedElements{ [oldICs](quint32 i) { return oldICs[i]; },
693 oldObj->nInlineComponents },
694 IndexedElements{ [newICs](quint32 i) { return newICs[i]; },
695 newObj->nInlineComponents },
696 [this](const InlineComponent &a, const InlineComponent &b) {
697 return a.objectIndex == b.objectIndex && stringsEqual(a.nameIndex, b.nameIndex);
698 });
699 }
700
701 void compareObjectRequiredPropertyExtraData(const Object *oldObj, const Object *newObj)
702 {
703 const RequiredPropertyExtraData *oldRPED = oldObj->requiredPropertyExtraDataTable();
704 const RequiredPropertyExtraData *newRPED = newObj->requiredPropertyExtraDataTable();
705 compareIndexedElements<RequiredPropertyExtraData>(
706 IndexedElements{ [oldRPED](quint32 i) { return oldRPED[i]; },
707 oldObj->nRequiredPropertyExtraData },
708 IndexedElements{ [newRPED](quint32 i) { return newRPED[i]; },
709 newObj->nRequiredPropertyExtraData },
710 [this](const RequiredPropertyExtraData &a, const RequiredPropertyExtraData &b) {
711 return stringsEqual(a.nameIndex, b.nameIndex);
712 });
713 }
714
715 void compareObjectBindings(const Object *oldObj, const Object *newObj)
716 {
717 const Binding *oldBindings = oldObj->bindingTable();
718 const Binding *newBindings = newObj->bindingTable();
719 compareIndexedElements<Binding>(
720 IndexedElements{ [oldBindings](quint32 i) { return oldBindings[i]; },
721 oldObj->nBindings },
722 IndexedElements{ [newBindings](quint32 i) { return newBindings[i]; },
723 newObj->nBindings },
724 [this](const Binding &a, const Binding &b) { return bindingContentEqual(a, b); });
725 }
726
728 {
729 const QmlUnit *oldQml = oldUnit->qmlUnit();
730 const QmlUnit *newQml = newUnit->qmlUnit();
731
732 if (!oldQml || !newQml)
733 return CompilationUnitDiff();
734
735 // Check for unit metadata changes (source file, timestamp, checksum, etc.)
737 Change change;
739 change.data = extractData(*newUnit);
740 changes.append(change);
741 }
742
743 compareIndexedElements<Object>(
744 IndexedElements{
745 [oldQml](quint32 i) -> const Object & { return *oldQml->objectAt(i); },
746 oldQml->nObjects },
747 IndexedElements{
748 [newQml](quint32 i) -> const Object & { return *newQml->objectAt(i); },
749 newQml->nObjects },
750 [this](const Object &a, const Object &b) { return objectContentEqual(a, b); });
751
752 // Compare details of overlapping objects only
753 const quint32 commonCount = std::min(oldQml->nObjects, newQml->nObjects);
754 for (quint32 objectIndex = 0; objectIndex < commonCount; ++objectIndex) {
755 QScopedValueRollback guard(m_currentObjectIndex, static_cast<int>(objectIndex));
756 const Object *oldObj = oldQml->objectAt(objectIndex);
757 const Object *newObj = newQml->objectAt(objectIndex);
758
759 compareObjectProperties(oldObj, newObj);
760 compareObjectAliases(oldObj, newObj);
761 compareObjectEnums(oldObj, newObj);
762 compareObjectSignals(oldObj, newObj);
763 compareObjectInlineComponents(oldObj, newObj);
764 compareObjectRequiredPropertyExtraData(oldObj, newObj);
765 compareObjectBindings(oldObj, newObj);
766 }
767
768 compareIndexedElements<quint64_le>(
769 IndexedElements{ [this](quint32 i) { return oldUnit->constants()[i]; },
770 oldUnit->constantTableSize },
771 IndexedElements{ [this](quint32 i) { return newUnit->constants()[i]; },
772 newUnit->constantTableSize },
773 [](const quint64_le &a, const quint64_le &b) { return a == b; });
774
775 compareIndexedElements<QString>(
776 IndexedElements{ [this](quint32 i) { return oldUnit->stringAtInternal(i); },
777 oldUnit->stringTableSize },
778 IndexedElements{ [this](quint32 i) { return newUnit->stringAtInternal(i); },
779 newUnit->stringTableSize },
780 [](const QString &a, const QString &b) { return a == b; });
781
782 compareIndexedElements<Lookup>(
783 IndexedElements{ [this](quint32 i) { return oldUnit->lookupTable()[i]; },
784 oldUnit->lookupTableSize },
785 IndexedElements{ [this](quint32 i) { return newUnit->lookupTable()[i]; },
786 newUnit->lookupTableSize },
787 [this](const Lookup &a, const Lookup &b) {
788 return a.type() == b.type() && a.mode() == b.mode()
789 && stringsEqual(a.nameIndex(), b.nameIndex());
790 });
791
792 compareIndexedElements<Import>(
793 IndexedElements{ [oldQml](quint32 i) { return *oldQml->importAt(i); },
794 oldQml->nImports },
795 IndexedElements{ [newQml](quint32 i) { return *newQml->importAt(i); },
796 newQml->nImports },
797 [this](const Import &a, const Import &b) {
798 return a.type == b.type && stringsEqual(a.uriIndex, b.uriIndex)
799 && stringsEqual(a.qualifierIndex, b.qualifierIndex)
800 && a.version == b.version;
801 });
802
803 compareIndexedElements<Function>(IndexedElements{ [this](quint32 i) -> const Function & {
804 return *oldUnit->functionAt(i);
805 },
806 oldUnit->functionTableSize },
807 IndexedElements{ [this](quint32 i) -> const Function & {
808 return *newUnit->functionAt(i);
809 },
810 newUnit->functionTableSize },
811 [this](const Function &a, const Function &b) {
812 return functionContentEqual(a, b);
813 });
814
815 compareIndexedElements<TranslationData>(
816 IndexedElements{ [this](quint32 i) { return oldUnit->translations()[i]; },
817 oldUnit->translationTableSize },
818 IndexedElements{ [this](quint32 i) { return newUnit->translations()[i]; },
819 newUnit->translationTableSize },
820 [this](const TranslationData &a, const TranslationData &b) {
821 return translationContentEqual(a, b);
822 });
823
824 compareIndexedElements<RegExp>(
825 IndexedElements{
826 [this](quint32 i) -> const RegExp & { return *oldUnit->regexpAt(i); },
827 oldUnit->regexpTableSize },
828 IndexedElements{
829 [this](quint32 i) -> const RegExp & { return *newUnit->regexpAt(i); },
830 newUnit->regexpTableSize },
831 [this](const RegExp &a, const RegExp &b) {
832 return a.flags() == b.flags() && stringsEqual(a.stringIndex(), b.stringIndex());
833 });
834
835 compareIndexedElements<Class>(
836 IndexedElements{
837 [this](quint32 i) -> const Class & { return *oldUnit->classAt(i); },
838 oldUnit->classTableSize },
839 IndexedElements{
840 [this](quint32 i) -> const Class & { return *newUnit->classAt(i); },
841 newUnit->classTableSize },
842 [this](const Class &a, const Class &b) { return classContentEqual(a, b); });
843
844 compareIndexedElements<TemplateObject>(
845 IndexedElements{ [this](quint32 i) -> const TemplateObject & {
846 return *oldUnit->templateObjectAt(i);
847 },
848 oldUnit->templateObjectTableSize },
849 IndexedElements{ [this](quint32 i) -> const TemplateObject & {
850 return *newUnit->templateObjectAt(i);
851 },
852 newUnit->templateObjectTableSize },
853 [this](const TemplateObject &a, const TemplateObject &b) {
854 return templateObjectContentEqual(a, b);
855 });
856
857 {
858 auto jsClassFromUnit = [](const Unit *unit, quint32 idx) -> const JSClass & {
859 const quint32_le *offsetTable = reinterpret_cast<const quint32_le *>(
860 reinterpret_cast<const char *>(unit) + unit->offsetToJSClassTable);
861 return *reinterpret_cast<const JSClass *>(reinterpret_cast<const char *>(unit)
862 + offsetTable[idx]);
863 };
864 compareIndexedElements<JSClass>(
865 IndexedElements{ [this, jsClassFromUnit](quint32 i) -> const JSClass & {
866 return jsClassFromUnit(oldUnit, i);
867 },
868 oldUnit->jsClassTableSize },
869 IndexedElements{ [this, jsClassFromUnit](quint32 i) -> const JSClass & {
870 return jsClassFromUnit(newUnit, i);
871 },
872 newUnit->jsClassTableSize },
873 [this](const JSClass &a, const JSClass &b) {
874 return jsClassContentEqual(a, b);
875 });
876 }
877
878 compareIndexedElements<Block>(
879 IndexedElements{
880 [this](quint32 i) -> const Block & { return *oldUnit->blockAt(i); },
881 oldUnit->blockTableSize },
882 IndexedElements{
883 [this](quint32 i) -> const Block & { return *newUnit->blockAt(i); },
884 newUnit->blockTableSize },
885 [this](const Block &a, const Block &b) { return blockContentEqual(a, b); });
886
887 return CompilationUnitDiff{ std::move(changes), true };
888 }
889};
890
891// NB: AddObject and RemoveObject are mutually exclusive since all objects are
892// in a global order per compilation unit. The number of objects can either
893// grow or shrink, but not both. Likewise AddBinding and RemoveBinding, but
894// per object. Before adding bindings, all the objects need to be present.
895// It's a good idea to remove stale bindings before they can cause any
896// trouble. Therefore, we do that first, before changing or adding bindings.
907
908static Severity classifyChange(const Change &change)
909{
910 switch (change.type) {
920 return Replace;
939 return Rebuild;
941 return AddObject;
943 return RemoveBinding;
945 return ChangeBinding;
947 return AddBinding;
949 return RemoveObject;
959 return Ignore;
988 break;
989 }
990 return Ignore;
991}
992
993static void sortChanges(QSpan<Change> changes)
994{
995 std::stable_sort(changes.begin(), changes.end(), [](const Change &a, const Change &b) {
996 return quint8(classifyChange(a)) < quint8(classifyChange(b));
997 });
998}
999
1000CompilationUnitDiff diffCompilationUnits(const Unit *oldUnit, const Unit *newUnit)
1001{
1002 auto diff = UnitDiffer{ oldUnit, newUnit, {} }.diff();
1003
1004 // Sort the changes so that they are easy to apply.
1005 // We want the most severe changes first.
1006 sortChanges(diff.changes);
1007
1008 return diff;
1009}
1010
1011} // namespace QV4::CompiledData
1012
1013QT_END_NAMESPACE
static Severity classifyChange(const Change &change)
CompilationUnitDiff diffCompilationUnits(const Unit *oldUnit, const Unit *newUnit)
IndexedElements(AccessorFn, quint32) -> IndexedElements< AccessorFn >
static EnumHunk extractData(const Enum &e)
Data extractData(const Data &data)
static void sortChanges(QSpan< Change > changes)
Definition qjsvalue.h:24
#define COMPARISON_TRAITS(Element)
#define COMPARISON_TRAITS_WITH_LOCATION(Element)
bool objectContentEqual(const Object &oldObj, const Object &newObj) const
void compareObjectProperties(const Object *oldObj, const Object *newObj)
bool parameterTypeContentEqual(const ParameterType &oldType, const ParameterType &newType) const
bool aliasContentEqual(const Alias &oldAlias, const Alias &newAlias) const
void compareObjectInlineComponents(const Object *oldObj, const Object *newObj)
bool enumContentEqual(const Enum &oldEnum, const Enum &newEnum) const
bool stringsEqual(uint oldName, uint newName) const
void compareObjectBindings(const Object *oldObj, const Object *newObj)
bool blockContentEqual(const Block &oldBlock, const Block &newBlock) const
void compareObjectRequiredPropertyExtraData(const Object *oldObj, const Object *newObj)
bool jsClassContentEqual(const JSClass &oldJC, const JSClass &newJC) const
void compareObjectAliases(const Object *oldObj, const Object *newObj)
bool translationContentEqual(const TranslationData &oldTrans, const TranslationData &newTrans) const
bool signalContentEqual(const Signal &oldSig, const Signal &newSig) const
void compareObjectEnums(const Object *oldObj, const Object *newObj)
bool templateObjectContentEqual(const TemplateObject &oldTO, const TemplateObject &newTO) const
bool propertyContentEqual(const Property &oldProp, const Property &newProp) const
bool functionContentEqual(const Function &oldFunc, const Function &newFunc) const
bool classContentEqual(const Class &oldCls, const Class &newCls) const
void compareObjectSignals(const Object *oldObj, const Object *newObj)
void compareIndexedElements(OldSource oldSource, NewSource newSource, ContentEqFn contentEq)
bool bindingContentEqual(const Binding &oldBinding, const Binding &newBinding) const