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 == 0x54);
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.nInlineComponents; ++i)
98 data.inlineComponents.append(extractData(obj.inlineComponentTable()[i]));
99
100 for (quint32 i = 0; i < obj.nRequiredPropertyExtraData; ++i)
101 data.requiredPropertyExtraData.append(extractData(obj.requiredPropertyExtraDataTable()[i]));
102
103 return data;
104}
105
106static UnitHunk extractData(const Unit &unit)
107{
108 return unit;
109}
110
111static ClassHunk extractData(const Class &cls)
112{
113 ClassHunk data;
114 data.data = cls;
115 const Method *methods = cls.methodTable();
116 for (quint32 i = 0, end = cls.nStaticMethods + cls.nMethods; i < end; ++i)
117 data.methods.append(methods[i]);
118 return data;
119}
120
121static TemplateObjectHunk extractData(const TemplateObject &to)
122{
124 data.size = to.size;
125 const quint32_le *strings = reinterpret_cast<const quint32_le *>(&to + 1);
126 for (quint32 i = 0; i < 2u * to.size; ++i)
127 data.strings.append(strings[i]);
128 return data;
129}
130
131static JsClassHunk extractData(const JSClass &jsClass)
132{
133 JsClassHunk data;
134 data.nMembers = jsClass.nMembers;
135 const JSClassMember *members = reinterpret_cast<const JSClassMember *>(&jsClass + 1);
136 for (quint32 i = 0; i < jsClass.nMembers; ++i)
137 data.members.append(members[i]);
138 return data;
139}
140
141static BlockHunk extractData(const Block &block)
142{
143 BlockHunk data;
144 data.data = block;
145 for (quint32 i = 0; i < block.nLocals; ++i)
146 data.locals.append(block.localsTable()[i]);
147 return data;
148}
149
150// location extraction
151
152template <typename T, typename L = void>
154{
155 static bool extract(const T &) { return false; }
156};
157
158template <typename T>
160{
161 static Location extract(const T &t) { return t.location; }
162};
163
164template <>
166{
168 {
170 }
171};
172
173template <>
175{
177 {
178 return { alias.location(), alias.referenceLocation() };
179 }
180};
181
182template <>
184{
186 {
188 }
189};
190
191template <typename ElementT>
193
194#define COMPARISON_TRAITS(Element) template
195 <> struct
196 ComparisonTraits<Element> \
197{
198 static constexpr ChangeType Changed = ChangeType::Element ## Changed;
199 static constexpr ChangeType LocationChanged = ChangeType::None;
200 static constexpr ChangeType Removed = ChangeType::Element ## Removed;
201 static constexpr ChangeType Added = ChangeType::Element ## Added; \
202};
203
204
205#define COMPARISON_TRAITS_WITH_LOCATION(Element) template
206 <> struct
207 ComparisonTraits<Element> \
208{
209 static constexpr ChangeType Changed = ChangeType::Element ## Changed;
210 static constexpr ChangeType LocationChanged = ChangeType::Element ## LocationChanged;
211 static constexpr ChangeType Removed = ChangeType::Element ## Removed;
212 static constexpr ChangeType Added = ChangeType::Element ## Added; \
213};
214
224
235
236template <typename AccessorFn>
238{
239 AccessorFn accessor;
241};
242
243template <typename AccessorFn>
244IndexedElements(AccessorFn, quint32) -> IndexedElements<AccessorFn>;
245
247{
248 const Unit *const oldUnit;
249 const Unit *const newUnit;
252
253 template <typename ElementT, typename OldSource, typename NewSource, typename ContentEqFn>
254 void compareIndexedElements(OldSource oldSource, NewSource newSource, ContentEqFn contentEq)
255 {
256 using Traits = ComparisonTraits<ElementT>;
257 const auto &oldAccessor = oldSource.accessor;
258 const auto &newAccessor = newSource.accessor;
259 const quint32 oldCount = oldSource.count;
260 const quint32 newCount = newSource.count;
261 quint32 commonCount = qMin(oldCount, newCount);
262
263 // Compare elements at common positions
264 for (quint32 i = 0; i < commonCount; ++i) {
265 const ElementT &oldElem = oldAccessor(i);
266 const ElementT &newElem = newAccessor(i);
267
268 if (!contentEq(oldElem, newElem)) {
269 Change change;
271 change.index = i;
272 change.type = Traits::Changed;
273 change.data = extractData(newElem);
274 changes.append(change);
275 } else if (LocationExtractor<ElementT>::extract(oldElem)
276 != LocationExtractor<ElementT>::extract(newElem)) {
277 Change change;
279 change.index = i;
280 change.type = Traits::LocationChanged;
281 change.data = extractData(newElem);
282 changes.append(change);
283 }
284 }
285
286 // Emit Removed for extra elements in old
287 for (quint32 i = commonCount; i < oldCount; ++i) {
288 Change change;
289 change.type = Traits::Removed;
291 change.index = i;
292 changes.append(change);
293 }
294
295 // Emit Added for extra elements in new
296 for (quint32 i = commonCount; i < newCount; ++i) {
297 Change change;
298 change.type = Traits::Added;
300 change.index = i;
301 change.data = extractData(newAccessor(i));
302 changes.append(change);
303 }
304 }
305
306 bool stringsEqual(uint oldName, uint newName) const
307 {
308 Q_ASSERT(oldName < oldUnit->stringTableSize);
309 Q_ASSERT(newName < newUnit->stringTableSize);
310 return oldName == newName
311 && oldUnit->stringAtInternal(oldName) == newUnit->stringAtInternal(newName);
312 }
313
315 {
316 if (std::memcmp(oldUnit->magic, newUnit->magic, sizeof(oldUnit->magic)) != 0)
317 return false;
318
319 if (oldUnit->version != newUnit->version || oldUnit->reserved != newUnit->reserved
320 || oldUnit->sourceTimeStamp != newUnit->sourceTimeStamp
321 || oldUnit->unitSize != newUnit->unitSize) {
322 return false;
323 }
324
325 // sourceChecksum lives before md5Checksum and is therefore not covered by it.
326 if (std::memcmp(oldUnit->sourceChecksum, newUnit->sourceChecksum,
327 sizeof(oldUnit->sourceChecksum))
328 != 0) {
329 return false;
330 }
331
332 // If anything below the checksum has changed, the checksum itself has changed, too
333 return std::memcmp(oldUnit->md5Checksum, newUnit->md5Checksum, sizeof(oldUnit->md5Checksum))
334 == 0;
335 }
336
337 bool objectContentEqual(const Object &oldObj, const Object &newObj) const
338 {
339 // Keep in sync with the Object struct in qv4compileddata_p.h
340 static_assert(sizeof(Object) == 76, "Update objectContentEqual when Object layout changes");
341 if (oldObj.nFunctions != newObj.nFunctions)
342 return false;
343
344 const quint32_le *oldFuncs = oldObj.functionOffsetTable();
345 const quint32_le *newFuncs = newObj.functionOffsetTable();
346 for (quint32 i = 0; i < oldObj.nFunctions; ++i) {
347 if (oldFuncs[i] != newFuncs[i])
348 return false;
349 }
350
351 return stringsEqual(oldObj.inheritedTypeNameIndex, newObj.inheritedTypeNameIndex)
352 && stringsEqual(oldObj.idNameIndex, newObj.idNameIndex)
353 && oldObj.flags() == newObj.flags()
354 && oldObj.hasAliasAsDefaultProperty() == newObj.hasAliasAsDefaultProperty()
355 && oldObj.indexOfDefaultPropertyOrAlias == newObj.indexOfDefaultPropertyOrAlias;
356 }
357
358 bool propertyContentEqual(const Property &oldProp, const Property &newProp) const
359 {
360 // Keep in sync with the Property struct in qv4compileddata_p.h
361 static_assert(sizeof(Property) == 12, "Update propertyContentEqual when Property layout changes");
362 return stringsEqual(oldProp.nameIndex(), newProp.nameIndex())
363 && (oldProp.isCommonType() == newProp.isCommonType()
364 && (oldProp.isCommonType()
365 ? oldProp.commonTypeOrTypeNameIndex()
366 == newProp.commonTypeOrTypeNameIndex()
367 : stringsEqual(oldProp.typeNameIndex(), newProp.typeNameIndex())))
368 && oldProp.isReadOnly() == newProp.isReadOnly()
369 && oldProp.isRequired() == newProp.isRequired()
370 && oldProp.isList() == newProp.isList()
371 && oldProp.isVirtual() == newProp.isVirtual()
372 && oldProp.isOverride() == newProp.isOverride()
373 && oldProp.isFinal() == newProp.isFinal();
374 }
375
376 // In addition to structural equality, also compare the actual name strings: the integer
377 // nameIndex may be the same while the string at that index changed (property rename).
378 bool aliasContentEqual(const Alias &oldAlias, const Alias &newAlias) const
379 {
380 // Keep in sync with the Alias struct in qv4compileddata_p.h
381 static_assert(sizeof(Alias) == 20, "Update aliasContentEqual when Alias layout changes");
382 return stringsEqual(oldAlias.idIndex(), newAlias.idIndex())
383 && stringsEqual(oldAlias.nameIndex(), newAlias.nameIndex())
384 && stringsEqual(oldAlias.propertyNameIndex(), newAlias.propertyNameIndex())
385 && oldAlias.isReadOnly() == newAlias.isReadOnly();
386 }
387
388 bool enumContentEqual(const Enum &oldEnum, const Enum &newEnum) const
389 {
390 // Keep in sync with the Enum and EnumValue structs in qv4compileddata_p.h
391 static_assert(sizeof(Enum) == 12, "Update enumContentEqual when Enum layout changes");
392 static_assert(sizeof(EnumValue) == 8, "Update enumContentEqual when EnumValue layout changes");
393 if (!stringsEqual(oldEnum.nameIndex, newEnum.nameIndex)
394 || oldEnum.nEnumValues != newEnum.nEnumValues) {
395 return false;
396 }
397
398 for (quint32 i = 0; i < oldEnum.nEnumValues; ++i) {
399 const EnumValue *oldValue = oldEnum.enumValueAt(i);
400 const EnumValue *newValue = newEnum.enumValueAt(i);
401 if (oldValue->value != newValue->value
402 || !stringsEqual(oldValue->nameIndex, newValue->nameIndex)) {
403 return false;
404 }
405 }
406
407 return true;
408 }
409
410 bool signalContentEqual(const Signal &oldSig, const Signal &newSig) const
411 {
412 // Keep in sync with the Signal and Parameter structs in qv4compileddata_p.h
413 static_assert(sizeof(Signal) == 12, "Update signalContentEqual when Signal layout changes");
414 static_assert(sizeof(Parameter) == 8, "Update signalContentEqual when Parameter layout changes");
415 if (!stringsEqual(oldSig.nameIndex, newSig.nameIndex))
416 return false;
417 if (oldSig.nParameters != newSig.nParameters)
418 return false;
419 for (quint32 i = 0; i < oldSig.nParameters; ++i) {
420 const Parameter *oldP = oldSig.parameterAt(i);
421 const Parameter *newP = newSig.parameterAt(i);
422 if (!stringsEqual(oldP->nameIndex, newP->nameIndex)
423 || !parameterTypeContentEqual(oldP->type, newP->type)) {
424 return false;
425 }
426 }
427 return true;
428 }
429
430 bool bindingContentEqual(const Binding &oldBinding, const Binding &newBinding) const
431 {
432 // Keep in sync with the Binding and TranslationData structs in qv4compileddata_p.h
433 static_assert(sizeof(Binding) == 24, "Update bindingContentEqual when Binding layout changes");
434 static_assert(sizeof(TranslationData) == 16, "Update bindingContentEqual when TranslationData layout changes");
435 if (oldBinding.type() != newBinding.type() || oldBinding.flags() != newBinding.flags())
436 return false;
437
438 if (!stringsEqual(oldBinding.propertyNameIndex, newBinding.propertyNameIndex))
439 return false;
440
441 switch (oldBinding.type()) {
442 case Binding::Type_Invalid:
443 return true;
444 case Binding::Type_Boolean:
445 return oldBinding.valueAsBoolean() == newBinding.valueAsBoolean();
446 case Binding::Type_Number: {
447 const uint oldIdx = oldBinding.value.constantValueIndex;
448 const uint newIdx = newBinding.value.constantValueIndex;
449 Q_ASSERT(oldIdx < oldUnit->constantTableSize);
450 Q_ASSERT(newIdx < newUnit->constantTableSize);
451 return oldIdx == newIdx && oldUnit->constants()[oldIdx] == newUnit->constants()[newIdx];
452 }
453 case Binding::Type_String:
454 return stringsEqual(oldBinding.stringIndex, newBinding.stringIndex);
455 case Binding::Type_Null:
456 return true;
457 case Binding::Type_Translation:
458 case Binding::Type_TranslationById: {
459 const auto &oldTrans = oldUnit->translations()[oldBinding.value.translationDataIndex];
460 const auto &newTrans = newUnit->translations()[newBinding.value.translationDataIndex];
461
462 // contextIndex uses NoContextIndex as sentinel; compare by
463 // resolved string only when both are valid indices.
464 const bool oldHasCtx = oldTrans.contextIndex != TranslationData::NoContextIndex;
465 const bool newHasCtx = newTrans.contextIndex != TranslationData::NoContextIndex;
466
467 if (oldHasCtx != newHasCtx)
468 return false;
469
470 if (oldHasCtx && !stringsEqual(oldTrans.contextIndex, newTrans.contextIndex))
471 return false;
472
473 return stringsEqual(oldBinding.stringIndex, newBinding.stringIndex)
474 && stringsEqual(oldTrans.stringIndex, newTrans.stringIndex)
475 && stringsEqual(oldTrans.commentIndex, newTrans.commentIndex)
476 && oldTrans.number == newTrans.number;
477 }
478 case Binding::Type_Script:
479 return oldBinding.value.compiledScriptIndex == newBinding.value.compiledScriptIndex;
480 case Binding::Type_Object:
481 case Binding::Type_AttachedProperty:
482 case Binding::Type_GroupedProperty:
483 return oldBinding.value.objectIndex == newBinding.value.objectIndex
484 && objectContentEqual(
485 *oldUnit->qmlUnit()->objectAt(oldBinding.value.objectIndex),
486 *newUnit->qmlUnit()->objectAt(newBinding.value.objectIndex));
487 }
488
489 return false;
490 }
491
492 bool parameterTypeContentEqual(const ParameterType &oldType, const ParameterType &newType) const
493 {
494 // Keep in sync with the ParameterType struct in qv4compileddata_p.h
495 static_assert(sizeof(ParameterType) == 4, "Update parameterTypeContentEqual when ParameterType layout changes");
496 const bool indexIsCommonType = oldType.indexIsCommonType();
497 if (newType.indexIsCommonType() != indexIsCommonType
498 || oldType.isList() != newType.isList()) {
499 return false;
500 }
501
502 return indexIsCommonType
503 ? oldType.typeNameIndexOrCommonType() == newType.typeNameIndexOrCommonType()
504 : stringsEqual(oldType.typeNameIndexOrCommonType(),
505 newType.typeNameIndexOrCommonType());
506 }
507
508 bool functionContentEqual(const Function &oldFunc, const Function &newFunc) const
509 {
510 // Keep in sync with the Function struct in qv4compileddata_p.h
511 static_assert(sizeof(Function) == 56, "Update functionContentEqual when Function layout changes");
512 if (oldFunc.nestedFunctionIndex != newFunc.nestedFunctionIndex
513 || !stringsEqual(oldFunc.nameIndex, newFunc.nameIndex) || oldFunc.flags != newFunc.flags
514 || oldFunc.nFormals != newFunc.nFormals || oldFunc.codeSize != newFunc.codeSize
515 || !parameterTypeContentEqual(oldFunc.returnType, newFunc.returnType)) {
516 return false;
517 }
518
519 for (int i = 0, end = oldFunc.nFormals; i != end; ++i) {
520 const auto &oldFormal = oldFunc.formalsTable()[i];
521 const auto &newFormal = newFunc.formalsTable()[i];
522 if (!parameterTypeContentEqual(oldFormal.type, newFormal.type))
523 return false;
524 if (!stringsEqual(oldFormal.nameIndex, newFormal.nameIndex))
525 return false;
526 }
527
528 return std::memcmp(oldFunc.code(), newFunc.code(), oldFunc.codeSize) == 0;
529 }
530
531 bool translationContentEqual(const TranslationData &oldTrans,
532 const TranslationData &newTrans) const
533 {
534 // Keep in sync with the TranslationData struct in qv4compileddata_p.h
535 static_assert(sizeof(TranslationData) == 16, "Update translationContentEqual when TranslationData layout changes");
536 if (oldTrans.contextIndex != newTrans.contextIndex)
537 return false;
538 if (oldTrans.contextIndex != TranslationData::NoContextIndex
539 && !stringsEqual(oldTrans.contextIndex, newTrans.contextIndex)) {
540 return false;
541 }
542 return stringsEqual(oldTrans.stringIndex, newTrans.stringIndex)
543 && stringsEqual(oldTrans.commentIndex, newTrans.commentIndex)
544 && oldTrans.number == newTrans.number;
545 }
546
547 bool classContentEqual(const Class &oldCls, const Class &newCls) const
548 {
549 // Keep in sync with the Class and Method structs in qv4compileddata_p.h
550 static_assert(sizeof(Class) == 24, "Update classContentEqual when Class layout changes");
551 static_assert(sizeof(Method) == 12, "Update classContentEqual when Method layout changes");
552 if (!stringsEqual(oldCls.nameIndex, newCls.nameIndex)
553 || oldCls.scopeIndex != newCls.scopeIndex
554 || oldCls.constructorFunction != newCls.constructorFunction
555 || oldCls.nStaticMethods != newCls.nStaticMethods
556 || oldCls.nMethods != newCls.nMethods) {
557 return false;
558 }
559 const Method *oldMethods = oldCls.methodTable();
560 const Method *newMethods = newCls.methodTable();
561 for (quint32 i = 0, end = oldCls.nStaticMethods + oldCls.nMethods; i < end; ++i) {
562 if (!stringsEqual(oldMethods[i].name, newMethods[i].name)
563 || oldMethods[i].type != newMethods[i].type
564 || oldMethods[i].function != newMethods[i].function) {
565 return false;
566 }
567 }
568 return true;
569 }
570
571 bool templateObjectContentEqual(const TemplateObject &oldTO, const TemplateObject &newTO) const
572 {
573 // Keep in sync with the TemplateObject struct in qv4compileddata_p.h
574 static_assert(sizeof(TemplateObject) == 4, "Update templateObjectContentEqual when TemplateObject layout changes");
575 if (oldTO.size != newTO.size)
576 return false;
577 const quint32_le *oldStrings = reinterpret_cast<const quint32_le *>(&oldTO + 1);
578 const quint32_le *newStrings = reinterpret_cast<const quint32_le *>(&newTO + 1);
579 for (quint32 i = 0; i < 2u * oldTO.size; ++i) {
580 if (!stringsEqual(oldStrings[i], newStrings[i]))
581 return false;
582 }
583 return true;
584 }
585
586 bool jsClassContentEqual(const JSClass &oldJC, const JSClass &newJC) const
587 {
588 // Keep in sync with the JSClass and JSClassMember structs in qv4compileddata_p.h
589 static_assert(sizeof(JSClass) == 4, "Update jsClassContentEqual when JSClass layout changes");
590 static_assert(sizeof(JSClassMember) == 4, "Update jsClassContentEqual when JSClassMember layout changes");
591 if (oldJC.nMembers != newJC.nMembers)
592 return false;
593 const JSClassMember *oldMembers = reinterpret_cast<const JSClassMember *>(&oldJC + 1);
594 const JSClassMember *newMembers = reinterpret_cast<const JSClassMember *>(&newJC + 1);
595 for (quint32 i = 0; i < oldJC.nMembers; ++i) {
596 if (!stringsEqual(oldMembers[i].nameOffset(), newMembers[i].nameOffset())
597 || oldMembers[i].isAccessor() != newMembers[i].isAccessor()) {
598 return false;
599 }
600 }
601 return true;
602 }
603
604 bool blockContentEqual(const Block &oldBlock, const Block &newBlock) const
605 {
606 // Keep in sync with the Block struct in qv4compileddata_p.h
607 static_assert(sizeof(Block) == 12, "Update blockContentEqual when Block layout changes");
608 if (oldBlock.nLocals != newBlock.nLocals
609 || oldBlock.sizeOfLocalTemporalDeadZone != newBlock.sizeOfLocalTemporalDeadZone) {
610 return false;
611 }
612 for (quint32 i = 0; i < oldBlock.nLocals; ++i) {
613 if (!stringsEqual(oldBlock.localsTable()[i], newBlock.localsTable()[i]))
614 return false;
615 }
616 return true;
617 }
618
619 // Per-object comparison methods
620
621 void compareObjectProperties(const Object *oldObj, const Object *newObj)
622 {
623 const Property *oldProps = oldObj->propertyTable();
624 const Property *newProps = newObj->propertyTable();
625 compareIndexedElements<Property>(
626 IndexedElements{ [oldProps](quint32 i) { return oldProps[i]; },
627 oldObj->nProperties },
628 IndexedElements{ [newProps](quint32 i) { return newProps[i]; },
629 newObj->nProperties },
630 [this](const Property &a, const Property &b) {
631 return propertyContentEqual(a, b);
632 });
633 }
634
635 void compareObjectAliases(const Object *oldObj, const Object *newObj)
636 {
637 const Alias *oldAliases = oldObj->aliasesBegin();
638 const Alias *newAliases = newObj->aliasesBegin();
639 compareIndexedElements<Alias>(
640 IndexedElements{ [oldAliases](quint32 i) { return oldAliases[i]; },
641 oldObj->nAliases },
642 IndexedElements{ [newAliases](quint32 i) { return newAliases[i]; },
643 newObj->nAliases },
644 [this](const Alias &a, const Alias &b) { return aliasContentEqual(a, b); });
645 }
646
647 void compareObjectEnums(const Object *oldObj, const Object *newObj)
648 {
649 compareIndexedElements<Enum>(
650 IndexedElements{ [oldObj](quint32 i) -> const Enum & { return *oldObj->enumAt(i); },
651 oldObj->nEnums },
652 IndexedElements{ [newObj](quint32 i) -> const Enum & { return *newObj->enumAt(i); },
653 newObj->nEnums },
654 [this](const Enum &a, const Enum &b) { return enumContentEqual(a, b); });
655 }
656
657 void compareObjectSignals(const Object *oldObj, const Object *newObj)
658 {
659 compareIndexedElements<Signal>(
660 IndexedElements{
661 [oldObj](quint32 i) -> const Signal & { return *oldObj->signalAt(i); },
662 oldObj->nSignals },
663 IndexedElements{
664 [newObj](quint32 i) -> const Signal & { return *newObj->signalAt(i); },
665 newObj->nSignals },
666 [this](const Signal &a, const Signal &b) { return signalContentEqual(a, b); });
667 }
668
669 void compareObjectInlineComponents(const Object *oldObj, const Object *newObj)
670 {
671 const InlineComponent *oldICs = oldObj->inlineComponentTable();
672 const InlineComponent *newICs = newObj->inlineComponentTable();
673 compareIndexedElements<InlineComponent>(
674 IndexedElements{ [oldICs](quint32 i) { return oldICs[i]; },
675 oldObj->nInlineComponents },
676 IndexedElements{ [newICs](quint32 i) { return newICs[i]; },
677 newObj->nInlineComponents },
678 [this](const InlineComponent &a, const InlineComponent &b) {
679 return a.objectIndex == b.objectIndex && stringsEqual(a.nameIndex, b.nameIndex);
680 });
681 }
682
683 void compareObjectRequiredPropertyExtraData(const Object *oldObj, const Object *newObj)
684 {
685 const RequiredPropertyExtraData *oldRPED = oldObj->requiredPropertyExtraDataTable();
686 const RequiredPropertyExtraData *newRPED = newObj->requiredPropertyExtraDataTable();
687 compareIndexedElements<RequiredPropertyExtraData>(
688 IndexedElements{ [oldRPED](quint32 i) { return oldRPED[i]; },
689 oldObj->nRequiredPropertyExtraData },
690 IndexedElements{ [newRPED](quint32 i) { return newRPED[i]; },
691 newObj->nRequiredPropertyExtraData },
692 [this](const RequiredPropertyExtraData &a, const RequiredPropertyExtraData &b) {
693 return stringsEqual(a.nameIndex, b.nameIndex);
694 });
695 }
696
697 void compareObjectBindings(const Object *oldObj, const Object *newObj)
698 {
699 const Binding *oldBindings = oldObj->bindingTable();
700 const Binding *newBindings = newObj->bindingTable();
701 compareIndexedElements<Binding>(
702 IndexedElements{ [oldBindings](quint32 i) { return oldBindings[i]; },
703 oldObj->nBindings },
704 IndexedElements{ [newBindings](quint32 i) { return newBindings[i]; },
705 newObj->nBindings },
706 [this](const Binding &a, const Binding &b) { return bindingContentEqual(a, b); });
707 }
708
710 {
711 const QmlUnit *oldQml = oldUnit->qmlUnit();
712 const QmlUnit *newQml = newUnit->qmlUnit();
713
714 if (!oldQml || !newQml)
715 return CompilationUnitDiff();
716
717 // Check for unit metadata changes (source file, timestamp, checksum, etc.)
719 Change change;
721 change.data = extractData(*newUnit);
722 changes.append(change);
723 }
724
725 compareIndexedElements<Object>(
726 IndexedElements{
727 [oldQml](quint32 i) -> const Object & { return *oldQml->objectAt(i); },
728 oldQml->nObjects },
729 IndexedElements{
730 [newQml](quint32 i) -> const Object & { return *newQml->objectAt(i); },
731 newQml->nObjects },
732 [this](const Object &a, const Object &b) { return objectContentEqual(a, b); });
733
734 // Compare details of overlapping objects only
735 const quint32 commonCount = std::min(oldQml->nObjects, newQml->nObjects);
736 for (quint32 objectIndex = 0; objectIndex < commonCount; ++objectIndex) {
737 QScopedValueRollback guard(m_currentObjectIndex, static_cast<int>(objectIndex));
738 const Object *oldObj = oldQml->objectAt(objectIndex);
739 const Object *newObj = newQml->objectAt(objectIndex);
740
741 compareObjectProperties(oldObj, newObj);
742 compareObjectAliases(oldObj, newObj);
743 compareObjectEnums(oldObj, newObj);
744 compareObjectSignals(oldObj, newObj);
745 compareObjectInlineComponents(oldObj, newObj);
746 compareObjectRequiredPropertyExtraData(oldObj, newObj);
747 compareObjectBindings(oldObj, newObj);
748 }
749
750 compareIndexedElements<quint64_le>(
751 IndexedElements{ [this](quint32 i) { return oldUnit->constants()[i]; },
752 oldUnit->constantTableSize },
753 IndexedElements{ [this](quint32 i) { return newUnit->constants()[i]; },
754 newUnit->constantTableSize },
755 [](const quint64_le &a, const quint64_le &b) { return a == b; });
756
757 compareIndexedElements<QString>(
758 IndexedElements{ [this](quint32 i) { return oldUnit->stringAtInternal(i); },
759 oldUnit->stringTableSize },
760 IndexedElements{ [this](quint32 i) { return newUnit->stringAtInternal(i); },
761 newUnit->stringTableSize },
762 [](const QString &a, const QString &b) { return a == b; });
763
764 compareIndexedElements<Lookup>(
765 IndexedElements{ [this](quint32 i) { return oldUnit->lookupTable()[i]; },
766 oldUnit->lookupTableSize },
767 IndexedElements{ [this](quint32 i) { return newUnit->lookupTable()[i]; },
768 newUnit->lookupTableSize },
769 [this](const Lookup &a, const Lookup &b) {
770 return a.type() == b.type() && a.mode() == b.mode()
771 && stringsEqual(a.nameIndex(), b.nameIndex());
772 });
773
774 compareIndexedElements<Import>(
775 IndexedElements{ [oldQml](quint32 i) { return *oldQml->importAt(i); },
776 oldQml->nImports },
777 IndexedElements{ [newQml](quint32 i) { return *newQml->importAt(i); },
778 newQml->nImports },
779 [this](const Import &a, const Import &b) {
780 return a.type == b.type && stringsEqual(a.uriIndex, b.uriIndex)
781 && stringsEqual(a.qualifierIndex, b.qualifierIndex)
782 && a.version == b.version;
783 });
784
785 compareIndexedElements<Function>(IndexedElements{ [this](quint32 i) -> const Function & {
786 return *oldUnit->functionAt(i);
787 },
788 oldUnit->functionTableSize },
789 IndexedElements{ [this](quint32 i) -> const Function & {
790 return *newUnit->functionAt(i);
791 },
792 newUnit->functionTableSize },
793 [this](const Function &a, const Function &b) {
794 return functionContentEqual(a, b);
795 });
796
797 compareIndexedElements<TranslationData>(
798 IndexedElements{ [this](quint32 i) { return oldUnit->translations()[i]; },
799 oldUnit->translationTableSize },
800 IndexedElements{ [this](quint32 i) { return newUnit->translations()[i]; },
801 newUnit->translationTableSize },
802 [this](const TranslationData &a, const TranslationData &b) {
803 return translationContentEqual(a, b);
804 });
805
806 compareIndexedElements<RegExp>(
807 IndexedElements{
808 [this](quint32 i) -> const RegExp & { return *oldUnit->regexpAt(i); },
809 oldUnit->regexpTableSize },
810 IndexedElements{
811 [this](quint32 i) -> const RegExp & { return *newUnit->regexpAt(i); },
812 newUnit->regexpTableSize },
813 [this](const RegExp &a, const RegExp &b) {
814 return a.flags() == b.flags() && stringsEqual(a.stringIndex(), b.stringIndex());
815 });
816
817 compareIndexedElements<Class>(
818 IndexedElements{
819 [this](quint32 i) -> const Class & { return *oldUnit->classAt(i); },
820 oldUnit->classTableSize },
821 IndexedElements{
822 [this](quint32 i) -> const Class & { return *newUnit->classAt(i); },
823 newUnit->classTableSize },
824 [this](const Class &a, const Class &b) { return classContentEqual(a, b); });
825
826 compareIndexedElements<TemplateObject>(
827 IndexedElements{ [this](quint32 i) -> const TemplateObject & {
828 return *oldUnit->templateObjectAt(i);
829 },
830 oldUnit->templateObjectTableSize },
831 IndexedElements{ [this](quint32 i) -> const TemplateObject & {
832 return *newUnit->templateObjectAt(i);
833 },
834 newUnit->templateObjectTableSize },
835 [this](const TemplateObject &a, const TemplateObject &b) {
836 return templateObjectContentEqual(a, b);
837 });
838
839 {
840 auto jsClassFromUnit = [](const Unit *unit, quint32 idx) -> const JSClass & {
841 const quint32_le *offsetTable = reinterpret_cast<const quint32_le *>(
842 reinterpret_cast<const char *>(unit) + unit->offsetToJSClassTable);
843 return *reinterpret_cast<const JSClass *>(reinterpret_cast<const char *>(unit)
844 + offsetTable[idx]);
845 };
846 compareIndexedElements<JSClass>(
847 IndexedElements{ [this, jsClassFromUnit](quint32 i) -> const JSClass & {
848 return jsClassFromUnit(oldUnit, i);
849 },
850 oldUnit->jsClassTableSize },
851 IndexedElements{ [this, jsClassFromUnit](quint32 i) -> const JSClass & {
852 return jsClassFromUnit(newUnit, i);
853 },
854 newUnit->jsClassTableSize },
855 [this](const JSClass &a, const JSClass &b) {
856 return jsClassContentEqual(a, b);
857 });
858 }
859
860 compareIndexedElements<Block>(
861 IndexedElements{
862 [this](quint32 i) -> const Block & { return *oldUnit->blockAt(i); },
863 oldUnit->blockTableSize },
864 IndexedElements{
865 [this](quint32 i) -> const Block & { return *newUnit->blockAt(i); },
866 newUnit->blockTableSize },
867 [this](const Block &a, const Block &b) { return blockContentEqual(a, b); });
868
869 return CompilationUnitDiff{ std::move(changes), true };
870 }
871};
872
873// NB: AddObject and RemoveObject are mutually exclusive since all objects are
874// in a global order per compilation unit. The number of objects can either
875// grow or shrink, but not both. Likewise AddBinding and RemoveBinding, but
876// per object. Before adding bindings, all the objects need to be present.
877// It's a good idea to remove stale bindings before they can cause any
878// trouble. Therefore, we do that first, before changing or adding bindings.
889
890static Severity classifyChange(const Change &change)
891{
892 switch (change.type) {
902 return Replace;
921 return Rebuild;
923 return AddObject;
925 return RemoveBinding;
927 return ChangeBinding;
929 return AddBinding;
931 return RemoveObject;
941 return Ignore;
970 break;
971 }
972 return Ignore;
973}
974
975static void sortChanges(QSpan<Change> changes)
976{
977 std::stable_sort(changes.begin(), changes.end(), [](const Change &a, const Change &b) {
978 return quint8(classifyChange(a)) < quint8(classifyChange(b));
979 });
980}
981
982CompilationUnitDiff diffCompilationUnits(const Unit *oldUnit, const Unit *newUnit)
983{
984 auto diff = UnitDiffer{ oldUnit, newUnit, {} }.diff();
985
986 // Sort the changes so that they are easy to apply.
987 // We want the most severe changes first.
988 sortChanges(diff.changes);
989
990 return diff;
991}
992
993} // namespace QV4::CompiledData
994
995QT_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