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
qqmljstypepropagator.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant
4
7
9
10#include <private/qv4compilerscanfunctions_p.h>
11
12#include <QtQmlCompiler/private/qqmlsasourcelocation_p.h>
13
15
16using namespace Qt::StringLiterals;
17
18/*!
19 * \internal
20 * \class QQmlJSTypePropagator
21 *
22 * QQmlJSTypePropagator is the initial pass that performs the type inference and
23 * annotates every register in use at any instruction with the possible types it
24 * may hold. This includes information on how and in what scope the values are
25 * retrieved. These annotations may be used by further compile passes for
26 * refinement or code generation.
27 */
28
29QQmlJSCompilePass::BlocksAndAnnotations QQmlJSTypePropagator::run(const Function *function)
30{
31 m_function = function;
32 m_returnType = m_function->returnType;
33
34 // We are the only pass that merges register contents. See QQmlJSTypeResolver::m_mergeCache.
35 m_typeResolver->clearMergeCache();
36
37 // We cannot assume anything about how a script string will be used
38 if (m_returnType.containedType() == m_typeResolver->qQmlScriptStringType())
39 return {};
40
41 do {
42 // Reset the error if we need to do another pass
43 if (m_state.needsMorePasses)
44 m_logger->rollback();
45
46 m_logger->startTransaction();
47
48 m_prevStateAnnotations = m_state.annotations;
49 m_state = PassState();
50 m_state.annotations = m_annotations;
51 m_state.State::operator=(initialState(m_function));
52
53 reset();
54 decode(m_function->code.constData(), static_cast<uint>(m_function->code.size()));
55
56 // If we have found unresolved backwards jumps, we need to start over with a fresh state.
57 // Mind that m_jumpOriginRegisterStateByTargetInstructionOffset is retained in that case.
58 // This means that we won't start over for the same reason again.
59 } while (m_state.needsMorePasses);
60
61 m_logger->commit();
62 return { std::move(m_basicBlocks), std::move(m_state.annotations) };
63}
64
65#define INSTR_PROLOGUE_NOT_IMPLEMENTED()
66 addError(u"Instruction \"%1\" not implemented"_s.arg(QString::fromUtf8(__func__)));
67 return;
68
69#define INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC()
70 addError(u"Instruction \"%1\" not implemented"_s.arg(QString::fromUtf8(__func__)));
71 setVarAccumulatorAndError(); /* Keep sane state after error */
72 return;
73
74#define INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE()
75 m_logger->log(u"Instruction \"%1\" not implemented"_s.arg(QString::fromUtf8(__func__)),
76 qmlCompiler, QQmlJS::SourceLocation());
77 return;
78
79void QQmlJSTypePropagator::generate_Ret()
80{
81 if (m_function->isSignalHandler) {
82 // Signal handlers cannot return anything.
83 } else if (m_state.accumulatorIn().contains(m_typeResolver->voidType())) {
84 // You can always return undefined.
85 } else if (!m_returnType.isValid() && m_state.accumulatorIn().isValid()) {
86 addError(u"function without return type annotation returns %1. This may prevent proper "_s
87 u"compilation to Cpp."_s.arg(m_state.accumulatorIn().descriptiveName()));
88 return;
89 } else if (!canConvertFromTo(m_state.accumulatorIn(), m_returnType)) {
90 addError(u"cannot convert from %1 to %2"_s
91 .arg(m_state.accumulatorIn().descriptiveName(),
92 m_returnType.descriptiveName()));
93 return;
94 }
95
96 if (m_returnType.isValid()) {
97 // We need to preserve any possible undefined value as that resets the property.
98 if (m_typeResolver->canHoldUndefined(m_state.accumulatorIn()))
99 addReadAccumulator();
100 else
101 addReadAccumulator(m_returnType);
102 }
103
104 m_state.setHasInternalSideEffects();
105 m_state.skipInstructionsUntilNextJumpTarget = true;
106}
107
108void QQmlJSTypePropagator::generate_Debug()
109{
111}
112
113void QQmlJSTypePropagator::generate_LoadConst(int index)
114{
115 auto encodedConst = m_jsUnitGenerator->constant(index);
116 setAccumulator(m_typeResolver->literalType(m_typeResolver->typeForConst(encodedConst)));
117}
118
119void QQmlJSTypePropagator::generate_LoadZero()
120{
121 setAccumulator(m_typeResolver->literalType(m_typeResolver->int32Type()));
122}
123
124void QQmlJSTypePropagator::generate_LoadTrue()
125{
126 setAccumulator(m_typeResolver->literalType(m_typeResolver->boolType()));
127}
128
129void QQmlJSTypePropagator::generate_LoadFalse()
130{
131 setAccumulator(m_typeResolver->literalType(m_typeResolver->boolType()));
132}
133
134void QQmlJSTypePropagator::generate_LoadNull()
135{
136 setAccumulator(m_typeResolver->literalType(m_typeResolver->nullType()));
137}
138
139void QQmlJSTypePropagator::generate_LoadUndefined()
140{
141 setAccumulator(m_typeResolver->literalType(m_typeResolver->voidType()));
142}
143
144void QQmlJSTypePropagator::generate_LoadInt(int)
145{
146 setAccumulator(m_typeResolver->literalType(m_typeResolver->int32Type()));
147}
148
149void QQmlJSTypePropagator::generate_MoveConst(int constIndex, int destTemp)
150{
151 auto encodedConst = m_jsUnitGenerator->constant(constIndex);
152 setRegister(destTemp, m_typeResolver->literalType(m_typeResolver->typeForConst(encodedConst)));
153}
154
155void QQmlJSTypePropagator::generate_LoadReg(int reg)
156{
157 // Do not re-track the register. We're not manipulating it.
158 m_state.setIsRename(true);
159 const QQmlJSRegisterContent content = checkedInputRegister(reg);
160 m_state.addReadRegister(reg, content);
161 m_state.setRegister(Accumulator, content);
162}
163
164void QQmlJSTypePropagator::generate_StoreReg(int reg)
165{
166 // Do not re-track the register. We're not manipulating it.
167 m_state.setIsRename(true);
168 m_state.addReadAccumulator(m_state.accumulatorIn());
169 m_state.setRegister(reg, m_state.accumulatorIn());
170}
171
172void QQmlJSTypePropagator::generate_MoveReg(int srcReg, int destReg)
173{
174 Q_ASSERT(destReg != InvalidRegister);
175 // Do not re-track the register. We're not manipulating it.
176 m_state.setIsRename(true);
177 const QQmlJSRegisterContent content = checkedInputRegister(srcReg);
178 m_state.addReadRegister(srcReg, content);
179 m_state.setRegister(destReg, content);
180}
181
182void QQmlJSTypePropagator::generate_LoadImport(int index)
183{
184 Q_UNUSED(index)
186}
187
188void QQmlJSTypePropagator::generate_LoadLocal(int index)
189{
190 // TODO: In order to accurately track locals we'd need to track JavaScript contexts first.
191 // This could be done by populating the initial JS context and implementing the various
192 // Push and Pop operations. For now, this is pretty barren.
193
194 QQmlJSMetaProperty local;
195 local.setType(m_typeResolver->jsValueType());
196 local.setIndex(index);
197
198 setAccumulator(m_pool->createProperty(
199 local, QQmlJSRegisterContent::InvalidLookupIndex,
200 QQmlJSRegisterContent::InvalidLookupIndex,
201 QQmlJSRegisterContent::Property, QQmlJSRegisterContent()));
202}
203
204void QQmlJSTypePropagator::generate_StoreLocal(int index)
205{
206 Q_UNUSED(index)
208}
209
210void QQmlJSTypePropagator::generate_LoadScopedLocal(int scope, int index)
211{
212 Q_UNUSED(scope)
213 Q_UNUSED(index)
215}
216
217void QQmlJSTypePropagator::generate_StoreScopedLocal(int scope, int index)
218{
219 Q_UNUSED(scope)
220 Q_UNUSED(index)
222}
223
224void QQmlJSTypePropagator::generate_LoadRuntimeString(int stringId)
225{
226 Q_UNUSED(stringId)
227 setAccumulator(m_typeResolver->literalType(m_typeResolver->stringType()));
228}
229
230void QQmlJSTypePropagator::generate_MoveRegExp(int regExpId, int destReg)
231{
232 Q_UNUSED(regExpId)
233 m_state.setRegister(destReg, m_typeResolver->literalType(m_typeResolver->regexpType()));
234}
235
236void QQmlJSTypePropagator::generate_LoadClosure(int value)
237{
238 Q_UNUSED(value)
239 // TODO: Check the function at index and see whether it's a generator to return another type
240 // instead.
241 setAccumulator(m_typeResolver->literalType(m_typeResolver->functionType()));
242}
243
244void QQmlJSTypePropagator::generate_LoadName(int nameIndex)
245{
246 const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
247 setAccumulator(m_typeResolver->scopedType(m_function->qmlScope, name));
248 if (!m_state.accumulatorOut().isValid()) {
249 addError(u"Cannot find name "_s + name);
250 setVarAccumulatorAndError();
251 }
252}
253
254void QQmlJSTypePropagator::generate_LoadGlobalLookup(int index)
255{
256 generate_LoadName(m_jsUnitGenerator->lookupNameIndex(index));
257}
258
259void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isMethod) const
260{
261 Q_UNUSED(name);
262 Q_UNUSED(isMethod);
263}
264
265void QQmlJSTypePropagator::handleUnqualifiedAccessAndContextProperties(
266 const QString &name, bool isMethod) const
267{
268 Q_UNUSED(name);
269 Q_UNUSED(isMethod);
270}
271
272void QQmlJSTypePropagator::checkDeprecated(QQmlJSScope::ConstPtr scope, const QString &name,
273 bool isMethod) const
274{
275 Q_UNUSED(scope);
276 Q_UNUSED(name);
277 Q_UNUSED(isMethod);
278}
279
280bool QQmlJSTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope, const QString &name) const
281{
282 const auto property = scope->property(name);
283 return property.isValid();
284}
285
286void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index)
287{
288 // LoadQmlContextPropertyLookup does not use accumulatorIn. It always refers to the scope.
289 // Any import namespaces etc. are handled via LoadProperty or GetLookup.
290
291 const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index);
292 const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
293
294 setAccumulator(m_typeResolver->scopedType(m_function->qmlScope, name, index));
295
296 if (!m_state.accumulatorOut().isValid() && m_typeResolver->isPrefix(name)) {
297 setAccumulator(m_pool->createImportNamespace(
298 nameIndex, m_typeResolver->voidType(), QQmlJSRegisterContent::ModulePrefix,
299 m_function->qmlScope));
300 return;
301 }
302
303 checkDeprecated(m_function->qmlScope.containedType(), name, false);
304
305 const QQmlJSRegisterContent accumulatorOut = m_state.accumulatorOut();
306
307 if (!accumulatorOut.isValid()) {
308 addError(u"Cannot access value for name "_s + name);
309 handleUnqualifiedAccessAndContextProperties(name, false);
310 setVarAccumulatorAndError();
311 return;
312 }
313
314 const QQmlJSScope::ConstPtr retrieved
315 = m_typeResolver->genericType(accumulatorOut.containedType());
316
317 if (retrieved.isNull()) {
318 // It should really be valid.
319 // We get the generic type from aotContext->loadQmlContextPropertyIdLookup().
320 addError(u"Cannot determine generic type for "_s + name);
321 return;
322 }
323
324 if (accumulatorOut.variant() == QQmlJSRegisterContent::ObjectById
325 && !retrieved->isReferenceType()) {
326 addError(u"Cannot retrieve a non-object type by ID: "_s + name);
327 return;
328 }
329}
330
331/*!
332 \internal
333 As far as type propagation is involved, StoreNameSloppy and
334 StoreNameStrict are completely the same
335 StoreNameStrict is rejecting a few writes (where the variable was not
336 defined before) that would work in a sloppy context in JS, but the
337 compiler would always reject this. And for type propagation, this does
338 not matter at all.
339 \a nameIndex is the index in the string table corresponding to
340 the name which we are storing
341 */
342void QQmlJSTypePropagator::generate_StoreNameCommon(int nameIndex)
343{
344 const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
345 const QQmlJSRegisterContent type = m_typeResolver->scopedType(m_function->qmlScope, name);
346 const QQmlJSRegisterContent in = m_state.accumulatorIn();
347
348 if (!type.isValid()) {
349 handleUnqualifiedAccess(name, false);
350 addError(u"Cannot find name "_s + name);
351 return;
352 }
353
354 if (!type.isProperty()) {
355 QString message = type.isMethod() ? u"Cannot assign to method %1"_s
356 : u"Cannot assign to non-property %1"_s;
357 // The interpreter treats methods as read-only properties in its error messages
358 // and we lack a better fitting category. We might want to revisit this later.
359 m_logger->log(message.arg(name), qmlReadOnlyProperty,
360 currentSourceLocation());
361 addError(u"Cannot assign to non-property "_s + name);
362 return;
363 }
364
365 if (!type.isWritable() && !type.isList()) {
366 addError(u"Can't assign to read-only property %1"_s.arg(name));
367
368 m_logger->log(u"Cannot assign to read-only property %1"_s.arg(name), qmlReadOnlyProperty,
369 currentSourceLocation());
370
371 return;
372 }
373
374 if (!canConvertFromTo(in, type)) {
375 addError(u"cannot convert from %1 to %2"_s
376 .arg(in.descriptiveName(), type.descriptiveName()));
377 }
378
379 if (m_typeResolver->canHoldUndefined(in) && !m_typeResolver->canHoldUndefined(type)) {
380 if (in.contains(m_typeResolver->voidType()))
381 addReadAccumulator(m_typeResolver->varType());
382 else
383 addReadAccumulator();
384 } else {
385 addReadAccumulator(type);
386 }
387
388 m_state.setHasExternalSideEffects();
389}
390
391void QQmlJSTypePropagator::generate_StoreNameSloppy(int nameIndex)
392{
393 return generate_StoreNameCommon(nameIndex);
394}
395
396void QQmlJSTypePropagator::generate_StoreNameStrict(int name)
397{
398 return generate_StoreNameCommon(name);
399}
400
401bool QQmlJSTypePropagator::checkForEnumProblems(
402 QQmlJSRegisterContent base, const QString &propertyName)
403{
404 if (base.isEnumeration()) {
405 const auto metaEn = base.enumeration();
406 if (!metaEn.hasKey(propertyName)) {
407 addError(u"\"%1\" is not an entry of enum \"%2\"."_s
408 .arg(propertyName, metaEn.name()));
409 return true;
410 }
411 }
412
413 return false;
414}
415
416void QQmlJSTypePropagator::generate_LoadElement(int base)
417{
418 const QQmlJSRegisterContent in = m_state.accumulatorIn();
419 const QQmlJSRegisterContent baseRegister = m_state.registers[base].content;
420
421 const auto fallback = [&]() {
422 const QQmlJSScope::ConstPtr jsValue = m_typeResolver->jsValueType();
423
424 addReadAccumulator(jsValue);
425 addReadRegister(base, jsValue);
426
427 QQmlJSMetaProperty property;
428 property.setPropertyName(u"[]"_s);
429 property.setTypeName(jsValue->internalName());
430 property.setType(jsValue);
431
432 setAccumulator(m_pool->createProperty(
433 property, QQmlJSRegisterContent::InvalidLookupIndex,
434 QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::ListValue,
435 m_typeResolver->convert(m_typeResolver->elementType(baseRegister), jsValue)));
436 };
437
438 if (baseRegister.isList()) {
439 addReadRegister(base, m_typeResolver->arrayPrototype());
440 } else if (baseRegister.contains(m_typeResolver->stringType())) {
441 addReadRegister(base, m_typeResolver->stringType());
442 } else {
443 fallback();
444 return;
445 }
446
447 if (m_typeResolver->isNumeric(in)) {
448 const auto contained = in.containedType();
449 if (m_typeResolver->isSignedInteger(contained))
450 addReadAccumulator(m_typeResolver->sizeType());
451 else if (m_typeResolver->isUnsignedInteger(contained))
452 addReadAccumulator(m_typeResolver->uint32Type());
453 else
454 addReadAccumulator(m_typeResolver->realType());
455 } else if (m_typeResolver->isNumeric(m_typeResolver->extractNonVoidFromOptionalType(in))) {
456 addReadAccumulator();
457 } else {
458 fallback();
459 return;
460 }
461
462 // We can end up with undefined.
463 setAccumulator(m_typeResolver->merge(
464 m_typeResolver->elementType(baseRegister),
465 m_typeResolver->literalType(m_typeResolver->voidType())));
466}
467
468void QQmlJSTypePropagator::generate_StoreElement(int base, int index)
469{
470 const QQmlJSRegisterContent baseRegister = m_state.registers[base].content;
471 const QQmlJSRegisterContent indexRegister = checkedInputRegister(index);
472
473 if (!baseRegister.isList()
474 || !m_typeResolver->isNumeric(indexRegister)) {
475 const auto jsValue = m_typeResolver->jsValueType();
476 addReadAccumulator(jsValue);
477 addReadRegister(base, jsValue);
478 addReadRegister(index, jsValue);
479
480 // Writing to a JS array can have side effects all over the place since it's
481 // passed by reference.
482 m_state.setHasExternalSideEffects();
483 return;
484 }
485
486 const auto contained = indexRegister.containedType();
487 if (m_typeResolver->isSignedInteger(contained))
488 addReadRegister(index, m_typeResolver->int32Type());
489 else if (m_typeResolver->isUnsignedInteger(contained))
490 addReadRegister(index, m_typeResolver->uint32Type());
491 else
492 addReadRegister(index, m_typeResolver->realType());
493
494 addReadRegister(base, m_typeResolver->arrayPrototype());
495 addReadAccumulator(m_typeResolver->elementType(baseRegister));
496
497 // If we're writing a QQmlListProperty backed by a container somewhere else,
498 // that has side effects.
499 // If we're writing to a list retrieved from a property, that _should_ have side effects,
500 // but currently the QML engine doesn't implement them.
501 // TODO: Figure out the above and accurately set the flag.
502 m_state.setHasExternalSideEffects();
503}
504
505bool QQmlJSTypePropagator::handleImportNamespaceLookup(const QString &propertyName)
506{
507 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
508
509 if (m_typeResolver->isPrefix(propertyName)) {
510 Q_ASSERT(accumulatorIn.isValid());
511
512 if (!accumulatorIn.containedType()->isReferenceType()) {
513 setVarAccumulatorAndError();
514 return true;
515 }
516
517 addReadAccumulator();
518 setAccumulator(m_pool->createImportNamespace(
519 m_jsUnitGenerator->getStringId(propertyName),
520 accumulatorIn.containedType(),
521 QQmlJSRegisterContent::ModulePrefix,
522 accumulatorIn));
523 return true;
524 }
525
526 return false;
527}
528
529void QQmlJSTypePropagator::handleLookupError(const QString &propertyName)
530{
531 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
532
533 setVarAccumulatorAndError();
534 if (checkForEnumProblems(accumulatorIn, propertyName))
535 return;
536
537 addError(u"Cannot load property %1 from %2."_s
538 .arg(propertyName, accumulatorIn.descriptiveName()));
539}
540
541void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName, int lookupIndex)
542{
543 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
544 setAccumulator(
545 m_typeResolver->memberType(
546 accumulatorIn,
547 accumulatorIn.isImportNamespace()
548 ? m_jsUnitGenerator->stringForIndex(accumulatorIn.importNamespace())
549 + u'.' + propertyName
550 : propertyName, lookupIndex));
551
552 if (!m_state.accumulatorOut().isValid() && handleImportNamespaceLookup(propertyName))
553 return;
554
555 if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::Singleton
556 && accumulatorIn.variant() == QQmlJSRegisterContent::ModulePrefix
557 && !isQmlScopeObject(accumulatorIn.scope())) {
558 m_logger->log(
559 u"Cannot access singleton as a property of an object. Did you want to access an attached object?"_s,
560 qmlAccessSingleton, currentSourceLocation());
561 setAccumulator(QQmlJSRegisterContent());
562 } else if (m_state.accumulatorOut().isEnumeration()) {
563 switch (accumulatorIn.variant()) {
564 case QQmlJSRegisterContent::MetaType:
565 case QQmlJSRegisterContent::Attachment:
566 case QQmlJSRegisterContent::Enum:
567 case QQmlJSRegisterContent::ModulePrefix:
568 case QQmlJSRegisterContent::Singleton:
569 break; // OK, can look up enums on that thing
570 default:
571 setAccumulator(QQmlJSRegisterContent());
572 }
573 }
574
575 if (m_state.instructionHasError || !m_state.accumulatorOut().isValid()) {
576 handleLookupError(propertyName);
577 return;
578 }
579
580 if (m_state.accumulatorOut().isMethod() && m_state.accumulatorOut().method().size() != 1) {
581 addError(u"Cannot determine overloaded method on loadProperty"_s);
582 return;
583 }
584
585 if (m_state.accumulatorOut().isProperty()) {
586 const QQmlJSScope::ConstPtr mathObject
587 = m_typeResolver->jsGlobalObject()->property(u"Math"_s).type();
588 if (accumulatorIn.contains(mathObject)) {
589 QQmlJSMetaProperty prop;
590 prop.setPropertyName(propertyName);
591 prop.setTypeName(u"double"_s);
592 prop.setType(m_typeResolver->realType());
593 setAccumulator(
594 m_pool->createProperty(
595 prop, accumulatorIn.resultLookupIndex(), lookupIndex,
596 // Use pre-determined scope type here to avoid adjusting it later.
597 QQmlJSRegisterContent::Property, m_state.accumulatorOut().scope())
598 );
599
600 return;
601 }
602
603 if (m_state.accumulatorOut().contains(m_typeResolver->voidType())) {
604 addError(u"Type %1 does not have a property %2 for reading"_s
605 .arg(accumulatorIn.descriptiveName(), propertyName));
606 return;
607 }
608
609 if (!m_state.accumulatorOut().property().type()) {
610 m_logger->log(
611 QString::fromLatin1("Type of property \"%2\" not found").arg(propertyName),
612 qmlMissingType, currentSourceLocation());
613 }
614 }
615
616 switch (m_state.accumulatorOut().variant()) {
617 case QQmlJSRegisterContent::Enum:
618 case QQmlJSRegisterContent::Singleton:
619 // For reading enums or singletons, we don't need to access anything, unless it's an
620 // import namespace. Then we need the name.
621 if (accumulatorIn.isImportNamespace())
622 addReadAccumulator();
623 break;
624 default:
625 addReadAccumulator();
626 break;
627 }
628}
629
630void QQmlJSTypePropagator::generate_LoadProperty(int nameIndex)
631{
632 propagatePropertyLookup(m_jsUnitGenerator->stringForIndex(nameIndex));
633}
634
635void QQmlJSTypePropagator::generate_LoadOptionalProperty(int name, int offset)
636{
637 Q_UNUSED(name);
638 Q_UNUSED(offset);
640}
641
642void QQmlJSTypePropagator::generate_GetLookup(int index)
643{
644 propagatePropertyLookup(m_jsUnitGenerator->lookupName(index), index);
645}
646
647void QQmlJSTypePropagator::generate_GetOptionalLookup(int index, int offset)
648{
649 Q_UNUSED(offset);
650 saveRegisterStateForJump(offset);
651 propagatePropertyLookup(m_jsUnitGenerator->lookupName(index), index);
652}
653
654void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base)
655{
656 auto callBase = m_state.registers[base].content;
657 const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
658
659 QQmlJSRegisterContent property = m_typeResolver->memberType(callBase, propertyName);
660 if (!property.isProperty()) {
661 addError(u"Type %1 does not have a property %2 for writing"_s
662 .arg(callBase.descriptiveName(), propertyName));
663 return;
664 }
665
666 if (property.containedType().isNull()) {
667 addError(u"Cannot determine type for property %1 of type %2"_s.arg(
668 propertyName, callBase.descriptiveName()));
669 return;
670 }
671
672 if (!property.isWritable() && !property.containedType()->isListProperty()) {
673 addError(u"Can't assign to read-only property %1"_s.arg(propertyName));
674
675 m_logger->log(u"Cannot assign to read-only property %1"_s.arg(propertyName),
676 qmlReadOnlyProperty, currentSourceLocation());
677
678 return;
679 }
680
681 if (!canConvertFromTo(m_state.accumulatorIn(), property)) {
682 addError(u"cannot convert from %1 to %2"_s
683 .arg(m_state.accumulatorIn().descriptiveName(), property.descriptiveName()));
684 return;
685 }
686
687 // If the input can hold undefined we must not coerce it to the property type
688 // as that might eliminate an undefined value. For example, undefined -> string
689 // becomes "undefined".
690 // We need the undefined value for either resetting the property if that is supported
691 // or generating an exception otherwise. Therefore we explicitly require the value to
692 // be given as QVariant. This triggers the QVariant fallback path that's also used for
693 // shadowable properties. QVariant can hold undefined and the lookup functions will
694 // handle that appropriately.
695
696 const QQmlJSScope::ConstPtr varType = m_typeResolver->varType();
697 const QQmlJSRegisterContent readType = m_typeResolver->canHoldUndefined(m_state.accumulatorIn())
698 ? m_typeResolver->convert(property, varType)
699 : std::move(property);
700 addReadAccumulator(readType);
701 addReadRegister(base);
702 m_state.setHasExternalSideEffects();
703}
704
705void QQmlJSTypePropagator::generate_SetLookup(int index, int base)
706{
707 generate_StoreProperty(m_jsUnitGenerator->lookupNameIndex(index), base);
708}
709
710void QQmlJSTypePropagator::generate_LoadSuperProperty(int property)
711{
712 Q_UNUSED(property)
714}
715
716void QQmlJSTypePropagator::generate_StoreSuperProperty(int property)
717{
718 Q_UNUSED(property)
720}
721
722void QQmlJSTypePropagator::generate_Yield()
723{
725}
726
727void QQmlJSTypePropagator::generate_YieldStar()
728{
730}
731
732void QQmlJSTypePropagator::generate_Resume(int)
733{
735}
736
737void QQmlJSTypePropagator::generate_CallValue(int name, int argc, int argv)
738{
739 m_state.setHasExternalSideEffects();
740 Q_UNUSED(name)
741 Q_UNUSED(argc)
742 Q_UNUSED(argv)
744}
745
746void QQmlJSTypePropagator::generate_CallWithReceiver(int name, int thisObject, int argc, int argv)
747{
748 m_state.setHasExternalSideEffects();
749 Q_UNUSED(name)
750 Q_UNUSED(thisObject)
751 Q_UNUSED(argc)
752 Q_UNUSED(argv)
754}
755
756bool QQmlJSTypePropagator::isLoggingMethod(const QString &consoleMethod)
757{
758 return consoleMethod == u"log" || consoleMethod == u"debug" || consoleMethod == u"info"
759 || consoleMethod == u"warn" || consoleMethod == u"error";
760}
761
762void QQmlJSTypePropagator::generate_CallProperty_SCMath(
763 const QString &name, int base, int argc, int argv)
764{
765 // If we call a method on the Math object we don't need the actual Math object. We do need
766 // to transfer the type information to the code generator so that it knows that this is the
767 // Math object. Read the base register as void. void isn't stored, and the place where it's
768 // created will be optimized out if there are no other readers. The code generator can
769 // retrieve the original type and determine that it was the Math object.
770
771 addReadRegister(base, m_typeResolver->voidType());
772
773 QQmlJSRegisterContent math = m_state.registers[base].content;
774 const QList<QQmlJSMetaMethod> methods = math.containedType()->ownMethods(name);
775 if (methods.isEmpty()) {
776 setVarAccumulatorAndError();
777 std::optional<QQmlJSFixSuggestion> fixSuggestion = QQmlJSUtils::didYouMean(
778 name, math.containedType()->methods().keys(), m_logger->filePath(),
779 currentSourceLocation());
780 m_logger->log(u"Member \"%1\" not found on Math object"_s.arg(name),
781 qmlMissingProperty, currentSourceLocation(),
782 true, true, std::move(fixSuggestion));
783 return;
784 }
785 Q_ASSERT(methods.length() == 1);
786
787 // Declare the Math object as base type of itself so that it gets cloned and won't be
788 // adjusted later. This is what we do with all method calls.
789 QQmlJSRegisterContent realType = m_typeResolver->returnType(
790 methods[0], m_typeResolver->realType(),
791 m_typeResolver->baseType(math.containedType(), math));
792 for (int i = 0; i < argc; ++i)
793 addReadRegister(argv + i, realType);
794 setAccumulator(realType);
795}
796
797void QQmlJSTypePropagator::generate_CallProperty_SCconsole(
798 const QString &name, int base, int argc, int argv)
799{
800 // If we call a method on the console object we don't need the console object.
801 addReadRegister(base, m_typeResolver->voidType());
802
803 if (argc > 0) {
804 const QQmlJSRegisterContent firstContent = m_state.registers[argv].content;
805 const QQmlJSScope::ConstPtr firstArg = firstContent.containedType();
806 switch (firstArg->accessSemantics()) {
807 case QQmlJSScope::AccessSemantics::Reference:
808 // We cannot know whether this will be a logging category at run time.
809 // Therefore we always pass any object types as special last argument.
810 addReadRegister(argv, m_typeResolver->genericType(firstArg));
811 break;
812 case QQmlJSScope::AccessSemantics::Sequence:
813 addReadRegister(argv);
814 break;
815 default:
816 addReadRegister(argv, m_typeResolver->stringType());
817 break;
818 }
819 }
820
821 for (int i = 1; i < argc; ++i) {
822 const QQmlJSRegisterContent argContent = m_state.registers[argv + i].content;
823 const QQmlJSScope::ConstPtr arg = argContent.containedType();
824 if (arg->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence)
825 addReadRegister(argv + i);
826 else
827 addReadRegister(argv + i, m_typeResolver->stringType());
828 }
829
830 // It's debatable whether the console API should be considered an external side effect.
831 // You can certainly qInstallMessageHandler and then react to the message and change
832 // some property in an object exposed to the currently running method. However, we might
833 // disregard such a thing as abuse of the API. For now, the console API is considered to
834 // have external side effects, though.
835 m_state.setHasExternalSideEffects();
836
837 QQmlJSRegisterContent console = m_state.registers[base].content;
838 QList<QQmlJSMetaMethod> methods = console.containedType()->ownMethods(name);
839 Q_ASSERT(methods.length() == 1);
840
841 // Declare the console object as base type of itself so that it gets cloned and won't be
842 // adjusted later. This is what we do with all method calls.
843 setAccumulator(m_typeResolver->returnType(
844 methods[0], m_typeResolver->voidType(),
845 m_typeResolver->baseType(console.containedType(), console)));
846}
847
848void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int argc, int argv)
849{
850 Q_ASSERT(m_state.registers.contains(base));
851 const auto callBase = m_state.registers[base].content;
852 const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
853
854 if (callBase.contains(m_typeResolver->mathObject())) {
855 generate_CallProperty_SCMath(propertyName, base, argc, argv);
856 return;
857 }
858
859 if (callBase.contains(m_typeResolver->consoleObject()) && isLoggingMethod(propertyName)) {
860 generate_CallProperty_SCconsole(propertyName, base, argc, argv);
861 return;
862 }
863
864 const auto baseType = callBase.containedType();
865 const auto member = m_typeResolver->memberType(callBase, propertyName);
866
867 if (!member.isMethod()) {
868 if (callBase.contains(m_typeResolver->jsValueType())
869 || callBase.contains(m_typeResolver->varType())) {
870 const auto jsValueType = m_typeResolver->jsValueType();
871 addReadRegister(base, jsValueType);
872 for (int i = 0; i < argc; ++i)
873 addReadRegister(argv + i, jsValueType);
874 m_state.setHasExternalSideEffects();
875
876 QQmlJSMetaMethod method;
877 method.setIsJavaScriptFunction(true);
878 method.setMethodName(propertyName);
879 method.setMethodType(QQmlJSMetaMethod::MethodType::Method);
880
881 setAccumulator(m_typeResolver->returnType(
882 method, m_typeResolver->jsValueType(), callBase));
883 return;
884 }
885
886 setVarAccumulatorAndError();
887 addError(u"Type %1 does not have a property %2 for calling"_s
888 .arg(callBase.descriptiveName(), propertyName));
889
890 if (callBase.isType() && isCallingProperty(callBase.type(), propertyName))
891 return;
892
893 if (checkForEnumProblems(callBase, propertyName))
894 return;
895
896 std::optional<QQmlJSFixSuggestion> fixSuggestion;
897
898 if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->methods().keys(),
899 m_logger->filePath(), currentSourceLocation());
900 suggestion.has_value()) {
901 fixSuggestion = suggestion;
902 }
903
904 if (baseType->isFullyResolved() || baseType->isScript()) {
905 m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(
906 propertyName, callBase.containedTypeName()),
907 qmlMissingProperty, currentSourceLocation(), true, true, fixSuggestion);
908 }
909 return;
910 }
911
912 checkDeprecated(baseType, propertyName, true);
913
914 addReadRegister(base);
915
916 if (callBase.contains(m_typeResolver->stringType())) {
917 if (propertyName == u"arg"_s && argc == 1) {
918 propagateStringArgCall(callBase, argv);
919 return;
920 }
921 }
922
923 if (baseType->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
924 && member.scope().contains(m_typeResolver->arrayPrototype())
925 && propagateArrayMethod(propertyName, argc, argv, callBase)) {
926 return;
927 }
928
929 propagateCall(member.method(), argc, argv, member.scope());
930}
931
932QQmlJSMetaMethod QQmlJSTypePropagator::bestMatchForCall(const QList<QQmlJSMetaMethod> &methods,
933 int argc, int argv, QStringList *errors)
934{
935 QQmlJSMetaMethod javascriptFunction;
936 QQmlJSMetaMethod candidate;
937 bool hasMultipleCandidates = false;
938
939 for (const auto &method : methods) {
940
941 // If we encounter a JavaScript function, use this as a fallback if no other method matches
942 if (method.isJavaScriptFunction() && !javascriptFunction.isValid())
943 javascriptFunction = method;
944
945 if (method.returnType().isNull() && !method.returnTypeName().isEmpty()) {
946 errors->append(u"return type %1 cannot be resolved"_s
947 .arg(method.returnTypeName()));
948 continue;
949 }
950
951 const auto arguments = method.parameters();
952 if (argc != arguments.size()) {
953 errors->append(
954 u"Function expects %1 arguments, but %2 were provided"_s.arg(arguments.size())
955 .arg(argc));
956 continue;
957 }
958
959 bool fuzzyMatch = true;
960 bool exactMatch = true;
961 for (int i = 0; i < argc; ++i) {
962 const auto argumentType = arguments[i].type();
963 if (argumentType.isNull()) {
964 errors->append(
965 u"type %1 for argument %2 cannot be resolved"_s.arg(arguments[i].typeName())
966 .arg(i));
967 exactMatch = false;
968 fuzzyMatch = false;
969 break;
970 }
971
972 const auto content = m_state.registers[argv + i].content;
973 if (content.contains(argumentType))
974 continue;
975
976 exactMatch = false;
977 if (canConvertFromTo(content, argumentType))
978 continue;
979
980 // We can try to call a method that expects a derived type.
981 if (argumentType->isReferenceType()
982 && m_typeResolver->inherits(
983 argumentType->baseType(), content.containedType())) {
984 continue;
985 }
986
987 errors->append(
988 u"argument %1 contains %2 but is expected to contain the type %3"_s.arg(i).arg(
989 content.descriptiveName(), arguments[i].typeName()));
990 fuzzyMatch = false;
991 break;
992 }
993
994 if (exactMatch) {
995 return method;
996 } else if (fuzzyMatch) {
997 if (!candidate.isValid())
998 candidate = method;
999 else
1000 hasMultipleCandidates = true;
1001 }
1002 }
1003
1004 if (hasMultipleCandidates)
1005 return QQmlJSMetaMethod();
1006
1007 return candidate.isValid() ? candidate : javascriptFunction;
1008}
1009
1010void QQmlJSTypePropagator::setAccumulator(QQmlJSRegisterContent content)
1011{
1012 setRegister(Accumulator, content);
1013}
1014
1015void QQmlJSTypePropagator::setRegister(int index, QQmlJSRegisterContent content)
1016{
1017 // If we've come to the same conclusion before, let's not track the type again.
1018 auto it = m_prevStateAnnotations.find(currentInstructionOffset());
1019 if (it != m_prevStateAnnotations.end()) {
1020 QQmlJSRegisterContent lastTry = it->second.changedRegister;
1021 if (lastTry.contains(content.containedType())) {
1022 m_state.setRegister(index, lastTry);
1023 return;
1024 }
1025 }
1026
1027 m_state.setRegister(index, content);
1028}
1029
1030/*! \internal
1031 * Merges the types of two variations of the register at \index
1032 * When the code branches and merges, the same register can carry one of multiple types.
1033 * For example:
1034 *
1035 * let a
1036 * if (something)
1037 * a = 12
1038 * else
1039 * a = "stringstring"
1040 * console.log(a)
1041 *
1042 * At the point where we log the value we need a type that can hold both, the number and
1043 * the string because we don't know which branch was taken before. mergeRegister chooses a
1044 * type that can hold both variants.
1045 *
1046 * Since the type propagator can run multiple passes over the same code (for loops with back
1047 * jumps), we need to reproduce previous resolutions of the merge where they still fit. To that
1048 * effect, we check m_prevStateAnnotations here.
1049 */
1050void QQmlJSTypePropagator::mergeRegister(
1051 int index, const VirtualRegister &a, const VirtualRegister &b)
1052{
1053 const VirtualRegister merged = {
1054 (a.content == b.content) ? a.content : m_typeResolver->merge(a.content, b.content),
1055 a.canMove && b.canMove,
1056 a.affectedBySideEffects || b.affectedBySideEffects,
1057 a.isShadowable || b.isShadowable,
1058 };
1059
1060 Q_ASSERT(merged.content.isValid());
1061
1062 if (!merged.content.isConversion()) {
1063 // The registers were the same. We're already tracking them.
1064 m_state.annotations[currentInstructionOffset()].typeConversions[index] = merged;
1065 m_state.registers[index] = merged;
1066 return;
1067 }
1068
1069 auto tryPrevStateConversion = [this](int index, const VirtualRegister &merged) -> bool {
1070 auto it = m_prevStateAnnotations.find(currentInstructionOffset());
1071 if (it == m_prevStateAnnotations.end())
1072 return false;
1073
1074 auto conversion = it->second.typeConversions.find(index);
1075 if (conversion == it->second.typeConversions.end())
1076 return false;
1077
1078 const VirtualRegister &lastTry = conversion.value();
1079
1080 Q_ASSERT(lastTry.content.isValid());
1081 if (!lastTry.content.isConversion())
1082 return false;
1083
1084 if (lastTry.content.conversionResultType() != merged.content.conversionResultType()
1085 || lastTry.content.conversionOrigins() != merged.content.conversionOrigins()
1086 || lastTry.canMove != merged.canMove
1087 || lastTry.affectedBySideEffects != merged.affectedBySideEffects
1088 || lastTry.isShadowable != merged.isShadowable) {
1089 return false;
1090 }
1091
1092 // We don't need to track it again if we've come to the same conclusion before.
1093 m_state.annotations[currentInstructionOffset()].typeConversions[index] = lastTry;
1094
1095 // Do not reset the side effects
1096 Q_ASSERT(!m_state.registers[index].affectedBySideEffects || lastTry.affectedBySideEffects);
1097
1098 m_state.registers[index] = lastTry;
1099 return true;
1100 };
1101
1102 if (!tryPrevStateConversion(index, merged)) {
1103 // if a != b, we have already re-tracked it.
1104 const VirtualRegister cloned = {
1105 (a == b) ? m_pool->clone(merged.content) : merged.content,
1106 merged.canMove,
1107 merged.affectedBySideEffects,
1108 merged.isShadowable,
1109 };
1110 Q_ASSERT(cloned.content.isValid());
1111 m_state.annotations[currentInstructionOffset()].typeConversions[index] = cloned;
1112 m_state.registers[index] = cloned;
1113 }
1114}
1115
1116void QQmlJSTypePropagator::addReadRegister(int index)
1117{
1118 // Explicitly pass the same type through without conversion
1119 m_state.addReadRegister(index, m_state.registers[index].content);
1120}
1121
1122void QQmlJSTypePropagator::addReadRegister(int index, QQmlJSRegisterContent convertTo)
1123{
1124 if (m_state.registers[index].content == convertTo) {
1125 // Explicitly pass the same type through without conversion
1126 m_state.addReadRegister(index, convertTo);
1127 } else {
1128 m_state.addReadRegister(
1129 index, m_typeResolver->convert(m_state.registers[index].content, convertTo));
1130 }
1131}
1132
1133void QQmlJSTypePropagator::addReadRegister(int index, const QQmlJSScope::ConstPtr &convertTo)
1134{
1135 m_state.addReadRegister(
1136 index, m_typeResolver->convert(m_state.registers[index].content, convertTo));
1137}
1138
1139void QQmlJSTypePropagator::propagateCall(
1140 const QList<QQmlJSMetaMethod> &methods, int argc, int argv,
1141 QQmlJSRegisterContent scope)
1142{
1143 QStringList errors;
1144 const QQmlJSMetaMethod match = bestMatchForCall(methods, argc, argv, &errors);
1145
1146 if (!match.isValid()) {
1147 setVarAccumulatorAndError();
1148 if (methods.size() == 1) {
1149 // Cannot have multiple fuzzy matches if there is only one method
1150 Q_ASSERT(errors.size() == 1);
1151 addError(errors.first());
1152 } else if (errors.size() < methods.size()) {
1153 addError(u"Multiple matching overrides found. Cannot determine the right one."_s);
1154 } else {
1155 addError(u"No matching override found. Candidates:\n"_s + errors.join(u'\n'));
1156 }
1157 return;
1158 }
1159
1160 QQmlJSScope::ConstPtr returnType;
1161 if (match.isJavaScriptFunction())
1162 returnType = m_typeResolver->jsValueType();
1163 else if (match.isConstructor())
1164 returnType = scope.containedType();
1165 else
1166 returnType = match.returnType();
1167
1168 setAccumulator(m_typeResolver->returnType(match, returnType, scope));
1169 if (!m_state.accumulatorOut().isValid())
1170 addError(u"Cannot store return type of method %1()."_s.arg(match.methodName()));
1171
1172 const auto types = match.parameters();
1173 for (int i = 0; i < argc; ++i) {
1174 if (i < types.size()) {
1175 const QQmlJSScope::ConstPtr type = match.isJavaScriptFunction()
1176 ? m_typeResolver->jsValueType()
1177 : QQmlJSScope::ConstPtr(types.at(i).type());
1178 if (!type.isNull()) {
1179 addReadRegister(argv + i, type);
1180 continue;
1181 }
1182 }
1183 addReadRegister(argv + i, m_typeResolver->jsValueType());
1184 }
1185 m_state.setHasExternalSideEffects();
1186}
1187
1188void QQmlJSTypePropagator::propagateTranslationMethod_SAcheck(const QString &methodName)
1189{
1190 Q_UNUSED(methodName);
1191}
1192
1193bool QQmlJSTypePropagator::propagateTranslationMethod(
1194 const QList<QQmlJSMetaMethod> &methods, int argc, int argv)
1195{
1196 if (methods.size() != 1)
1197 return false;
1198
1199 const QQmlJSMetaMethod method = methods.front();
1200 const QQmlJSScope::ConstPtr intType = m_typeResolver->int32Type();
1201 const QQmlJSScope::ConstPtr stringType = m_typeResolver->stringType();
1202
1203 const QQmlJSRegisterContent returnType = m_typeResolver->returnType(
1204 method, m_typeResolver->stringType(), m_typeResolver->jsGlobalObjectContent());
1205
1206 if (method.methodName() == u"qsTranslate"_s) {
1207 switch (argc) {
1208 case 4:
1209 addReadRegister(argv + 3, intType); // n
1210 Q_FALLTHROUGH();
1211 case 3:
1212 addReadRegister(argv + 2, stringType); // disambiguation
1213 Q_FALLTHROUGH();
1214 case 2:
1215 addReadRegister(argv + 1, stringType); // sourceText
1216 addReadRegister(argv, stringType); // context
1217 setAccumulator(returnType);
1218 propagateTranslationMethod_SAcheck(method.methodName());
1219 return true;
1220 default:
1221 return false;
1222 }
1223 }
1224
1225 if (method.methodName() == u"QT_TRANSLATE_NOOP"_s) {
1226 switch (argc) {
1227 case 3:
1228 addReadRegister(argv + 2, stringType); // disambiguation
1229 Q_FALLTHROUGH();
1230 case 2:
1231 addReadRegister(argv + 1, stringType); // sourceText
1232 addReadRegister(argv, stringType); // context
1233 setAccumulator(returnType);
1234 propagateTranslationMethod_SAcheck(method.methodName());
1235 return true;
1236 default:
1237 return false;
1238 }
1239 }
1240
1241 if (method.methodName() == u"qsTr"_s) {
1242 switch (argc) {
1243 case 3:
1244 addReadRegister(argv + 2, intType); // n
1245 Q_FALLTHROUGH();
1246 case 2:
1247 addReadRegister(argv + 1, stringType); // disambiguation
1248 Q_FALLTHROUGH();
1249 case 1:
1250 addReadRegister(argv, stringType); // sourceText
1251 setAccumulator(returnType);
1252 propagateTranslationMethod_SAcheck(method.methodName());
1253 return true;
1254 default:
1255 return false;
1256 }
1257 }
1258
1259 if (method.methodName() == u"QT_TR_NOOP"_s) {
1260 switch (argc) {
1261 case 2:
1262 addReadRegister(argv + 1, stringType); // disambiguation
1263 Q_FALLTHROUGH();
1264 case 1:
1265 addReadRegister(argv, stringType); // sourceText
1266 setAccumulator(returnType);
1267 propagateTranslationMethod_SAcheck(method.methodName());
1268 return true;
1269 default:
1270 return false;
1271 }
1272 }
1273
1274 if (method.methodName() == u"qsTrId"_s) {
1275 switch (argc) {
1276 case 2:
1277 addReadRegister(argv + 1, intType); // n
1278 Q_FALLTHROUGH();
1279 case 1:
1280 addReadRegister(argv, stringType); // id
1281 setAccumulator(returnType);
1282 propagateTranslationMethod_SAcheck(method.methodName());
1283 return true;
1284 default:
1285 return false;
1286 }
1287 }
1288
1289 if (method.methodName() == u"QT_TRID_NOOP"_s) {
1290 switch (argc) {
1291 case 1:
1292 addReadRegister(argv, stringType); // id
1293 setAccumulator(returnType);
1294 propagateTranslationMethod_SAcheck(method.methodName());
1295 return true;
1296 default:
1297 return false;
1298 }
1299 }
1300
1301 return false;
1302}
1303
1304void QQmlJSTypePropagator::propagateStringArgCall(QQmlJSRegisterContent base, int argv)
1305{
1306 QQmlJSMetaMethod method;
1307 method.setIsJavaScriptFunction(true);
1308 method.setMethodName(u"arg"_s);
1309 setAccumulator(m_typeResolver->returnType(method, m_typeResolver->stringType(), base));
1310 Q_ASSERT(m_state.accumulatorOut().isValid());
1311
1312 const QQmlJSScope::ConstPtr input = m_state.registers[argv].content.containedType();
1313
1314 if (input == m_typeResolver->uint32Type()
1315 || input == m_typeResolver->int64Type()
1316 || input == m_typeResolver->uint64Type()) {
1317 addReadRegister(argv, m_typeResolver->realType());
1318 return;
1319 }
1320
1321 if (m_typeResolver->isIntegral(input)) {
1322 addReadRegister(argv, m_typeResolver->int32Type());
1323 return;
1324 }
1325
1326 if (m_typeResolver->isNumeric(input)) {
1327 addReadRegister(argv, m_typeResolver->realType());
1328 return;
1329 }
1330
1331 if (input == m_typeResolver->boolType()) {
1332 addReadRegister(argv, m_typeResolver->boolType());
1333 return;
1334 }
1335
1336 addReadRegister(argv, m_typeResolver->stringType());
1337}
1338
1339bool QQmlJSTypePropagator::propagateArrayMethod(
1340 const QString &name, int argc, int argv, QQmlJSRegisterContent baseType)
1341{
1342 // TODO:
1343 // * For concat() we need to decide what kind of array to return and what kinds of arguments to
1344 // accept.
1345 // * For entries(), keys(), and values() we need iterators.
1346 // * For find(), findIndex(), sort(), every(), some(), forEach(), map(), filter(), reduce(),
1347 // and reduceRight() we need typed function pointers.
1348
1349 // TODO:
1350 // For now, every method that mutates the original array is considered to have external
1351 // side effects. We could do better by figuring out whether the array is actually backed
1352 // by an external property or has entries backed by an external property. If not, there
1353 // can't be any external side effects.
1354
1355 const auto intType = m_typeResolver->int32Type();
1356 const auto stringType = m_typeResolver->stringType();
1357 const auto baseContained = baseType.containedType();
1358 const auto elementContained = baseContained->elementType();
1359
1360 const auto setReturnType = [&](const QQmlJSScope::ConstPtr type) {
1361 QQmlJSMetaMethod method;
1362 method.setIsJavaScriptFunction(true);
1363 method.setMethodName(name);
1364 setAccumulator(m_typeResolver->returnType(method, type, baseType));
1365 };
1366
1367 if (name == u"copyWithin" && argc > 0 && argc < 4) {
1368 for (int i = 0; i < argc; ++i) {
1369 if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
1370 return false;
1371 }
1372
1373 for (int i = 0; i < argc; ++i)
1374 addReadRegister(argv + i, intType);
1375
1376 m_state.setHasExternalSideEffects();
1377 setReturnType(baseContained);
1378 return true;
1379 }
1380
1381 if (name == u"fill" && argc > 0 && argc < 4) {
1382 if (!canConvertFromTo(m_state.registers[argv].content, elementContained))
1383 return false;
1384
1385 for (int i = 1; i < argc; ++i) {
1386 if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
1387 return false;
1388 }
1389
1390 addReadRegister(argv, elementContained);
1391
1392 for (int i = 1; i < argc; ++i)
1393 addReadRegister(argv + i, intType);
1394
1395 m_state.setHasExternalSideEffects();
1396 setReturnType(baseContained);
1397 return true;
1398 }
1399
1400 if (name == u"includes" && argc > 0 && argc < 3) {
1401 if (!canConvertFromTo(m_state.registers[argv].content, elementContained))
1402 return false;
1403
1404 if (argc == 2) {
1405 if (!canConvertFromTo(m_state.registers[argv + 1].content, intType))
1406 return false;
1407 addReadRegister(argv + 1, intType);
1408 }
1409
1410 addReadRegister(argv, elementContained);
1411 setReturnType(m_typeResolver->boolType());
1412 return true;
1413 }
1414
1415 if (name == u"toString" || (name == u"join" && argc < 2)) {
1416 if (argc == 1) {
1417 if (!canConvertFromTo(m_state.registers[argv].content, stringType))
1418 return false;
1419 addReadRegister(argv, stringType);
1420 }
1421
1422 setReturnType(m_typeResolver->stringType());
1423 return true;
1424 }
1425
1426 if ((name == u"pop" || name == u"shift") && argc == 0) {
1427 m_state.setHasExternalSideEffects();
1428 setReturnType(elementContained);
1429 return true;
1430 }
1431
1432 if (name == u"push" || name == u"unshift") {
1433 for (int i = 0; i < argc; ++i) {
1434 if (!canConvertFromTo(m_state.registers[argv + i].content, elementContained))
1435 return false;
1436 }
1437
1438 for (int i = 0; i < argc; ++i)
1439 addReadRegister(argv + i, elementContained);
1440
1441 m_state.setHasExternalSideEffects();
1442 setReturnType(m_typeResolver->int32Type());
1443 return true;
1444 }
1445
1446 if (name == u"reverse" && argc == 0) {
1447 m_state.setHasExternalSideEffects();
1448 setReturnType(baseContained);
1449 return true;
1450 }
1451
1452 if (name == u"slice" && argc < 3) {
1453 for (int i = 0; i < argc; ++i) {
1454 if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
1455 return false;
1456 }
1457
1458 for (int i = 0; i < argc; ++i)
1459 addReadRegister(argv + i, intType);
1460
1461 setReturnType(baseType.containedType()->isListProperty()
1462 ? m_typeResolver->qObjectListType()
1463 : baseContained);
1464 return true;
1465 }
1466
1467 if (name == u"splice" && argc > 0) {
1468 const int startAndDeleteCount = std::min(argc, 2);
1469 for (int i = 0; i < startAndDeleteCount; ++i) {
1470 if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
1471 return false;
1472 }
1473
1474 for (int i = 2; i < argc; ++i) {
1475 if (!canConvertFromTo(m_state.registers[argv + i].content, elementContained))
1476 return false;
1477 }
1478
1479 for (int i = 0; i < startAndDeleteCount; ++i)
1480 addReadRegister(argv + i, intType);
1481
1482 for (int i = 2; i < argc; ++i)
1483 addReadRegister(argv + i, elementContained);
1484
1485 m_state.setHasExternalSideEffects();
1486 setReturnType(baseContained);
1487 return true;
1488 }
1489
1490 if ((name == u"indexOf" || name == u"lastIndexOf") && argc > 0 && argc < 3) {
1491 if (!canConvertFromTo(m_state.registers[argv].content, elementContained))
1492 return false;
1493
1494 if (argc == 2) {
1495 if (!canConvertFromTo(m_state.registers[argv + 1].content, intType))
1496 return false;
1497 addReadRegister(argv + 1, intType);
1498 }
1499
1500 addReadRegister(argv, elementContained);
1501 setReturnType(m_typeResolver->int32Type());
1502 return true;
1503 }
1504
1505 return false;
1506}
1507
1508void QQmlJSTypePropagator::generate_CallPropertyLookup(int lookupIndex, int base, int argc,
1509 int argv)
1510{
1511 generate_CallProperty(m_jsUnitGenerator->lookupNameIndex(lookupIndex), base, argc, argv);
1512}
1513
1514void QQmlJSTypePropagator::generate_CallName(int name, int argc, int argv)
1515{
1516 propagateScopeLookupCall(m_jsUnitGenerator->stringForIndex(name), argc, argv);
1517}
1518
1519void QQmlJSTypePropagator::generate_CallPossiblyDirectEval(int argc, int argv)
1520{
1521 m_state.setHasExternalSideEffects();
1522 Q_UNUSED(argc)
1523 Q_UNUSED(argv)
1524
1526}
1527
1528void QQmlJSTypePropagator::propagateScopeLookupCall(const QString &functionName, int argc, int argv)
1529{
1530 const QQmlJSRegisterContent resolvedContent
1531 = m_typeResolver->scopedType(m_function->qmlScope, functionName);
1532 if (resolvedContent.isMethod()) {
1533 const auto methods = resolvedContent.method();
1534 if (resolvedContent.scope().contains(m_typeResolver->jsGlobalObject())) {
1535 if (propagateTranslationMethod(methods, argc, argv))
1536 return;
1537 }
1538
1539 if (!methods.isEmpty()) {
1540 propagateCall(methods, argc, argv, resolvedContent.scope());
1541 return;
1542 }
1543 }
1544
1545 addError(u"method %1 cannot be resolved."_s.arg(functionName));
1546 const auto jsValue = m_typeResolver->jsValueType();
1547 QQmlJSMetaMethod method;
1548 method.setMethodName(functionName);
1549 method.setIsJavaScriptFunction(true);
1550 setAccumulator(m_typeResolver->returnType(method, jsValue, m_function->qmlScope));
1551
1552 addError(u"Cannot find function '%1'"_s.arg(functionName));
1553
1554 handleUnqualifiedAccessAndContextProperties(functionName, true);
1555}
1556
1557void QQmlJSTypePropagator::generate_CallGlobalLookup(int index, int argc, int argv)
1558{
1559 propagateScopeLookupCall(m_jsUnitGenerator->lookupName(index), argc, argv);
1560}
1561
1562void QQmlJSTypePropagator::generate_CallQmlContextPropertyLookup(int index, int argc, int argv)
1563{
1564 const QString name = m_jsUnitGenerator->lookupName(index);
1565 propagateScopeLookupCall(name, argc, argv);
1566 checkDeprecated(m_function->qmlScope.containedType(), name, true);
1567}
1568
1569void QQmlJSTypePropagator::generate_CallWithSpread(int func, int thisObject, int argc, int argv)
1570{
1571 m_state.setHasExternalSideEffects();
1572 Q_UNUSED(func)
1573 Q_UNUSED(thisObject)
1574 Q_UNUSED(argc)
1575 Q_UNUSED(argv)
1577}
1578
1579void QQmlJSTypePropagator::generate_TailCall(int func, int thisObject, int argc, int argv)
1580{
1581 m_state.setHasExternalSideEffects();
1582 Q_UNUSED(func)
1583 Q_UNUSED(thisObject)
1584 Q_UNUSED(argc)
1585 Q_UNUSED(argv)
1587}
1588
1589void QQmlJSTypePropagator::generate_Construct_SCDate(
1590 const QQmlJSMetaMethod &ctor, int argc, int argv)
1591{
1592 setAccumulator(m_typeResolver->returnType(ctor, m_typeResolver->dateTimeType(), {}));
1593
1594 if (argc == 1) {
1595 const QQmlJSRegisterContent argType = m_state.registers[argv].content;
1596 if (m_typeResolver->isNumeric(argType)) {
1597 addReadRegister(argv, m_typeResolver->realType());
1598 } else if (argType.contains(m_typeResolver->stringType())) {
1599 addReadRegister(argv, m_typeResolver->stringType());
1600 } else if (argType.contains(m_typeResolver->dateTimeType())
1601 || argType.contains(m_typeResolver->dateType())
1602 || argType.contains(m_typeResolver->timeType())) {
1603 addReadRegister(argv, m_typeResolver->dateTimeType());
1604 } else {
1605 addReadRegister(argv, m_typeResolver->jsPrimitiveType());
1606 }
1607 } else {
1608 constexpr int maxArgc = 7; // year, month, day, hours, minutes, seconds, milliseconds
1609 for (int i = 0; i < std::min(argc, maxArgc); ++i)
1610 addReadRegister(argv + i, m_typeResolver->realType());
1611 }
1612}
1613
1614void QQmlJSTypePropagator::generate_Construct_SCArray(
1615 const QQmlJSMetaMethod &ctor, int argc, int argv)
1616{
1617 if (argc == 1) {
1618 if (m_typeResolver->isNumeric(m_state.registers[argv].content)) {
1619 setAccumulator(m_typeResolver->returnType(ctor, m_typeResolver->variantListType(), {}));
1620 addReadRegister(argv, m_typeResolver->realType());
1621 } else {
1622 generate_DefineArray(argc, argv);
1623 }
1624 } else {
1625 generate_DefineArray(argc, argv);
1626 }
1627}
1628void QQmlJSTypePropagator::generate_Construct(int func, int argc, int argv)
1629{
1630 const QQmlJSRegisterContent type = m_state.registers[func].content;
1631 if (type.contains(m_typeResolver->metaObjectType())) {
1632 const QQmlJSRegisterContent valueType = type.scope();
1633 const QQmlJSScope::ConstPtr contained = type.scopeType();
1634 if (contained->isValueType() && contained->isCreatable()) {
1635 const auto extension = contained->extensionType();
1636 if (extension.extensionSpecifier == QQmlJSScope::ExtensionType) {
1637 propagateCall(
1638 extension.scope->ownMethods(extension.scope->internalName()),
1639 argc, argv, valueType);
1640 } else {
1641 propagateCall(
1642 contained->ownMethods(contained->internalName()), argc, argv, valueType);
1643 }
1644 return;
1645 }
1646 }
1647
1648 if (!type.isMethod()) {
1649 m_state.setHasExternalSideEffects();
1650 QQmlJSMetaMethod method;
1651 method.setMethodName(type.containedTypeName());
1652 method.setIsJavaScriptFunction(true);
1653 method.setIsConstructor(true);
1654 setAccumulator(m_typeResolver->returnType(method, m_typeResolver->jsValueType(), {}));
1655 return;
1656 }
1657
1658 if (const auto methods = type.method();
1659 methods == m_typeResolver->jsGlobalObject()->methods(u"Date"_s)) {
1660 Q_ASSERT(methods.length() == 1);
1661 generate_Construct_SCDate(methods[0], argc, argv);
1662 return;
1663 }
1664
1665 if (const auto methods = type.method();
1666 methods == m_typeResolver->jsGlobalObject()->methods(u"Array"_s)) {
1667 Q_ASSERT(methods.length() == 1);
1668 generate_Construct_SCArray(methods[0], argc, argv);
1669 return;
1670 }
1671
1672 m_state.setHasExternalSideEffects();
1673
1674 QStringList errors;
1675 QQmlJSMetaMethod match = bestMatchForCall(type.method(), argc, argv, &errors);
1676 if (!match.isValid())
1677 addError(u"Cannot determine matching constructor. Candidates:\n"_s + errors.join(u'\n'));
1678 setAccumulator(m_typeResolver->returnType(match, m_typeResolver->jsValueType(), {}));
1679}
1680
1681void QQmlJSTypePropagator::generate_ConstructWithSpread(int func, int argc, int argv)
1682{
1683 m_state.setHasExternalSideEffects();
1684 Q_UNUSED(func)
1685 Q_UNUSED(argc)
1686 Q_UNUSED(argv)
1688}
1689
1690void QQmlJSTypePropagator::generate_SetUnwindHandler(int offset)
1691{
1692 m_state.setHasInternalSideEffects();
1693 Q_UNUSED(offset)
1695}
1696
1697void QQmlJSTypePropagator::generate_UnwindDispatch()
1698{
1699 m_state.setHasInternalSideEffects();
1701}
1702
1703void QQmlJSTypePropagator::generate_UnwindToLabel(int level, int offset)
1704{
1705 m_state.setHasInternalSideEffects();
1706 Q_UNUSED(level)
1707 Q_UNUSED(offset)
1709}
1710
1711void QQmlJSTypePropagator::generate_DeadTemporalZoneCheck(int name)
1712{
1713 const auto fail = [this, name]() {
1714 addError(u"Cannot statically assert the dead temporal zone check for %1"_s.arg(
1715 name ? m_jsUnitGenerator->stringForIndex(name) : u"the anonymous accumulator"_s));
1716 };
1717
1718 const QQmlJSRegisterContent in = m_state.accumulatorIn();
1719 if (in.isConversion()) {
1720 const auto &inConversionOrigins = in.conversionOrigins();
1721 for (QQmlJSRegisterContent origin : inConversionOrigins) {
1722 if (!origin.contains(m_typeResolver->emptyType()))
1723 continue;
1724 fail();
1725 break;
1726 }
1727 } else if (in.contains(m_typeResolver->emptyType())) {
1728 fail();
1729 }
1730}
1731
1732void QQmlJSTypePropagator::generate_ThrowException()
1733{
1734 addReadAccumulator(m_typeResolver->jsValueType());
1735 m_state.setHasInternalSideEffects();
1736 m_state.skipInstructionsUntilNextJumpTarget = true;
1737}
1738
1739void QQmlJSTypePropagator::generate_GetException()
1740{
1742}
1743
1744void QQmlJSTypePropagator::generate_SetException()
1745{
1746 m_state.setHasInternalSideEffects();
1748}
1749
1750void QQmlJSTypePropagator::generate_CreateCallContext()
1751{
1752 m_state.setHasInternalSideEffects();
1753}
1754
1755void QQmlJSTypePropagator::generate_PushCatchContext(int index, int name)
1756{
1757 m_state.setHasInternalSideEffects();
1758 Q_UNUSED(index)
1759 Q_UNUSED(name)
1761}
1762
1763void QQmlJSTypePropagator::generate_PushWithContext()
1764{
1765 m_state.setHasInternalSideEffects();
1767}
1768
1769void QQmlJSTypePropagator::generate_PushBlockContext(int index)
1770{
1771 m_state.setHasInternalSideEffects();
1772 Q_UNUSED(index)
1774}
1775
1776void QQmlJSTypePropagator::generate_CloneBlockContext()
1777{
1778 m_state.setHasInternalSideEffects();
1780}
1781
1782void QQmlJSTypePropagator::generate_PushScriptContext(int index)
1783{
1784 m_state.setHasInternalSideEffects();
1785 Q_UNUSED(index)
1787}
1788
1789void QQmlJSTypePropagator::generate_PopScriptContext()
1790{
1791 m_state.setHasInternalSideEffects();
1793}
1794
1795void QQmlJSTypePropagator::generate_PopContext()
1796{
1797 m_state.setHasInternalSideEffects();
1798}
1799
1800void QQmlJSTypePropagator::generate_GetIterator(int iterator)
1801{
1802 const QQmlJSRegisterContent listType = m_state.accumulatorIn();
1803 if (!listType.isList()) {
1804 const QQmlJSScope::ConstPtr jsValue = m_typeResolver->jsValueType();
1805 addReadAccumulator(jsValue);
1806
1807 QQmlJSMetaProperty prop;
1808 prop.setPropertyName(u"<>"_s);
1809 prop.setTypeName(jsValue->internalName());
1810 prop.setType(jsValue);
1811 setAccumulator(m_pool->createProperty(
1812 prop, currentInstructionOffset(),
1813 QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::ListIterator,
1814 listType));
1815 return;
1816 }
1817
1818 addReadAccumulator();
1819 setAccumulator(m_typeResolver->iteratorPointer(
1820 listType, QQmlJS::AST::ForEachType(iterator), currentInstructionOffset()));
1821}
1822
1823void QQmlJSTypePropagator::generate_IteratorNext(int value, int offset)
1824{
1825 const QQmlJSRegisterContent iteratorType = m_state.accumulatorIn();
1826 addReadAccumulator();
1827 setRegister(value, m_typeResolver->merge(
1828 m_typeResolver->elementType(iteratorType),
1829 m_typeResolver->literalType(m_typeResolver->voidType())));
1830 saveRegisterStateForJump(offset);
1831 m_state.setHasInternalSideEffects();
1832}
1833
1834void QQmlJSTypePropagator::generate_IteratorNextForYieldStar(int iterator, int object, int offset)
1835{
1836 Q_UNUSED(iterator)
1837 Q_UNUSED(object)
1838 Q_UNUSED(offset)
1840}
1841
1842void QQmlJSTypePropagator::generate_IteratorClose()
1843{
1844 // Noop
1845}
1846
1847void QQmlJSTypePropagator::generate_DestructureRestElement()
1848{
1850}
1851
1852void QQmlJSTypePropagator::generate_DeleteProperty(int base, int index)
1853{
1854 Q_UNUSED(base)
1855 Q_UNUSED(index)
1857}
1858
1859void QQmlJSTypePropagator::generate_DeleteName(int name)
1860{
1861 Q_UNUSED(name)
1863}
1864
1865void QQmlJSTypePropagator::generate_TypeofName(int name)
1866{
1867 Q_UNUSED(name);
1868 setAccumulator(m_typeResolver->operationType(m_typeResolver->stringType()));
1869}
1870
1871void QQmlJSTypePropagator::generate_TypeofValue()
1872{
1873 setAccumulator(m_typeResolver->operationType(m_typeResolver->stringType()));
1874}
1875
1876void QQmlJSTypePropagator::generate_DeclareVar(int varName, int isDeletable)
1877{
1878 Q_UNUSED(varName)
1879 Q_UNUSED(isDeletable)
1881}
1882
1883void QQmlJSTypePropagator::generate_DefineArray(int argc, int args)
1884{
1885 setAccumulator(m_typeResolver->operationType(m_typeResolver->variantListType()));
1886
1887 // Track all arguments as the same type.
1888 const QQmlJSScope::ConstPtr elementType = m_typeResolver->varType();
1889 for (int i = 0; i < argc; ++i)
1890 addReadRegister(args + i, elementType);
1891}
1892
1893void QQmlJSTypePropagator::generate_DefineObjectLiteral(int internalClassId, int argc, int args)
1894{
1895 const int classSize = m_jsUnitGenerator->jsClassSize(internalClassId);
1896 Q_ASSERT(argc >= classSize);
1897
1898 // Track each element as separate type
1899 for (int i = 0; i < classSize; ++i)
1900 addReadRegister(args + i, m_typeResolver->varType());
1901
1902 for (int i = classSize; i < argc; i += 3) {
1903 // layout for remaining members is:
1904 // 0: ObjectLiteralArgument - Value|Method|Getter|Setter
1905 // We cannot do anything useful with this. Any code that would call a getter/setter/method
1906 // could not be compiled to C++. Ignore it.
1907
1908 // 1: name of argument
1909 addReadRegister(args + i + 1, m_typeResolver->stringType());
1910
1911 // 2: value of argument
1912 addReadRegister(args + i + 2, m_typeResolver->varType());
1913 }
1914
1915 setAccumulator(m_typeResolver->operationType(m_typeResolver->variantMapType()));
1916}
1917
1918void QQmlJSTypePropagator::generate_CreateClass(int classIndex, int heritage, int computedNames)
1919{
1920 Q_UNUSED(classIndex)
1921 Q_UNUSED(heritage)
1922 Q_UNUSED(computedNames)
1924}
1925
1926void QQmlJSTypePropagator::generate_CreateMappedArgumentsObject()
1927{
1929}
1930
1931void QQmlJSTypePropagator::generate_CreateUnmappedArgumentsObject()
1932{
1934}
1935
1936void QQmlJSTypePropagator::generate_CreateRestParameter(int argIndex)
1937{
1938 Q_UNUSED(argIndex)
1940}
1941
1942void QQmlJSTypePropagator::generate_ConvertThisToObject()
1943{
1944 setRegister(This, m_pool->clone(m_function->qmlScope));
1945}
1946
1947void QQmlJSTypePropagator::generate_LoadSuperConstructor()
1948{
1950}
1951
1952void QQmlJSTypePropagator::generate_ToObject()
1953{
1955}
1956
1957void QQmlJSTypePropagator::generate_Jump(int offset)
1958{
1959 saveRegisterStateForJump(offset);
1960 m_state.skipInstructionsUntilNextJumpTarget = true;
1961 m_state.setHasInternalSideEffects();
1962}
1963
1964void QQmlJSTypePropagator::generate_JumpTrue(int offset)
1965{
1966 if (!canConvertFromTo(m_state.accumulatorIn(), m_typeResolver->boolType())) {
1967 addError(u"cannot convert from %1 to boolean"_s
1968 .arg(m_state.accumulatorIn().descriptiveName()));
1969 return;
1970 }
1971 saveRegisterStateForJump(offset);
1972 addReadAccumulator(m_typeResolver->boolType());
1973 m_state.setHasInternalSideEffects();
1974}
1975
1976void QQmlJSTypePropagator::generate_JumpFalse(int offset)
1977{
1978 if (!canConvertFromTo(m_state.accumulatorIn(), m_typeResolver->boolType())) {
1979 addError(u"cannot convert from %1 to boolean"_s
1980 .arg(m_state.accumulatorIn().descriptiveName()));
1981 return;
1982 }
1983 saveRegisterStateForJump(offset);
1984 addReadAccumulator(m_typeResolver->boolType());
1985 m_state.setHasInternalSideEffects();
1986}
1987
1988void QQmlJSTypePropagator::generate_JumpNoException(int offset)
1989{
1990 saveRegisterStateForJump(offset);
1991 m_state.setHasInternalSideEffects();
1992}
1993
1994void QQmlJSTypePropagator::generate_JumpNotUndefined(int offset)
1995{
1996 Q_UNUSED(offset)
1998}
1999
2000void QQmlJSTypePropagator::generate_CheckException()
2001{
2002 m_state.setHasInternalSideEffects();
2003}
2004
2005void QQmlJSTypePropagator::recordEqualsNullType()
2006{
2007 // TODO: We can specialize this further, for QVariant, QJSValue, int, bool, whatever.
2008 if (m_state.accumulatorIn().contains(m_typeResolver->nullType())
2009 || m_state.accumulatorIn().containedType()->isReferenceType()) {
2010 addReadAccumulator();
2011 } else {
2012 addReadAccumulator(m_typeResolver->jsPrimitiveType());
2013 }
2014}
2015void QQmlJSTypePropagator::recordEqualsIntType()
2016{
2017 // We have specializations for numeric types and bool.
2018 const QQmlJSScope::ConstPtr in = m_state.accumulatorIn().containedType();
2019 if (m_state.accumulatorIn().contains(m_typeResolver->boolType())
2020 || m_typeResolver->isNumeric(m_state.accumulatorIn())) {
2021 addReadAccumulator();
2022 } else {
2023 addReadAccumulator(m_typeResolver->jsPrimitiveType());
2024 }
2025}
2026void QQmlJSTypePropagator::recordEqualsType(int lhs)
2027{
2028 const auto isNumericOrEnum = [this](QQmlJSRegisterContent content) {
2029 return content.isEnumeration() || m_typeResolver->isNumeric(content);
2030 };
2031
2032 const auto accumulatorIn = m_state.accumulatorIn();
2033 const auto lhsRegister = m_state.registers[lhs].content;
2034
2035 // If the types are primitive, we compare directly ...
2036 if (m_typeResolver->isPrimitive(accumulatorIn) || accumulatorIn.isEnumeration()) {
2037 if (accumulatorIn.contains(lhsRegister.containedType())
2038 || (isNumericOrEnum(accumulatorIn) && isNumericOrEnum(lhsRegister))
2039 || m_typeResolver->isPrimitive(lhsRegister)) {
2040 addReadRegister(lhs);
2041 addReadAccumulator();
2042 return;
2043 }
2044 }
2045
2046 const auto containedAccumulatorIn = m_typeResolver->isOptionalType(accumulatorIn)
2047 ? m_typeResolver->extractNonVoidFromOptionalType(accumulatorIn).containedType()
2048 : accumulatorIn.containedType();
2049
2050 const auto containedLhs = m_typeResolver->isOptionalType(lhsRegister)
2051 ? m_typeResolver->extractNonVoidFromOptionalType(lhsRegister).containedType()
2052 : lhsRegister.containedType();
2053
2054 // We don't modify types if the types are comparable with QObject, QUrl or var types
2055 if (QQmlJSUtils::canStrictlyCompareWithVar(m_typeResolver, containedLhs, containedAccumulatorIn)
2056 || QQmlJSUtils::canCompareWithQObject(m_typeResolver, containedLhs, containedAccumulatorIn)
2057 || QQmlJSUtils::canCompareWithQUrl(m_typeResolver, containedLhs, containedAccumulatorIn)) {
2058 addReadRegister(lhs);
2059 addReadAccumulator();
2060 return;
2061 }
2062
2063 // Otherwise they're both casted to QJSValue.
2064 // TODO: We can add more specializations here: object/null etc
2065
2066 const QQmlJSScope::ConstPtr jsval = m_typeResolver->jsValueType();
2067 addReadRegister(lhs, jsval);
2068 addReadAccumulator(jsval);
2069}
2070
2071void QQmlJSTypePropagator::recordCompareType(int lhs)
2072{
2073 // TODO: Revisit this. Does it make any sense to do a comparison on something non-numeric?
2074 // Does it pay off to record the exact number type to use?
2075
2076 const QQmlJSRegisterContent lhsContent = m_state.registers[lhs].content;
2077 const QQmlJSRegisterContent rhsContent = m_state.accumulatorIn();
2078 if (lhsContent == rhsContent) {
2079 // Do not re-track in this case. We want any manipulations on the original types to persist.
2080 // TODO: Why? Can we just use double and be done with it?
2081 addReadRegister(lhs, lhsContent);
2082 addReadAccumulator(lhsContent);
2083 } else if (m_typeResolver->isNumeric(lhsContent) && m_typeResolver->isNumeric(rhsContent)) {
2084 // If they're both numeric, we can compare them directly.
2085 // They may be casted to double, though.
2086 const QQmlJSRegisterContent merged = m_typeResolver->merge(lhsContent, rhsContent);
2087 addReadRegister(lhs, merged);
2088 addReadAccumulator(merged);
2089 } else {
2090 const QQmlJSScope::ConstPtr primitive = m_typeResolver->jsPrimitiveType();
2091 addReadRegister(lhs, primitive);
2092 addReadAccumulator(primitive);
2093 }
2094}
2095
2096void QQmlJSTypePropagator::warnAboutTypeCoercion(int lhs)
2097{
2098 Q_UNUSED(lhs);
2099}
2100
2101void QQmlJSTypePropagator::generate_CmpEqNull()
2102{
2103 recordEqualsNullType();
2104 setAccumulator(m_typeResolver->operationType(m_typeResolver->boolType()));
2105}
2106
2107void QQmlJSTypePropagator::generate_CmpNeNull()
2108{
2109 recordEqualsNullType();
2110 setAccumulator(m_typeResolver->operationType(m_typeResolver->boolType()));
2111}
2112
2113void QQmlJSTypePropagator::generate_CmpEqInt(int lhsConst)
2114{
2115 recordEqualsIntType();
2116 Q_UNUSED(lhsConst)
2117 setAccumulator(m_typeResolver->typeForBinaryOperation(
2118 QSOperator::Op::Equal, m_typeResolver->literalType(m_typeResolver->int32Type()),
2119 m_state.accumulatorIn()));
2120}
2121
2122void QQmlJSTypePropagator::generate_CmpNeInt(int lhsConst)
2123{
2124 recordEqualsIntType();
2125 Q_UNUSED(lhsConst)
2126 setAccumulator(m_typeResolver->typeForBinaryOperation(
2127 QSOperator::Op::NotEqual, m_typeResolver->literalType(m_typeResolver->int32Type()),
2128 m_state.accumulatorIn()));
2129}
2130
2131void QQmlJSTypePropagator::generate_CmpEq(int lhs)
2132{
2133 warnAboutTypeCoercion(lhs);
2134 recordEqualsType(lhs);
2135 propagateBinaryOperation(QSOperator::Op::Equal, lhs);
2136}
2137
2138void QQmlJSTypePropagator::generate_CmpNe(int lhs)
2139{
2140 warnAboutTypeCoercion(lhs);
2141 recordEqualsType(lhs);
2142 propagateBinaryOperation(QSOperator::Op::NotEqual, lhs);
2143}
2144
2145void QQmlJSTypePropagator::generate_CmpGt(int lhs)
2146{
2147 recordCompareType(lhs);
2148 propagateBinaryOperation(QSOperator::Op::Gt, lhs);
2149}
2150
2151void QQmlJSTypePropagator::generate_CmpGe(int lhs)
2152{
2153 recordCompareType(lhs);
2154 propagateBinaryOperation(QSOperator::Op::Ge, lhs);
2155}
2156
2157void QQmlJSTypePropagator::generate_CmpLt(int lhs)
2158{
2159 recordCompareType(lhs);
2160 propagateBinaryOperation(QSOperator::Op::Lt, lhs);
2161}
2162
2163void QQmlJSTypePropagator::generate_CmpLe(int lhs)
2164{
2165 recordCompareType(lhs);
2166 propagateBinaryOperation(QSOperator::Op::Le, lhs);
2167}
2168
2169void QQmlJSTypePropagator::generate_CmpStrictEqual(int lhs)
2170{
2171 recordEqualsType(lhs);
2172 propagateBinaryOperation(QSOperator::Op::StrictEqual, lhs);
2173}
2174
2175void QQmlJSTypePropagator::generate_CmpStrictNotEqual(int lhs)
2176{
2177 recordEqualsType(lhs);
2178 propagateBinaryOperation(QSOperator::Op::StrictNotEqual, lhs);
2179}
2180
2181void QQmlJSTypePropagator::generate_CmpIn(int lhs)
2182{
2183 // TODO: Most of the time we don't need the object at all, but only its metatype.
2184 // Fix this when we add support for the "in" instruction to the code generator.
2185 // Also, specialize on lhs to avoid conversion to QJSPrimitiveValue.
2186
2187 addReadRegister(lhs, m_typeResolver->jsValueType());
2188 addReadAccumulator(m_typeResolver->jsValueType());
2189
2190 propagateBinaryOperation(QSOperator::Op::In, lhs);
2191}
2192
2193void QQmlJSTypePropagator::generate_CmpInstanceOf(int lhs)
2194{
2195 Q_UNUSED(lhs)
2197}
2198
2199void QQmlJSTypePropagator::generate_As(int lhs)
2200{
2201 const QQmlJSRegisterContent input = checkedInputRegister(lhs);
2202 const QQmlJSScope::ConstPtr inContained = input.containedType();
2203
2204 QQmlJSRegisterContent output;
2205
2206 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
2207 switch (accumulatorIn.variant()) {
2208 case QQmlJSRegisterContent::Attachment:
2209 output = accumulatorIn.scope();
2210 break;
2211 case QQmlJSRegisterContent::MetaType:
2212 output = accumulatorIn.scope();
2213 if (output.containedType()->isComposite()) // Otherwise we don't need it
2214 addReadAccumulator(m_typeResolver->metaObjectType());
2215 break;
2216 default:
2217 output = accumulatorIn;
2218 break;
2219 }
2220
2221 QQmlJSScope::ConstPtr outContained = output.containedType();
2222
2223 if (outContained->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
2224 // A referece type cast can result in either the type or null.
2225 // Reference types can hold null. We don't need to special case that.
2226
2227 if (m_typeResolver->inherits(inContained, outContained))
2228 output = m_pool->clone(input);
2229 else
2230 output = m_pool->castTo(input, outContained);
2231 } else if (m_typeResolver->inherits(inContained, outContained)) {
2232 // A "slicing" cannot result in void
2233 output = m_pool->castTo(input, outContained);
2234 } else {
2235 // A value type cast can result in either the type or undefined.
2236 // Using convert() retains the variant of the input type.
2237 output = m_typeResolver->merge(
2238 m_pool->castTo(input, outContained),
2239 m_pool->castTo(input, m_typeResolver->voidType()));
2240 }
2241
2242 addReadRegister(lhs);
2243 setAccumulator(output);
2244}
2245
2246void QQmlJSTypePropagator::checkConversion(
2247 QQmlJSRegisterContent from, QQmlJSRegisterContent to)
2248{
2249 if (!canConvertFromTo(from, to)) {
2250 addError(u"cannot convert from %1 to %2"_s
2251 .arg(from.descriptiveName(), to.descriptiveName()));
2252 }
2253}
2254
2255void QQmlJSTypePropagator::generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator op)
2256{
2257 const QQmlJSRegisterContent type = m_typeResolver->typeForArithmeticUnaryOperation(
2258 op, m_state.accumulatorIn());
2259 checkConversion(m_state.accumulatorIn(), type);
2260 addReadAccumulator(type);
2261 setAccumulator(type);
2262}
2263
2264void QQmlJSTypePropagator::generate_UNot()
2265{
2266 generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Not);
2267}
2268
2269void QQmlJSTypePropagator::generate_UPlus()
2270{
2271 generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Plus);
2272}
2273
2274void QQmlJSTypePropagator::generate_UMinus()
2275{
2276 generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Minus);
2277}
2278
2279void QQmlJSTypePropagator::generate_UCompl()
2280{
2281 generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Complement);
2282}
2283
2284void QQmlJSTypePropagator::generate_Increment()
2285{
2286 generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Increment);
2287}
2288
2289void QQmlJSTypePropagator::generate_Decrement()
2290{
2291 generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Decrement);
2292}
2293
2294void QQmlJSTypePropagator::generateBinaryArithmeticOperation(QSOperator::Op op, int lhs)
2295{
2296 const QQmlJSRegisterContent type = propagateBinaryOperation(op, lhs);
2297
2298 checkConversion(checkedInputRegister(lhs), type);
2299 addReadRegister(lhs, type);
2300
2301 checkConversion(m_state.accumulatorIn(), type);
2302 addReadAccumulator(type);
2303}
2304
2305void QQmlJSTypePropagator::generateBinaryConstArithmeticOperation(QSOperator::Op op)
2306{
2307 const QQmlJSRegisterContent type = m_typeResolver->typeForBinaryOperation(
2308 op, m_state.accumulatorIn(),
2309 m_typeResolver->literalType(m_typeResolver->int32Type()));
2310
2311 checkConversion(m_state.accumulatorIn(), type);
2312 addReadAccumulator(type);
2313 setAccumulator(type);
2314}
2315
2316void QQmlJSTypePropagator::generate_Add(int lhs)
2317{
2318 generateBinaryArithmeticOperation(QSOperator::Op::Add, lhs);
2319}
2320
2321void QQmlJSTypePropagator::generate_BitAnd(int lhs)
2322{
2323 generateBinaryArithmeticOperation(QSOperator::Op::BitAnd, lhs);
2324}
2325
2326void QQmlJSTypePropagator::generate_BitOr(int lhs)
2327{
2328 generateBinaryArithmeticOperation(QSOperator::Op::BitOr, lhs);
2329}
2330
2331void QQmlJSTypePropagator::generate_BitXor(int lhs)
2332{
2333 generateBinaryArithmeticOperation(QSOperator::Op::BitXor, lhs);
2334}
2335
2336void QQmlJSTypePropagator::generate_UShr(int lhs)
2337{
2338 generateBinaryArithmeticOperation(QSOperator::Op::URShift, lhs);
2339}
2340
2341void QQmlJSTypePropagator::generate_Shr(int lhs)
2342{
2343 generateBinaryArithmeticOperation(QSOperator::Op::RShift, lhs);
2344}
2345
2346void QQmlJSTypePropagator::generate_Shl(int lhs)
2347{
2348 generateBinaryArithmeticOperation(QSOperator::Op::LShift, lhs);
2349}
2350
2351void QQmlJSTypePropagator::generate_BitAndConst(int rhsConst)
2352{
2353 Q_UNUSED(rhsConst)
2354 generateBinaryConstArithmeticOperation(QSOperator::Op::BitAnd);
2355}
2356
2357void QQmlJSTypePropagator::generate_BitOrConst(int rhsConst)
2358{
2359 Q_UNUSED(rhsConst)
2360 generateBinaryConstArithmeticOperation(QSOperator::Op::BitOr);
2361}
2362
2363void QQmlJSTypePropagator::generate_BitXorConst(int rhsConst)
2364{
2365 Q_UNUSED(rhsConst)
2366 generateBinaryConstArithmeticOperation(QSOperator::Op::BitXor);
2367}
2368
2369void QQmlJSTypePropagator::generate_UShrConst(int rhsConst)
2370{
2371 Q_UNUSED(rhsConst)
2372 generateBinaryConstArithmeticOperation(QSOperator::Op::URShift);
2373}
2374
2375void QQmlJSTypePropagator::generate_ShrConst(int rhsConst)
2376{
2377 Q_UNUSED(rhsConst)
2378 generateBinaryConstArithmeticOperation(QSOperator::Op::RShift);
2379}
2380
2381void QQmlJSTypePropagator::generate_ShlConst(int rhsConst)
2382{
2383 Q_UNUSED(rhsConst)
2384 generateBinaryConstArithmeticOperation(QSOperator::Op::LShift);
2385}
2386
2387void QQmlJSTypePropagator::generate_Exp(int lhs)
2388{
2389 generateBinaryArithmeticOperation(QSOperator::Op::Exp, lhs);
2390}
2391
2392void QQmlJSTypePropagator::generate_Mul(int lhs)
2393{
2394 generateBinaryArithmeticOperation(QSOperator::Op::Mul, lhs);
2395}
2396
2397void QQmlJSTypePropagator::generate_Div(int lhs)
2398{
2399 generateBinaryArithmeticOperation(QSOperator::Op::Div, lhs);
2400}
2401
2402void QQmlJSTypePropagator::generate_Mod(int lhs)
2403{
2404 generateBinaryArithmeticOperation(QSOperator::Op::Mod, lhs);
2405}
2406
2407void QQmlJSTypePropagator::generate_Sub(int lhs)
2408{
2409 generateBinaryArithmeticOperation(QSOperator::Op::Sub, lhs);
2410}
2411
2412void QQmlJSTypePropagator::generate_InitializeBlockDeadTemporalZone(int firstReg, int count)
2413{
2414 setAccumulator(m_typeResolver->literalType(m_typeResolver->emptyType()));
2415 for (int reg = firstReg, end = firstReg + count; reg < end; ++reg)
2416 setRegister(reg, m_typeResolver->literalType(m_typeResolver->emptyType()));
2417}
2418
2419void QQmlJSTypePropagator::generate_ThrowOnNullOrUndefined()
2420{
2422}
2423
2424void QQmlJSTypePropagator::generate_GetTemplateObject(int index)
2425{
2426 Q_UNUSED(index)
2428}
2429
2430QV4::Moth::ByteCodeHandler::Verdict
2431QQmlJSTypePropagator::startInstruction(QV4::Moth::Instr::Type type)
2432{
2433 if (m_state.jumpTargets.contains(currentInstructionOffset())) {
2434 if (m_state.skipInstructionsUntilNextJumpTarget) {
2435 // When re-surfacing from dead code, all registers are invalid.
2436 m_state.registers.clear();
2437 m_state.skipInstructionsUntilNextJumpTarget = false;
2438 }
2439 } else if (m_state.skipInstructionsUntilNextJumpTarget
2440 && !instructionManipulatesContext(type)) {
2441 return SkipInstruction;
2442 }
2443
2444 const int currentOffset = currentInstructionOffset();
2445
2446 // If we reach an instruction that is a target of a jump earlier, then we must check that the
2447 // register state at the origin matches the current state. If not, then we may have to inject
2448 // conversion code (communicated to code gen via m_state.typeConversions). For
2449 // example:
2450 //
2451 // function blah(x: number) { return x > 10 ? 10 : x}
2452 //
2453 // translates to a situation where in the "true" case, we load an integer into the accumulator
2454 // and in the else case a number (x). When the control flow is joined, the types don't match and
2455 // we need to make sure that the int is converted to a double just before the jump.
2456 for (auto originRegisterStateIt =
2457 m_jumpOriginRegisterStateByTargetInstructionOffset.constFind(currentOffset);
2458 originRegisterStateIt != m_jumpOriginRegisterStateByTargetInstructionOffset.constEnd()
2459 && originRegisterStateIt.key() == currentOffset;
2460 ++originRegisterStateIt) {
2461 auto stateToMerge = *originRegisterStateIt;
2462 for (auto registerIt = stateToMerge.registers.constBegin(),
2463 end = stateToMerge.registers.constEnd();
2464 registerIt != end; ++registerIt) {
2465 const int registerIndex = registerIt.key();
2466
2467 const VirtualRegister &newType = registerIt.value();
2468 if (!newType.content.isValid()) {
2469 addError(u"When reached from offset %1, %2 is undefined"_s
2470 .arg(stateToMerge.originatingOffset)
2471 .arg(registerName(registerIndex)));
2472 return SkipInstruction;
2473 }
2474
2475 auto currentRegister = m_state.registers.find(registerIndex);
2476 if (currentRegister != m_state.registers.end())
2477 mergeRegister(registerIndex, newType, currentRegister.value());
2478 else
2479 mergeRegister(registerIndex, newType, newType);
2480 }
2481 }
2482
2483 return ProcessInstruction;
2484}
2485
2486bool QQmlJSTypePropagator::populatesAccumulator(QV4::Moth::Instr::Type instr) const
2487{
2488 switch (instr) {
2489 case QV4::Moth::Instr::Type::CheckException:
2490 case QV4::Moth::Instr::Type::CloneBlockContext:
2491 case QV4::Moth::Instr::Type::ConvertThisToObject:
2492 case QV4::Moth::Instr::Type::CreateCallContext:
2493 case QV4::Moth::Instr::Type::DeadTemporalZoneCheck:
2494 case QV4::Moth::Instr::Type::Debug:
2495 case QV4::Moth::Instr::Type::DeclareVar:
2496 case QV4::Moth::Instr::Type::IteratorClose:
2497 case QV4::Moth::Instr::Type::IteratorNext:
2498 case QV4::Moth::Instr::Type::IteratorNextForYieldStar:
2499 case QV4::Moth::Instr::Type::Jump:
2500 case QV4::Moth::Instr::Type::JumpFalse:
2501 case QV4::Moth::Instr::Type::JumpNoException:
2502 case QV4::Moth::Instr::Type::JumpNotUndefined:
2503 case QV4::Moth::Instr::Type::JumpTrue:
2504 case QV4::Moth::Instr::Type::MoveConst:
2505 case QV4::Moth::Instr::Type::MoveReg:
2506 case QV4::Moth::Instr::Type::MoveRegExp:
2507 case QV4::Moth::Instr::Type::PopContext:
2508 case QV4::Moth::Instr::Type::PushBlockContext:
2509 case QV4::Moth::Instr::Type::PushCatchContext:
2510 case QV4::Moth::Instr::Type::PushScriptContext:
2511 case QV4::Moth::Instr::Type::Resume:
2512 case QV4::Moth::Instr::Type::Ret:
2513 case QV4::Moth::Instr::Type::SetException:
2514 case QV4::Moth::Instr::Type::SetLookup:
2515 case QV4::Moth::Instr::Type::SetUnwindHandler:
2516 case QV4::Moth::Instr::Type::StoreElement:
2517 case QV4::Moth::Instr::Type::StoreLocal:
2518 case QV4::Moth::Instr::Type::StoreNameSloppy:
2519 case QV4::Moth::Instr::Type::StoreNameStrict:
2520 case QV4::Moth::Instr::Type::StoreProperty:
2521 case QV4::Moth::Instr::Type::StoreReg:
2522 case QV4::Moth::Instr::Type::StoreScopedLocal:
2523 case QV4::Moth::Instr::Type::StoreSuperProperty:
2524 case QV4::Moth::Instr::Type::ThrowException:
2525 case QV4::Moth::Instr::Type::ThrowOnNullOrUndefined:
2526 case QV4::Moth::Instr::Type::UnwindDispatch:
2527 case QV4::Moth::Instr::Type::UnwindToLabel:
2528 case QV4::Moth::Instr::Type::Yield:
2529 case QV4::Moth::Instr::Type::YieldStar:
2530 return false;
2531 case QV4::Moth::Instr::Type::Add:
2532 case QV4::Moth::Instr::Type::As:
2533 case QV4::Moth::Instr::Type::BitAnd:
2534 case QV4::Moth::Instr::Type::BitAndConst:
2535 case QV4::Moth::Instr::Type::BitOr:
2536 case QV4::Moth::Instr::Type::BitOrConst:
2537 case QV4::Moth::Instr::Type::BitXor:
2538 case QV4::Moth::Instr::Type::BitXorConst:
2539 case QV4::Moth::Instr::Type::CallGlobalLookup:
2540 case QV4::Moth::Instr::Type::CallName:
2541 case QV4::Moth::Instr::Type::CallPossiblyDirectEval:
2542 case QV4::Moth::Instr::Type::CallProperty:
2543 case QV4::Moth::Instr::Type::CallPropertyLookup:
2544 case QV4::Moth::Instr::Type::CallQmlContextPropertyLookup:
2545 case QV4::Moth::Instr::Type::CallValue:
2546 case QV4::Moth::Instr::Type::CallWithReceiver:
2547 case QV4::Moth::Instr::Type::CallWithSpread:
2548 case QV4::Moth::Instr::Type::CmpEq:
2549 case QV4::Moth::Instr::Type::CmpEqInt:
2550 case QV4::Moth::Instr::Type::CmpEqNull:
2551 case QV4::Moth::Instr::Type::CmpGe:
2552 case QV4::Moth::Instr::Type::CmpGt:
2553 case QV4::Moth::Instr::Type::CmpIn:
2554 case QV4::Moth::Instr::Type::CmpInstanceOf:
2555 case QV4::Moth::Instr::Type::CmpLe:
2556 case QV4::Moth::Instr::Type::CmpLt:
2557 case QV4::Moth::Instr::Type::CmpNe:
2558 case QV4::Moth::Instr::Type::CmpNeInt:
2559 case QV4::Moth::Instr::Type::CmpNeNull:
2560 case QV4::Moth::Instr::Type::CmpStrictEqual:
2561 case QV4::Moth::Instr::Type::CmpStrictNotEqual:
2562 case QV4::Moth::Instr::Type::Construct:
2563 case QV4::Moth::Instr::Type::ConstructWithSpread:
2564 case QV4::Moth::Instr::Type::CreateClass:
2565 case QV4::Moth::Instr::Type::CreateMappedArgumentsObject:
2566 case QV4::Moth::Instr::Type::CreateRestParameter:
2567 case QV4::Moth::Instr::Type::CreateUnmappedArgumentsObject:
2568 case QV4::Moth::Instr::Type::Decrement:
2569 case QV4::Moth::Instr::Type::DefineArray:
2570 case QV4::Moth::Instr::Type::DefineObjectLiteral:
2571 case QV4::Moth::Instr::Type::DeleteName:
2572 case QV4::Moth::Instr::Type::DeleteProperty:
2573 case QV4::Moth::Instr::Type::DestructureRestElement:
2574 case QV4::Moth::Instr::Type::Div:
2575 case QV4::Moth::Instr::Type::Exp:
2576 case QV4::Moth::Instr::Type::GetException:
2577 case QV4::Moth::Instr::Type::GetIterator:
2578 case QV4::Moth::Instr::Type::GetLookup:
2579 case QV4::Moth::Instr::Type::GetOptionalLookup:
2580 case QV4::Moth::Instr::Type::GetTemplateObject:
2581 case QV4::Moth::Instr::Type::Increment:
2582 case QV4::Moth::Instr::Type::InitializeBlockDeadTemporalZone:
2583 case QV4::Moth::Instr::Type::LoadClosure:
2584 case QV4::Moth::Instr::Type::LoadConst:
2585 case QV4::Moth::Instr::Type::LoadElement:
2586 case QV4::Moth::Instr::Type::LoadFalse:
2587 case QV4::Moth::Instr::Type::LoadGlobalLookup:
2588 case QV4::Moth::Instr::Type::LoadImport:
2589 case QV4::Moth::Instr::Type::LoadInt:
2590 case QV4::Moth::Instr::Type::LoadLocal:
2591 case QV4::Moth::Instr::Type::LoadName:
2592 case QV4::Moth::Instr::Type::LoadNull:
2593 case QV4::Moth::Instr::Type::LoadOptionalProperty:
2594 case QV4::Moth::Instr::Type::LoadProperty:
2595 case QV4::Moth::Instr::Type::LoadQmlContextPropertyLookup:
2596 case QV4::Moth::Instr::Type::LoadReg:
2597 case QV4::Moth::Instr::Type::LoadRuntimeString:
2598 case QV4::Moth::Instr::Type::LoadScopedLocal:
2599 case QV4::Moth::Instr::Type::LoadSuperConstructor:
2600 case QV4::Moth::Instr::Type::LoadSuperProperty:
2601 case QV4::Moth::Instr::Type::LoadTrue:
2602 case QV4::Moth::Instr::Type::LoadUndefined:
2603 case QV4::Moth::Instr::Type::LoadZero:
2604 case QV4::Moth::Instr::Type::Mod:
2605 case QV4::Moth::Instr::Type::Mul:
2606 case QV4::Moth::Instr::Type::PushWithContext:
2607 case QV4::Moth::Instr::Type::Shl:
2608 case QV4::Moth::Instr::Type::ShlConst:
2609 case QV4::Moth::Instr::Type::Shr:
2610 case QV4::Moth::Instr::Type::ShrConst:
2611 case QV4::Moth::Instr::Type::Sub:
2612 case QV4::Moth::Instr::Type::TailCall:
2613 case QV4::Moth::Instr::Type::ToObject:
2614 case QV4::Moth::Instr::Type::TypeofName:
2615 case QV4::Moth::Instr::Type::TypeofValue:
2616 case QV4::Moth::Instr::Type::UCompl:
2617 case QV4::Moth::Instr::Type::UMinus:
2618 case QV4::Moth::Instr::Type::UNot:
2619 case QV4::Moth::Instr::Type::UPlus:
2620 case QV4::Moth::Instr::Type::UShr:
2621 case QV4::Moth::Instr::Type::UShrConst:
2622 return true;
2623 default:
2624 Q_UNREACHABLE_RETURN(false);
2625 }
2626}
2627
2628bool QQmlJSTypePropagator::isNoop(QV4::Moth::Instr::Type instr) const
2629{
2630 switch (instr) {
2631 case QV4::Moth::Instr::Type::DeadTemporalZoneCheck:
2632 case QV4::Moth::Instr::Type::IteratorClose:
2633 return true;
2634 default:
2635 return false;
2636 }
2637}
2638
2639void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr)
2640{
2641 InstructionAnnotation &currentInstruction = m_state.annotations[currentInstructionOffset()];
2642 currentInstruction.changedRegister = m_state.changedRegister();
2643 currentInstruction.changedRegisterIndex = m_state.changedRegisterIndex();
2644 currentInstruction.readRegisters = m_state.takeReadRegisters();
2645 currentInstruction.hasExternalSideEffects = m_state.hasExternalSideEffects();
2646 currentInstruction.hasInternalSideEffects = m_state.hasInternalSideEffects();
2647 currentInstruction.isRename = m_state.isRename();
2648
2649 bool populates = populatesAccumulator(instr);
2650 int changedIndex = m_state.changedRegisterIndex();
2651
2652 // TODO: Find a way to deal with instructions that change multiple registers
2653 if (instr != QV4::Moth::Instr::Type::InitializeBlockDeadTemporalZone) {
2654 Q_ASSERT((populates && changedIndex == Accumulator && m_state.accumulatorOut().isValid())
2655 || (!populates && changedIndex != Accumulator));
2656 }
2657
2658 if (!m_logger->currentFunctionHasCompileError() && !isNoop(instr)) {
2659 // An instruction needs to have side effects or write to another register or be a known
2660 // noop. Anything else is a problem.
2661 Q_ASSERT(m_state.hasInternalSideEffects() || changedIndex != InvalidRegister);
2662 }
2663
2664 if (changedIndex != InvalidRegister) {
2665 Q_ASSERT(m_logger->currentFunctionHasCompileError() || m_state.changedRegister().isValid());
2666 VirtualRegister &r = m_state.registers[changedIndex];
2667 r.content = m_state.changedRegister();
2668 r.canMove = false;
2669 r.affectedBySideEffects = m_state.isRename()
2670 && m_state.isRegisterAffectedBySideEffects(m_state.renameSourceRegisterIndex());
2671 m_state.clearChangedRegister();
2672 }
2673
2674 m_state.resetSideEffects();
2675 m_state.setIsRename(false);
2676 m_state.setReadRegisters(VirtualRegisters());
2677 m_state.instructionHasError = false;
2678}
2679
2680QQmlJSRegisterContent QQmlJSTypePropagator::propagateBinaryOperation(QSOperator::Op op, int lhs)
2681{
2682 auto lhsRegister = checkedInputRegister(lhs);
2683 if (!lhsRegister.isValid())
2684 return QQmlJSRegisterContent();
2685
2686 const QQmlJSRegisterContent type = m_typeResolver->typeForBinaryOperation(
2687 op, lhsRegister, m_state.accumulatorIn());
2688
2689 setAccumulator(type);
2690 return type;
2691}
2692
2693static bool deepCompare(const QQmlJSRegisterContent &a, const QQmlJSRegisterContent &b)
2694{
2695 if (!a.isValid() && !b.isValid())
2696 return true;
2697
2698 return a.containedType() == b.containedType()
2699 && a.variant() == b.variant()
2700 && deepCompare(a.scope(), b.scope());
2701}
2702
2703void QQmlJSTypePropagator::saveRegisterStateForJump(int offset)
2704{
2705 auto jumpToOffset = offset + nextInstructionOffset();
2706 ExpectedRegisterState state;
2707 state.registers = m_state.registers;
2708 state.originatingOffset = currentInstructionOffset();
2709 m_state.jumpTargets.insert(jumpToOffset);
2710 if (offset < 0) {
2711 // We're jumping backwards. We won't get to merge the register states in this pass anymore.
2712
2713 const auto registerStates =
2714 m_jumpOriginRegisterStateByTargetInstructionOffset.equal_range(jumpToOffset);
2715 for (auto it = registerStates.first; it != registerStates.second; ++it) {
2716 if (it->registers.keys() != state.registers.keys())
2717 continue;
2718
2719 const auto valuesIt = it->registers.values();
2720 const auto valuesState = state.registers.values();
2721
2722 bool different = false;
2723 for (qsizetype i = 0, end = valuesIt.size(); i != end; ++i) {
2724 const auto &valueIt = valuesIt[i];
2725 const auto &valueState = valuesState[i];
2726 if (valueIt.affectedBySideEffects != valueState.affectedBySideEffects
2727 || valueIt.canMove != valueState.canMove
2728 || valueIt.isShadowable != valueState.isShadowable
2729 || !deepCompare(valueIt.content, valueState.content)) {
2730 different = true;
2731 break;
2732 }
2733 }
2734
2735 if (!different)
2736 return; // We've seen the same register state before. No need for merging.
2737 }
2738
2739 // The register state at the target offset needs to be resolved in a further pass.
2740 m_state.needsMorePasses = true;
2741 }
2742 m_jumpOriginRegisterStateByTargetInstructionOffset.insert(jumpToOffset, state);
2743}
2744
2745QString QQmlJSTypePropagator::registerName(int registerIndex) const
2746{
2747 switch (registerIndex) {
2748 case InvalidRegister:
2749 return u"invalid"_s;
2750 case CurrentFunction:
2751 return u"function"_s;
2752 case Context:
2753 return u"context"_s;
2754 case Accumulator:
2755 return u"accumulator"_s;
2756 case This:
2757 return u"this"_s;
2758 case Argc:
2759 return u"argc"_s;
2760 case NewTarget:
2761 return u"newTarget"_s;
2762 default:
2763 break;
2764 }
2765
2766 if (isArgument(registerIndex))
2767 return u"argument %1"_s.arg(registerIndex - FirstArgument);
2768
2769 return u"temporary register %1"_s.arg(
2770 registerIndex - FirstArgument - m_function->argumentTypes.size());
2771}
2772
2773QQmlJSRegisterContent QQmlJSTypePropagator::checkedInputRegister(int reg)
2774{
2775 const auto regIt = m_state.registers.find(reg);
2776 if (regIt != m_state.registers.end())
2777 return regIt.value().content;
2778
2779 switch (reg) {
2780 case CurrentFunction:
2781 return m_typeResolver->syntheticType(m_typeResolver->functionType());
2782 case Context:
2783 return m_typeResolver->syntheticType(m_typeResolver->jsValueType());
2784 case Accumulator:
2785 addError(u"Type error: no value found in accumulator"_s);
2786 return {};
2787 case This:
2788 return m_function->qmlScope;
2789 case Argc:
2790 return m_typeResolver->syntheticType(m_typeResolver->int32Type());
2791 case NewTarget:
2792 // over-approximation: needed in qmllint to not crash on `eval()`-calls
2793 return m_typeResolver->syntheticType(m_typeResolver->varType());
2794 default:
2795 break;
2796 }
2797
2798 if (isArgument(reg))
2799 return argumentType(reg);
2800
2801 addError(u"Type error: could not infer the type of an expression"_s);
2802 return {};
2803}
2804
2805bool QQmlJSTypePropagator::canConvertFromTo(
2806 QQmlJSRegisterContent from, QQmlJSRegisterContent to)
2807{
2808 return m_typeResolver->canConvertFromTo(from, to);
2809}
2810
2811bool QQmlJSTypePropagator::canConvertFromTo(
2812 QQmlJSRegisterContent from, const QQmlJSScope::ConstPtr &to)
2813{
2814 return m_typeResolver->canConvertFromTo(from.containedType(), to);
2815}
2816
2817QT_END_NAMESPACE
Combined button and popup list for selecting options.
#define INSTR_PROLOGUE_NOT_IMPLEMENTED()
static bool deepCompare(const QQmlJSRegisterContent &a, const QQmlJSRegisterContent &b)
#define INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE()
#define INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC()