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 == 0x4c);
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 // If anything below the checksum has changed, the checksum itself has changed, too
331 return std::memcmp(oldUnit->md5Checksum, newUnit->md5Checksum, sizeof(oldUnit->md5Checksum))
332 == 0;
333 }
334
335 bool objectContentEqual(const Object &oldObj, const Object &newObj) const
336 {
337 // Keep in sync with the Object struct in qv4compileddata_p.h
338 static_assert(sizeof(Object) == 84, "Update objectContentEqual when Object layout changes");
339 if (oldObj.nNamedObjectsInComponent != newObj.nNamedObjectsInComponent)
340 return false;
341
342 const quint32_le *oldNamed = oldObj.namedObjectsInComponentTable();
343 const quint32_le *newNamed = newObj.namedObjectsInComponentTable();
344 for (quint32 i = 0; i < oldObj.nNamedObjectsInComponent; ++i) {
345 if (oldNamed[i] != newNamed[i])
346 return false;
347 }
348
349 if (oldObj.nFunctions != newObj.nFunctions)
350 return false;
351
352 const quint32_le *oldFuncs = oldObj.functionOffsetTable();
353 const quint32_le *newFuncs = newObj.functionOffsetTable();
354 for (quint32 i = 0; i < oldObj.nFunctions; ++i) {
355 if (oldFuncs[i] != newFuncs[i])
356 return false;
357 if (!functionContentEqual(*oldUnit->functionAt(oldFuncs[i]),
358 *newUnit->functionAt(newFuncs[i]))) {
359 return false;
360 }
361 }
362
363 return stringsEqual(oldObj.inheritedTypeNameIndex, newObj.inheritedTypeNameIndex)
364 && stringsEqual(oldObj.idNameIndex, newObj.idNameIndex)
365 && oldObj.flags() == newObj.flags()
366 && oldObj.hasAliasAsDefaultProperty() == newObj.hasAliasAsDefaultProperty()
367 && oldObj.objectId() == newObj.objectId()
368 && oldObj.indexOfDefaultPropertyOrAlias == newObj.indexOfDefaultPropertyOrAlias;
369 }
370
371 bool propertyContentEqual(const Property &oldProp, const Property &newProp) const
372 {
373 // Keep in sync with the Property struct in qv4compileddata_p.h
374 static_assert(sizeof(Property) == 12, "Update propertyContentEqual when Property layout changes");
375 return stringsEqual(oldProp.nameIndex(), newProp.nameIndex())
376 && (oldProp.isCommonType() == newProp.isCommonType()
377 && (oldProp.isCommonType()
378 ? oldProp.commonTypeOrTypeNameIndex()
379 == newProp.commonTypeOrTypeNameIndex()
380 : stringsEqual(oldProp.typeNameIndex(), newProp.typeNameIndex())))
381 && oldProp.isReadOnly() == newProp.isReadOnly()
382 && oldProp.isRequired() == newProp.isRequired()
383 && oldProp.isList() == newProp.isList()
384 && oldProp.isVirtual() == newProp.isVirtual()
385 && oldProp.isOverride() == newProp.isOverride()
386 && oldProp.isFinal() == newProp.isFinal();
387 }
388
389 // In addition to structural equality, also compare the actual name strings: the integer
390 // nameIndex may be the same while the string at that index changed (property rename).
391 bool aliasContentEqual(const Alias &oldAlias, const Alias &newAlias) const
392 {
393 // Keep in sync with the Alias struct in qv4compileddata_p.h
394 static_assert(sizeof(Alias) == 20, "Update aliasContentEqual when Alias layout changes");
395 return stringsEqual(oldAlias.idIndex(), newAlias.idIndex())
396 && stringsEqual(oldAlias.nameIndex(), newAlias.nameIndex())
397 && stringsEqual(oldAlias.propertyNameIndex(), newAlias.propertyNameIndex())
398 && oldAlias.isReadOnly() == newAlias.isReadOnly();
399 }
400
401 bool enumContentEqual(const Enum &oldEnum, const Enum &newEnum) const
402 {
403 // Keep in sync with the Enum and EnumValue structs in qv4compileddata_p.h
404 static_assert(sizeof(Enum) == 12, "Update enumContentEqual when Enum layout changes");
405 static_assert(sizeof(EnumValue) == 8, "Update enumContentEqual when EnumValue layout changes");
406 if (!stringsEqual(oldEnum.nameIndex, newEnum.nameIndex)
407 || oldEnum.nEnumValues != newEnum.nEnumValues) {
408 return false;
409 }
410
411 for (quint32 i = 0; i < oldEnum.nEnumValues; ++i) {
412 const EnumValue *oldValue = oldEnum.enumValueAt(i);
413 const EnumValue *newValue = newEnum.enumValueAt(i);
414 if (oldValue->value != newValue->value
415 || !stringsEqual(oldValue->nameIndex, newValue->nameIndex)) {
416 return false;
417 }
418 }
419
420 return true;
421 }
422
423 bool signalContentEqual(const Signal &oldSig, const Signal &newSig) const
424 {
425 // Keep in sync with the Signal and Parameter structs in qv4compileddata_p.h
426 static_assert(sizeof(Signal) == 12, "Update signalContentEqual when Signal layout changes");
427 static_assert(sizeof(Parameter) == 8, "Update signalContentEqual when Parameter layout changes");
428 if (!stringsEqual(oldSig.nameIndex, newSig.nameIndex))
429 return false;
430 if (oldSig.nParameters != newSig.nParameters)
431 return false;
432 for (quint32 i = 0; i < oldSig.nParameters; ++i) {
433 const Parameter *oldP = oldSig.parameterAt(i);
434 const Parameter *newP = newSig.parameterAt(i);
435 if (!stringsEqual(oldP->nameIndex, newP->nameIndex)
436 || !parameterTypeContentEqual(oldP->type, newP->type)) {
437 return false;
438 }
439 }
440 return true;
441 }
442
443 bool bindingContentEqual(const Binding &oldBinding, const Binding &newBinding) const
444 {
445 // Keep in sync with the Binding and TranslationData structs in qv4compileddata_p.h
446 static_assert(sizeof(Binding) == 24, "Update bindingContentEqual when Binding layout changes");
447 static_assert(sizeof(TranslationData) == 16, "Update bindingContentEqual when TranslationData layout changes");
448 if (oldBinding.type() != newBinding.type() || oldBinding.flags() != newBinding.flags())
449 return false;
450
451 if (!stringsEqual(oldBinding.propertyNameIndex, newBinding.propertyNameIndex))
452 return false;
453
454 switch (oldBinding.type()) {
455 case Binding::Type_Invalid:
456 return true;
457 case Binding::Type_Boolean:
458 return oldBinding.valueAsBoolean() == newBinding.valueAsBoolean();
459 case Binding::Type_Number: {
460 const uint oldIdx = oldBinding.value.constantValueIndex;
461 const uint newIdx = newBinding.value.constantValueIndex;
462 Q_ASSERT(oldIdx < oldUnit->constantTableSize);
463 Q_ASSERT(newIdx < newUnit->constantTableSize);
464 return oldIdx == newIdx && oldUnit->constants()[oldIdx] == newUnit->constants()[newIdx];
465 }
466 case Binding::Type_String:
467 return stringsEqual(oldBinding.stringIndex, newBinding.stringIndex);
468 case Binding::Type_Null:
469 return true;
470 case Binding::Type_Translation:
471 case Binding::Type_TranslationById: {
472 const auto &oldTrans = oldUnit->translations()[oldBinding.value.translationDataIndex];
473 const auto &newTrans = newUnit->translations()[newBinding.value.translationDataIndex];
474
475 // contextIndex uses NoContextIndex as sentinel; compare by
476 // resolved string only when both are valid indices.
477 const bool oldHasCtx = oldTrans.contextIndex != TranslationData::NoContextIndex;
478 const bool newHasCtx = newTrans.contextIndex != TranslationData::NoContextIndex;
479
480 if (oldHasCtx != newHasCtx)
481 return false;
482
483 if (oldHasCtx && !stringsEqual(oldTrans.contextIndex, newTrans.contextIndex))
484 return false;
485
486 return stringsEqual(oldBinding.stringIndex, newBinding.stringIndex)
487 && stringsEqual(oldTrans.stringIndex, newTrans.stringIndex)
488 && stringsEqual(oldTrans.commentIndex, newTrans.commentIndex)
489 && oldTrans.number == newTrans.number;
490 }
491 case Binding::Type_Script: {
492 if (oldBinding.value.compiledScriptIndex != newBinding.value.compiledScriptIndex)
493 return false;
494 const auto *oldFunc = oldUnit->functionAt(oldBinding.value.compiledScriptIndex);
495 const auto *newFunc = newUnit->functionAt(newBinding.value.compiledScriptIndex);
496 return oldFunc->nFormals == newFunc->nFormals && oldFunc->codeSize == newFunc->codeSize
497 && std::memcmp(oldFunc->code(), newFunc->code(), oldFunc->codeSize) == 0;
498 }
499 case Binding::Type_Object:
500 case Binding::Type_AttachedProperty:
501 case Binding::Type_GroupProperty:
502 return oldBinding.value.objectIndex == newBinding.value.objectIndex
503 && objectContentEqual(
504 *oldUnit->qmlUnit()->objectAt(oldBinding.value.objectIndex),
505 *newUnit->qmlUnit()->objectAt(newBinding.value.objectIndex));
506 }
507
508 return false;
509 }
510
511 bool parameterTypeContentEqual(const ParameterType &oldType, const ParameterType &newType) const
512 {
513 // Keep in sync with the ParameterType struct in qv4compileddata_p.h
514 static_assert(sizeof(ParameterType) == 4, "Update parameterTypeContentEqual when ParameterType layout changes");
515 const bool indexIsCommonType = oldType.indexIsCommonType();
516 if (newType.indexIsCommonType() != indexIsCommonType
517 || oldType.isList() != newType.isList()) {
518 return false;
519 }
520
521 return indexIsCommonType
522 ? oldType.typeNameIndexOrCommonType() == newType.typeNameIndexOrCommonType()
523 : stringsEqual(oldType.typeNameIndexOrCommonType(),
524 newType.typeNameIndexOrCommonType());
525 }
526
527 bool functionContentEqual(const Function &oldFunc, const Function &newFunc) const
528 {
529 // Keep in sync with the Function struct in qv4compileddata_p.h
530 static_assert(sizeof(Function) == 56, "Update functionContentEqual when Function layout changes");
531 if (oldFunc.nestedFunctionIndex != newFunc.nestedFunctionIndex
532 || !stringsEqual(oldFunc.nameIndex, newFunc.nameIndex) || oldFunc.flags != newFunc.flags
533 || oldFunc.nFormals != newFunc.nFormals || oldFunc.codeSize != newFunc.codeSize
534 || !parameterTypeContentEqual(oldFunc.returnType, newFunc.returnType)) {
535 return false;
536 }
537
538 for (int i = 0, end = oldFunc.nFormals; i != end; ++i) {
539 const auto &oldFormal = oldFunc.formalsTable()[i];
540 const auto &newFormal = newFunc.formalsTable()[i];
541 if (!parameterTypeContentEqual(oldFormal.type, newFormal.type))
542 return false;
543 if (!stringsEqual(oldFormal.nameIndex, newFormal.nameIndex))
544 return false;
545 }
546
547 return std::memcmp(oldFunc.code(), newFunc.code(), oldFunc.codeSize) == 0;
548 }
549
550 bool translationContentEqual(const TranslationData &oldTrans,
551 const TranslationData &newTrans) const
552 {
553 // Keep in sync with the TranslationData struct in qv4compileddata_p.h
554 static_assert(sizeof(TranslationData) == 16, "Update translationContentEqual when TranslationData layout changes");
555 if (oldTrans.contextIndex != newTrans.contextIndex)
556 return false;
557 if (oldTrans.contextIndex != TranslationData::NoContextIndex
558 && !stringsEqual(oldTrans.contextIndex, newTrans.contextIndex)) {
559 return false;
560 }
561 return stringsEqual(oldTrans.stringIndex, newTrans.stringIndex)
562 && stringsEqual(oldTrans.commentIndex, newTrans.commentIndex)
563 && oldTrans.number == newTrans.number;
564 }
565
566 bool classContentEqual(const Class &oldCls, const Class &newCls) const
567 {
568 // Keep in sync with the Class and Method structs in qv4compileddata_p.h
569 static_assert(sizeof(Class) == 24, "Update classContentEqual when Class layout changes");
570 static_assert(sizeof(Method) == 12, "Update classContentEqual when Method layout changes");
571 if (!stringsEqual(oldCls.nameIndex, newCls.nameIndex)
572 || oldCls.scopeIndex != newCls.scopeIndex
573 || oldCls.constructorFunction != newCls.constructorFunction
574 || oldCls.nStaticMethods != newCls.nStaticMethods
575 || oldCls.nMethods != newCls.nMethods) {
576 return false;
577 }
578 const Method *oldMethods = oldCls.methodTable();
579 const Method *newMethods = newCls.methodTable();
580 for (quint32 i = 0, end = oldCls.nStaticMethods + oldCls.nMethods; i < end; ++i) {
581 if (!stringsEqual(oldMethods[i].name, newMethods[i].name)
582 || oldMethods[i].type != newMethods[i].type
583 || oldMethods[i].function != newMethods[i].function) {
584 return false;
585 }
586 }
587 return true;
588 }
589
590 bool templateObjectContentEqual(const TemplateObject &oldTO, const TemplateObject &newTO) const
591 {
592 // Keep in sync with the TemplateObject struct in qv4compileddata_p.h
593 static_assert(sizeof(TemplateObject) == 4, "Update templateObjectContentEqual when TemplateObject layout changes");
594 if (oldTO.size != newTO.size)
595 return false;
596 const quint32_le *oldStrings = reinterpret_cast<const quint32_le *>(&oldTO + 1);
597 const quint32_le *newStrings = reinterpret_cast<const quint32_le *>(&newTO + 1);
598 for (quint32 i = 0; i < 2u * oldTO.size; ++i) {
599 if (!stringsEqual(oldStrings[i], newStrings[i]))
600 return false;
601 }
602 return true;
603 }
604
605 bool jsClassContentEqual(const JSClass &oldJC, const JSClass &newJC) const
606 {
607 // Keep in sync with the JSClass and JSClassMember structs in qv4compileddata_p.h
608 static_assert(sizeof(JSClass) == 4, "Update jsClassContentEqual when JSClass layout changes");
609 static_assert(sizeof(JSClassMember) == 4, "Update jsClassContentEqual when JSClassMember layout changes");
610 if (oldJC.nMembers != newJC.nMembers)
611 return false;
612 const JSClassMember *oldMembers = reinterpret_cast<const JSClassMember *>(&oldJC + 1);
613 const JSClassMember *newMembers = reinterpret_cast<const JSClassMember *>(&newJC + 1);
614 for (quint32 i = 0; i < oldJC.nMembers; ++i) {
615 if (!stringsEqual(oldMembers[i].nameOffset(), newMembers[i].nameOffset())
616 || oldMembers[i].isAccessor() != newMembers[i].isAccessor()) {
617 return false;
618 }
619 }
620 return true;
621 }
622
623 bool blockContentEqual(const Block &oldBlock, const Block &newBlock) const
624 {
625 // Keep in sync with the Block struct in qv4compileddata_p.h
626 static_assert(sizeof(Block) == 12, "Update blockContentEqual when Block layout changes");
627 if (oldBlock.nLocals != newBlock.nLocals
628 || oldBlock.sizeOfLocalTemporalDeadZone != newBlock.sizeOfLocalTemporalDeadZone) {
629 return false;
630 }
631 for (quint32 i = 0; i < oldBlock.nLocals; ++i) {
632 if (!stringsEqual(oldBlock.localsTable()[i], newBlock.localsTable()[i]))
633 return false;
634 }
635 return true;
636 }
637
638 // Per-object comparison methods
639
640 void compareObjectProperties(const Object *oldObj, const Object *newObj)
641 {
642 const Property *oldProps = oldObj->propertyTable();
643 const Property *newProps = newObj->propertyTable();
644 compareIndexedElements<Property>(
645 IndexedElements{ [oldProps](quint32 i) { return oldProps[i]; },
646 oldObj->nProperties },
647 IndexedElements{ [newProps](quint32 i) { return newProps[i]; },
648 newObj->nProperties },
649 [this](const Property &a, const Property &b) {
650 return propertyContentEqual(a, b);
651 });
652 }
653
654 void compareObjectAliases(const Object *oldObj, const Object *newObj)
655 {
656 const Alias *oldAliases = oldObj->aliasesBegin();
657 const Alias *newAliases = newObj->aliasesBegin();
658 compareIndexedElements<Alias>(
659 IndexedElements{ [oldAliases](quint32 i) { return oldAliases[i]; },
660 oldObj->nAliases },
661 IndexedElements{ [newAliases](quint32 i) { return newAliases[i]; },
662 newObj->nAliases },
663 [this](const Alias &a, const Alias &b) { return aliasContentEqual(a, b); });
664 }
665
666 void compareObjectEnums(const Object *oldObj, const Object *newObj)
667 {
668 compareIndexedElements<Enum>(
669 IndexedElements{ [oldObj](quint32 i) -> const Enum & { return *oldObj->enumAt(i); },
670 oldObj->nEnums },
671 IndexedElements{ [newObj](quint32 i) -> const Enum & { return *newObj->enumAt(i); },
672 newObj->nEnums },
673 [this](const Enum &a, const Enum &b) { return enumContentEqual(a, b); });
674 }
675
676 void compareObjectSignals(const Object *oldObj, const Object *newObj)
677 {
678 compareIndexedElements<Signal>(
679 IndexedElements{
680 [oldObj](quint32 i) -> const Signal & { return *oldObj->signalAt(i); },
681 oldObj->nSignals },
682 IndexedElements{
683 [newObj](quint32 i) -> const Signal & { return *newObj->signalAt(i); },
684 newObj->nSignals },
685 [this](const Signal &a, const Signal &b) { return signalContentEqual(a, b); });
686 }
687
688 void compareObjectInlineComponents(const Object *oldObj, const Object *newObj)
689 {
690 const InlineComponent *oldICs = oldObj->inlineComponentTable();
691 const InlineComponent *newICs = newObj->inlineComponentTable();
692 compareIndexedElements<InlineComponent>(
693 IndexedElements{ [oldICs](quint32 i) { return oldICs[i]; },
694 oldObj->nInlineComponents },
695 IndexedElements{ [newICs](quint32 i) { return newICs[i]; },
696 newObj->nInlineComponents },
697 [this](const InlineComponent &a, const InlineComponent &b) {
698 return a.objectIndex == b.objectIndex && stringsEqual(a.nameIndex, b.nameIndex);
699 });
700 }
701
702 void compareObjectRequiredPropertyExtraData(const Object *oldObj, const Object *newObj)
703 {
704 const RequiredPropertyExtraData *oldRPED = oldObj->requiredPropertyExtraDataTable();
705 const RequiredPropertyExtraData *newRPED = newObj->requiredPropertyExtraDataTable();
706 compareIndexedElements<RequiredPropertyExtraData>(
707 IndexedElements{ [oldRPED](quint32 i) { return oldRPED[i]; },
708 oldObj->nRequiredPropertyExtraData },
709 IndexedElements{ [newRPED](quint32 i) { return newRPED[i]; },
710 newObj->nRequiredPropertyExtraData },
711 [this](const RequiredPropertyExtraData &a, const RequiredPropertyExtraData &b) {
712 return stringsEqual(a.nameIndex, b.nameIndex);
713 });
714 }
715
716 void compareObjectBindings(const Object *oldObj, const Object *newObj)
717 {
718 const Binding *oldBindings = oldObj->bindingTable();
719 const Binding *newBindings = newObj->bindingTable();
720 compareIndexedElements<Binding>(
721 IndexedElements{ [oldBindings](quint32 i) { return oldBindings[i]; },
722 oldObj->nBindings },
723 IndexedElements{ [newBindings](quint32 i) { return newBindings[i]; },
724 newObj->nBindings },
725 [this](const Binding &a, const Binding &b) { return bindingContentEqual(a, b); });
726 }
727
729 {
730 const QmlUnit *oldQml = oldUnit->qmlUnit();
731 const QmlUnit *newQml = newUnit->qmlUnit();
732
733 if (!oldQml || !newQml)
734 return CompilationUnitDiff();
735
736 // Check for unit metadata changes (source file, timestamp, checksum, etc.)
738 Change change;
740 change.data = extractData(*newUnit);
741 changes.append(change);
742 }
743
744 compareIndexedElements<Object>(
745 IndexedElements{
746 [oldQml](quint32 i) -> const Object & { return *oldQml->objectAt(i); },
747 oldQml->nObjects },
748 IndexedElements{
749 [newQml](quint32 i) -> const Object & { return *newQml->objectAt(i); },
750 newQml->nObjects },
751 [this](const Object &a, const Object &b) { return objectContentEqual(a, b); });
752
753 // Compare details of overlapping objects only
754 const quint32 commonCount = std::min(oldQml->nObjects, newQml->nObjects);
755 for (quint32 objectIndex = 0; objectIndex < commonCount; ++objectIndex) {
756 QScopedValueRollback guard(m_currentObjectIndex, static_cast<int>(objectIndex));
757 const Object *oldObj = oldQml->objectAt(objectIndex);
758 const Object *newObj = newQml->objectAt(objectIndex);
759
760 compareObjectProperties(oldObj, newObj);
761 compareObjectAliases(oldObj, newObj);
762 compareObjectEnums(oldObj, newObj);
763 compareObjectSignals(oldObj, newObj);
764 compareObjectInlineComponents(oldObj, newObj);
765 compareObjectRequiredPropertyExtraData(oldObj, newObj);
766 compareObjectBindings(oldObj, newObj);
767 }
768
769 compareIndexedElements<quint64_le>(
770 IndexedElements{ [this](quint32 i) { return oldUnit->constants()[i]; },
771 oldUnit->constantTableSize },
772 IndexedElements{ [this](quint32 i) { return newUnit->constants()[i]; },
773 newUnit->constantTableSize },
774 [](const quint64_le &a, const quint64_le &b) { return a == b; });
775
776 compareIndexedElements<QString>(
777 IndexedElements{ [this](quint32 i) { return oldUnit->stringAtInternal(i); },
778 oldUnit->stringTableSize },
779 IndexedElements{ [this](quint32 i) { return newUnit->stringAtInternal(i); },
780 newUnit->stringTableSize },
781 [](const QString &a, const QString &b) { return a == b; });
782
783 compareIndexedElements<Lookup>(
784 IndexedElements{ [this](quint32 i) { return oldUnit->lookupTable()[i]; },
785 oldUnit->lookupTableSize },
786 IndexedElements{ [this](quint32 i) { return newUnit->lookupTable()[i]; },
787 newUnit->lookupTableSize },
788 [this](const Lookup &a, const Lookup &b) {
789 return a.type() == b.type() && a.mode() == b.mode()
790 && stringsEqual(a.nameIndex(), b.nameIndex());
791 });
792
793 compareIndexedElements<Import>(
794 IndexedElements{ [oldQml](quint32 i) { return *oldQml->importAt(i); },
795 oldQml->nImports },
796 IndexedElements{ [newQml](quint32 i) { return *newQml->importAt(i); },
797 newQml->nImports },
798 [this](const Import &a, const Import &b) {
799 return a.type == b.type && stringsEqual(a.uriIndex, b.uriIndex)
800 && stringsEqual(a.qualifierIndex, b.qualifierIndex)
801 && a.version == b.version;
802 });
803
804 compareIndexedElements<Function>(IndexedElements{ [this](quint32 i) -> const Function & {
805 return *oldUnit->functionAt(i);
806 },
807 oldUnit->functionTableSize },
808 IndexedElements{ [this](quint32 i) -> const Function & {
809 return *newUnit->functionAt(i);
810 },
811 newUnit->functionTableSize },
812 [this](const Function &a, const Function &b) {
813 return functionContentEqual(a, b);
814 });
815
816 compareIndexedElements<TranslationData>(
817 IndexedElements{ [this](quint32 i) { return oldUnit->translations()[i]; },
818 oldUnit->translationTableSize },
819 IndexedElements{ [this](quint32 i) { return newUnit->translations()[i]; },
820 newUnit->translationTableSize },
821 [this](const TranslationData &a, const TranslationData &b) {
822 return translationContentEqual(a, b);
823 });
824
825 compareIndexedElements<RegExp>(
826 IndexedElements{
827 [this](quint32 i) -> const RegExp & { return *oldUnit->regexpAt(i); },
828 oldUnit->regexpTableSize },
829 IndexedElements{
830 [this](quint32 i) -> const RegExp & { return *newUnit->regexpAt(i); },
831 newUnit->regexpTableSize },
832 [this](const RegExp &a, const RegExp &b) {
833 return a.flags() == b.flags() && stringsEqual(a.stringIndex(), b.stringIndex());
834 });
835
836 compareIndexedElements<Class>(
837 IndexedElements{
838 [this](quint32 i) -> const Class & { return *oldUnit->classAt(i); },
839 oldUnit->classTableSize },
840 IndexedElements{
841 [this](quint32 i) -> const Class & { return *newUnit->classAt(i); },
842 newUnit->classTableSize },
843 [this](const Class &a, const Class &b) { return classContentEqual(a, b); });
844
845 compareIndexedElements<TemplateObject>(
846 IndexedElements{ [this](quint32 i) -> const TemplateObject & {
847 return *oldUnit->templateObjectAt(i);
848 },
849 oldUnit->templateObjectTableSize },
850 IndexedElements{ [this](quint32 i) -> const TemplateObject & {
851 return *newUnit->templateObjectAt(i);
852 },
853 newUnit->templateObjectTableSize },
854 [this](const TemplateObject &a, const TemplateObject &b) {
855 return templateObjectContentEqual(a, b);
856 });
857
858 {
859 auto jsClassFromUnit = [](const Unit *unit, quint32 idx) -> const JSClass & {
860 const quint32_le *offsetTable = reinterpret_cast<const quint32_le *>(
861 reinterpret_cast<const char *>(unit) + unit->offsetToJSClassTable);
862 return *reinterpret_cast<const JSClass *>(reinterpret_cast<const char *>(unit)
863 + offsetTable[idx]);
864 };
865 compareIndexedElements<JSClass>(
866 IndexedElements{ [this, jsClassFromUnit](quint32 i) -> const JSClass & {
867 return jsClassFromUnit(oldUnit, i);
868 },
869 oldUnit->jsClassTableSize },
870 IndexedElements{ [this, jsClassFromUnit](quint32 i) -> const JSClass & {
871 return jsClassFromUnit(newUnit, i);
872 },
873 newUnit->jsClassTableSize },
874 [this](const JSClass &a, const JSClass &b) {
875 return jsClassContentEqual(a, b);
876 });
877 }
878
879 compareIndexedElements<Block>(
880 IndexedElements{
881 [this](quint32 i) -> const Block & { return *oldUnit->blockAt(i); },
882 oldUnit->blockTableSize },
883 IndexedElements{
884 [this](quint32 i) -> const Block & { return *newUnit->blockAt(i); },
885 newUnit->blockTableSize },
886 [this](const Block &a, const Block &b) { return blockContentEqual(a, b); });
887
888 return CompilationUnitDiff{ std::move(changes), true };
889 }
890};
891
892// NB: AddObject and RemoveObject are mutually exclusive since all objects are
893// in a global order per compilation unit. The number of objects can either
894// grow or shrink, but not both. Likewise AddBinding and RemoveBinding, but
895// per object. Before adding bindings, all the objects need to be present.
896// It's a good idea to remove stale bindings before they can cause any
897// trouble. Therefore, we do that first, before changing or adding bindings.
908
909static Severity classifyChange(const Change &change)
910{
911 switch (change.type) {
921 return Replace;
940 return Rebuild;
942 return AddObject;
944 return RemoveBinding;
946 return ChangeBinding;
948 return AddBinding;
950 return RemoveObject;
960 return Ignore;
989 break;
990 }
991 return Ignore;
992}
993
994static void sortChanges(QSpan<Change> changes)
995{
996 std::stable_sort(changes.begin(), changes.end(), [](const Change &a, const Change &b) {
997 return quint8(classifyChange(a)) < quint8(classifyChange(b));
998 });
999}
1000
1001CompilationUnitDiff diffCompilationUnits(const Unit *oldUnit, const Unit *newUnit)
1002{
1003 auto diff = UnitDiffer{ oldUnit, newUnit, {} }.diff();
1004
1005 // Sort the changes so that they are easy to apply.
1006 // We want the most severe changes first.
1007 sortChanges(diff.changes);
1008
1009 return diff;
1010}
1011
1012} // namespace QV4::CompiledData
1013
1014QT_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