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 QString name = callBase.descriptiveName();
662 if (!name.isEmpty())
663 name += u' ';
664 addError(u"Type %1does not have a property %2 for writing"_s.arg(name, propertyName));
665 return;
666 }
667
668 if (property.containedType().isNull()) {
669 addError(u"Cannot determine type for property %1 of type %2"_s.arg(
670 propertyName, callBase.descriptiveName()));
671 return;
672 }
673
674 if (!property.isWritable() && !property.containedType()->isListProperty()) {
675 addError(u"Can't assign to read-only property %1"_s.arg(propertyName));
676
677 m_logger->log(u"Cannot assign to read-only property %1"_s.arg(propertyName),
678 qmlReadOnlyProperty, currentSourceLocation());
679
680 return;
681 }
682
683 if (!canConvertFromTo(m_state.accumulatorIn(), property)) {
684 addError(u"cannot convert from %1 to %2"_s
685 .arg(m_state.accumulatorIn().descriptiveName(), property.descriptiveName()));
686 return;
687 }
688
689 // If the input can hold undefined we must not coerce it to the property type
690 // as that might eliminate an undefined value. For example, undefined -> string
691 // becomes "undefined".
692 // We need the undefined value for either resetting the property if that is supported
693 // or generating an exception otherwise. Therefore we explicitly require the value to
694 // be given as QVariant. This triggers the QVariant fallback path that's also used for
695 // shadowable properties. QVariant can hold undefined and the lookup functions will
696 // handle that appropriately.
697
698 const QQmlJSScope::ConstPtr varType = m_typeResolver->varType();
699 const QQmlJSRegisterContent readType = m_typeResolver->canHoldUndefined(m_state.accumulatorIn())
700 ? m_typeResolver->convert(property, varType)
701 : std::move(property);
702 addReadAccumulator(readType);
703 addReadRegister(base);
704 m_state.setHasExternalSideEffects();
705}
706
707void QQmlJSTypePropagator::generate_SetLookup(int index, int base)
708{
709 generate_StoreProperty(m_jsUnitGenerator->lookupNameIndex(index), base);
710}
711
712void QQmlJSTypePropagator::generate_LoadSuperProperty(int property)
713{
714 Q_UNUSED(property)
716}
717
718void QQmlJSTypePropagator::generate_StoreSuperProperty(int property)
719{
720 Q_UNUSED(property)
722}
723
724void QQmlJSTypePropagator::generate_Yield()
725{
727}
728
729void QQmlJSTypePropagator::generate_YieldStar()
730{
732}
733
734void QQmlJSTypePropagator::generate_Resume(int)
735{
737}
738
739void QQmlJSTypePropagator::generate_CallValue(int name, int argc, int argv)
740{
741 m_state.setHasExternalSideEffects();
742 Q_UNUSED(name)
743 Q_UNUSED(argc)
744 Q_UNUSED(argv)
746}
747
748void QQmlJSTypePropagator::generate_CallWithReceiver(int name, int thisObject, int argc, int argv)
749{
750 m_state.setHasExternalSideEffects();
751 Q_UNUSED(name)
752 Q_UNUSED(thisObject)
753 Q_UNUSED(argc)
754 Q_UNUSED(argv)
756}
757
758bool QQmlJSTypePropagator::isLoggingMethod(const QString &consoleMethod)
759{
760 return consoleMethod == u"log" || consoleMethod == u"debug" || consoleMethod == u"info"
761 || consoleMethod == u"warn" || consoleMethod == u"error";
762}
763
764void QQmlJSTypePropagator::generate_CallProperty_SCMath(
765 const QString &name, int base, int argc, int argv)
766{
767 // If we call a method on the Math object we don't need the actual Math object. We do need
768 // to transfer the type information to the code generator so that it knows that this is the
769 // Math object. Read the base register as void. void isn't stored, and the place where it's
770 // created will be optimized out if there are no other readers. The code generator can
771 // retrieve the original type and determine that it was the Math object.
772
773 addReadRegister(base, m_typeResolver->voidType());
774
775 QQmlJSRegisterContent math = m_state.registers[base].content;
776 const QList<QQmlJSMetaMethod> methods = math.containedType()->ownMethods(name);
777 if (methods.isEmpty()) {
778 setVarAccumulatorAndError();
779 std::optional<QQmlJSFixSuggestion> fixSuggestion = QQmlJSUtils::didYouMean(
780 name, math.containedType()->methods().keys(), m_logger->filePath(),
781 currentSourceLocation());
782 m_logger->log(u"Member \"%1\" not found on Math object"_s.arg(name),
783 qmlMissingProperty, currentSourceLocation(),
784 true, true, std::move(fixSuggestion));
785 return;
786 }
787 Q_ASSERT(methods.length() == 1);
788
789 // Declare the Math object as base type of itself so that it gets cloned and won't be
790 // adjusted later. This is what we do with all method calls.
791 QQmlJSRegisterContent realType = m_typeResolver->returnType(
792 methods[0], m_typeResolver->realType(),
793 m_typeResolver->baseType(math.containedType(), math));
794 for (int i = 0; i < argc; ++i)
795 addReadRegister(argv + i, realType);
796 setAccumulator(realType);
797}
798
799void QQmlJSTypePropagator::generate_CallProperty_SCconsole(
800 const QString &name, int base, int argc, int argv)
801{
802 // If we call a method on the console object we don't need the console object.
803 addReadRegister(base, m_typeResolver->voidType());
804
805 if (argc > 0) {
806 const QQmlJSRegisterContent firstContent = m_state.registers[argv].content;
807 const QQmlJSScope::ConstPtr firstArg = firstContent.containedType();
808 switch (firstArg->accessSemantics()) {
809 case QQmlJSScope::AccessSemantics::Reference:
810 // We cannot know whether this will be a logging category at run time.
811 // Therefore we always pass any object types as special last argument.
812 addReadRegister(argv, m_typeResolver->genericType(firstArg));
813 break;
814 case QQmlJSScope::AccessSemantics::Sequence:
815 addReadRegister(argv);
816 break;
817 default:
818 addReadRegister(argv, m_typeResolver->stringType());
819 break;
820 }
821 }
822
823 for (int i = 1; i < argc; ++i) {
824 const QQmlJSRegisterContent argContent = m_state.registers[argv + i].content;
825 const QQmlJSScope::ConstPtr arg = argContent.containedType();
826 if (arg->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence)
827 addReadRegister(argv + i);
828 else
829 addReadRegister(argv + i, m_typeResolver->stringType());
830 }
831
832 // It's debatable whether the console API should be considered an external side effect.
833 // You can certainly qInstallMessageHandler and then react to the message and change
834 // some property in an object exposed to the currently running method. However, we might
835 // disregard such a thing as abuse of the API. For now, the console API is considered to
836 // have external side effects, though.
837 m_state.setHasExternalSideEffects();
838
839 QQmlJSRegisterContent console = m_state.registers[base].content;
840 QList<QQmlJSMetaMethod> methods = console.containedType()->ownMethods(name);
841 Q_ASSERT(methods.length() == 1);
842
843 // Declare the console object as base type of itself so that it gets cloned and won't be
844 // adjusted later. This is what we do with all method calls.
845 setAccumulator(m_typeResolver->returnType(
846 methods[0], m_typeResolver->voidType(),
847 m_typeResolver->baseType(console.containedType(), console)));
848}
849
850void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int argc, int argv)
851{
852 Q_ASSERT(m_state.registers.contains(base));
853 const auto callBase = m_state.registers[base].content;
854 const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
855
856 if (callBase.contains(m_typeResolver->mathObject())) {
857 generate_CallProperty_SCMath(propertyName, base, argc, argv);
858 return;
859 }
860
861 if (callBase.contains(m_typeResolver->consoleObject()) && isLoggingMethod(propertyName)) {
862 generate_CallProperty_SCconsole(propertyName, base, argc, argv);
863 return;
864 }
865
866 const auto baseType = callBase.containedType();
867 const auto member = m_typeResolver->memberType(callBase, propertyName);
868
869 if (!member.isMethod()) {
870 if (callBase.contains(m_typeResolver->jsValueType())
871 || callBase.contains(m_typeResolver->varType())) {
872 const auto jsValueType = m_typeResolver->jsValueType();
873 addReadRegister(base, jsValueType);
874 for (int i = 0; i < argc; ++i)
875 addReadRegister(argv + i, jsValueType);
876 m_state.setHasExternalSideEffects();
877
878 QQmlJSMetaMethod method;
879 method.setIsJavaScriptFunction(true);
880 method.setMethodName(propertyName);
881 method.setMethodType(QQmlJSMetaMethod::MethodType::Method);
882
883 setAccumulator(m_typeResolver->returnType(
884 method, m_typeResolver->jsValueType(), callBase));
885 return;
886 }
887
888 setVarAccumulatorAndError();
889 addError(u"Type %1 does not have a property %2 for calling"_s
890 .arg(callBase.descriptiveName(), propertyName));
891
892 if (callBase.isType() && isCallingProperty(callBase.type(), propertyName))
893 return;
894
895 if (checkForEnumProblems(callBase, propertyName))
896 return;
897
898 std::optional<QQmlJSFixSuggestion> fixSuggestion;
899
900 if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->methods().keys(),
901 m_logger->filePath(), currentSourceLocation());
902 suggestion.has_value()) {
903 fixSuggestion = suggestion;
904 }
905
906 if (baseType->isFullyResolved() || baseType->isScript()) {
907 m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(
908 propertyName, callBase.containedTypeName()),
909 qmlMissingProperty, currentSourceLocation(), true, true, fixSuggestion);
910 }
911 return;
912 }
913
914 checkDeprecated(baseType, propertyName, true);
915
916 addReadRegister(base);
917
918 if (callBase.contains(m_typeResolver->stringType())) {
919 if (propertyName == u"arg"_s && argc == 1) {
920 propagateStringArgCall(callBase, argv);
921 return;
922 }
923 }
924
925 if (baseType->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
926 && member.scope().contains(m_typeResolver->arrayPrototype())
927 && propagateArrayMethod(propertyName, argc, argv, callBase)) {
928 return;
929 }
930
931 propagateCall(member.method(), argc, argv, member.scope());
932}
933
934QQmlJSMetaMethod QQmlJSTypePropagator::bestMatchForCall(const QList<QQmlJSMetaMethod> &methods,
935 int argc, int argv, QStringList *errors)
936{
937 QQmlJSMetaMethod javascriptFunction;
938 QQmlJSMetaMethod candidate;
939 bool hasMultipleCandidates = false;
940
941 for (const auto &method : methods) {
942
943 // If we encounter a JavaScript function, use this as a fallback if no other method matches
944 if (method.isJavaScriptFunction() && !javascriptFunction.isValid())
945 javascriptFunction = method;
946
947 if (method.returnType().isNull() && !method.returnTypeName().isEmpty()) {
948 errors->append(u"return type %1 cannot be resolved"_s
949 .arg(method.returnTypeName()));
950 continue;
951 }
952
953 const auto arguments = method.parameters();
954 if (argc != arguments.size()) {
955 errors->append(
956 u"Function expects %1 arguments, but %2 were provided"_s.arg(arguments.size())
957 .arg(argc));
958 continue;
959 }
960
961 bool fuzzyMatch = true;
962 bool exactMatch = true;
963 for (int i = 0; i < argc; ++i) {
964 const auto argumentType = arguments[i].type();
965 if (argumentType.isNull()) {
966 errors->append(
967 u"type %1 for argument %2 cannot be resolved"_s.arg(arguments[i].typeName())
968 .arg(i));
969 exactMatch = false;
970 fuzzyMatch = false;
971 break;
972 }
973
974 const auto content = m_state.registers[argv + i].content;
975 if (content.contains(argumentType))
976 continue;
977
978 exactMatch = false;
979 if (canConvertFromTo(content, argumentType))
980 continue;
981
982 // We can try to call a method that expects a derived type.
983 if (argumentType->isReferenceType()
984 && m_typeResolver->inherits(
985 argumentType->baseType(), content.containedType())) {
986 continue;
987 }
988
989 errors->append(
990 u"argument %1 contains %2 but is expected to contain the type %3"_s.arg(i).arg(
991 content.descriptiveName(), arguments[i].typeName()));
992 fuzzyMatch = false;
993 break;
994 }
995
996 if (exactMatch) {
997 return method;
998 } else if (fuzzyMatch) {
999 if (!candidate.isValid())
1000 candidate = method;
1001 else
1002 hasMultipleCandidates = true;
1003 }
1004 }
1005
1006 if (hasMultipleCandidates)
1007 return QQmlJSMetaMethod();
1008
1009 return candidate.isValid() ? candidate : javascriptFunction;
1010}
1011
1012void QQmlJSTypePropagator::setAccumulator(QQmlJSRegisterContent content)
1013{
1014 setRegister(Accumulator, content);
1015}
1016
1017void QQmlJSTypePropagator::setRegister(int index, QQmlJSRegisterContent content)
1018{
1019 // If we've come to the same conclusion before, let's not track the type again.
1020 auto it = m_prevStateAnnotations.find(currentInstructionOffset());
1021 if (it != m_prevStateAnnotations.end()) {
1022 QQmlJSRegisterContent lastTry = it->second.changedRegister;
1023 if (lastTry.contains(content.containedType())) {
1024 m_state.setRegister(index, lastTry);
1025 return;
1026 }
1027 }
1028
1029 m_state.setRegister(index, content);
1030}
1031
1032/*! \internal
1033 * Merges the types of two variations of the register at \index
1034 * When the code branches and merges, the same register can carry one of multiple types.
1035 * For example:
1036 *
1037 * let a
1038 * if (something)
1039 * a = 12
1040 * else
1041 * a = "stringstring"
1042 * console.log(a)
1043 *
1044 * At the point where we log the value we need a type that can hold both, the number and
1045 * the string because we don't know which branch was taken before. mergeRegister chooses a
1046 * type that can hold both variants.
1047 *
1048 * Since the type propagator can run multiple passes over the same code (for loops with back
1049 * jumps), we need to reproduce previous resolutions of the merge where they still fit. To that
1050 * effect, we check m_prevStateAnnotations here.
1051 */
1052void QQmlJSTypePropagator::mergeRegister(
1053 int index, const VirtualRegister &a, const VirtualRegister &b)
1054{
1055 const VirtualRegister merged = {
1056 (a.content == b.content) ? a.content : m_typeResolver->merge(a.content, b.content),
1057 a.canMove && b.canMove,
1058 a.affectedBySideEffects || b.affectedBySideEffects,
1059 a.isShadowable || b.isShadowable,
1060 };
1061
1062 Q_ASSERT(merged.content.isValid());
1063
1064 if (!merged.content.isConversion()) {
1065 // The registers were the same. We're already tracking them.
1066 m_state.annotations[currentInstructionOffset()].typeConversions[index] = merged;
1067 m_state.registers[index] = merged;
1068 return;
1069 }
1070
1071 auto tryPrevStateConversion = [this](int index, const VirtualRegister &merged) -> bool {
1072 auto it = m_prevStateAnnotations.find(currentInstructionOffset());
1073 if (it == m_prevStateAnnotations.end())
1074 return false;
1075
1076 auto conversion = it->second.typeConversions.find(index);
1077 if (conversion == it->second.typeConversions.end())
1078 return false;
1079
1080 const VirtualRegister &lastTry = conversion.value();
1081
1082 Q_ASSERT(lastTry.content.isValid());
1083 if (!lastTry.content.isConversion())
1084 return false;
1085
1086 if (lastTry.content.conversionResultType() != merged.content.conversionResultType()
1087 || lastTry.content.conversionOrigins() != merged.content.conversionOrigins()
1088 || lastTry.canMove != merged.canMove
1089 || lastTry.affectedBySideEffects != merged.affectedBySideEffects
1090 || lastTry.isShadowable != merged.isShadowable) {
1091 return false;
1092 }
1093
1094 // We don't need to track it again if we've come to the same conclusion before.
1095 m_state.annotations[currentInstructionOffset()].typeConversions[index] = lastTry;
1096
1097 // Do not reset the side effects
1098 Q_ASSERT(!m_state.registers[index].affectedBySideEffects || lastTry.affectedBySideEffects);
1099
1100 m_state.registers[index] = lastTry;
1101 return true;
1102 };
1103
1104 if (!tryPrevStateConversion(index, merged)) {
1105 // if a != b, we have already re-tracked it.
1106 const VirtualRegister cloned = {
1107 (a == b) ? m_pool->clone(merged.content) : merged.content,
1108 merged.canMove,
1109 merged.affectedBySideEffects,
1110 merged.isShadowable,
1111 };
1112 Q_ASSERT(cloned.content.isValid());
1113 m_state.annotations[currentInstructionOffset()].typeConversions[index] = cloned;
1114 m_state.registers[index] = cloned;
1115 }
1116}
1117
1118void QQmlJSTypePropagator::addReadRegister(int index)
1119{
1120 // Explicitly pass the same type through without conversion
1121 m_state.addReadRegister(index, m_state.registers[index].content);
1122}
1123
1124void QQmlJSTypePropagator::addReadRegister(int index, QQmlJSRegisterContent convertTo)
1125{
1126 if (m_state.registers[index].content == convertTo) {
1127 // Explicitly pass the same type through without conversion
1128 m_state.addReadRegister(index, convertTo);
1129 } else {
1130 m_state.addReadRegister(
1131 index, m_typeResolver->convert(m_state.registers[index].content, convertTo));
1132 }
1133}
1134
1135void QQmlJSTypePropagator::addReadRegister(int index, const QQmlJSScope::ConstPtr &convertTo)
1136{
1137 m_state.addReadRegister(
1138 index, m_typeResolver->convert(m_state.registers[index].content, convertTo));
1139}
1140
1141void QQmlJSTypePropagator::propagateCall(
1142 const QList<QQmlJSMetaMethod> &methods, int argc, int argv,
1143 QQmlJSRegisterContent scope)
1144{
1145 QStringList errors;
1146 const QQmlJSMetaMethod match = bestMatchForCall(methods, argc, argv, &errors);
1147
1148 if (!match.isValid()) {
1149 setVarAccumulatorAndError();
1150 if (methods.size() == 1) {
1151 // Cannot have multiple fuzzy matches if there is only one method
1152 Q_ASSERT(errors.size() == 1);
1153 addError(errors.first());
1154 } else if (errors.size() < methods.size()) {
1155 addError(u"Multiple matching overrides found. Cannot determine the right one."_s);
1156 } else {
1157 addError(u"No matching override found. Candidates:\n"_s + errors.join(u'\n'));
1158 }
1159 return;
1160 }
1161
1162 QQmlJSScope::ConstPtr returnType;
1163 if (match.isJavaScriptFunction())
1164 returnType = m_typeResolver->jsValueType();
1165 else if (match.isConstructor())
1166 returnType = scope.containedType();
1167 else
1168 returnType = match.returnType();
1169
1170 setAccumulator(m_typeResolver->returnType(match, returnType, scope));
1171 if (!m_state.accumulatorOut().isValid())
1172 addError(u"Cannot store return type of method %1()."_s.arg(match.methodName()));
1173
1174 const auto types = match.parameters();
1175 for (int i = 0; i < argc; ++i) {
1176 if (i < types.size()) {
1177 const QQmlJSScope::ConstPtr type = match.isJavaScriptFunction()
1178 ? m_typeResolver->jsValueType()
1179 : QQmlJSScope::ConstPtr(types.at(i).type());
1180 if (!type.isNull()) {
1181 addReadRegister(argv + i, type);
1182 continue;
1183 }
1184 }
1185 addReadRegister(argv + i, m_typeResolver->jsValueType());
1186 }
1187 m_state.setHasExternalSideEffects();
1188}
1189
1190void QQmlJSTypePropagator::propagateTranslationMethod_SAcheck(const QString &methodName)
1191{
1192 Q_UNUSED(methodName);
1193}
1194
1195bool QQmlJSTypePropagator::propagateTranslationMethod(
1196 const QList<QQmlJSMetaMethod> &methods, int argc, int argv)
1197{
1198 if (methods.size() != 1)
1199 return false;
1200
1201 const QQmlJSMetaMethod method = methods.front();
1202 const QQmlJSScope::ConstPtr intType = m_typeResolver->int32Type();
1203 const QQmlJSScope::ConstPtr stringType = m_typeResolver->stringType();
1204
1205 const QQmlJSRegisterContent returnType = m_typeResolver->returnType(
1206 method, m_typeResolver->stringType(), m_typeResolver->jsGlobalObjectContent());
1207
1208 if (method.methodName() == u"qsTranslate"_s) {
1209 switch (argc) {
1210 case 4:
1211 addReadRegister(argv + 3, intType); // n
1212 Q_FALLTHROUGH();
1213 case 3:
1214 addReadRegister(argv + 2, stringType); // disambiguation
1215 Q_FALLTHROUGH();
1216 case 2:
1217 addReadRegister(argv + 1, stringType); // sourceText
1218 addReadRegister(argv, stringType); // context
1219 setAccumulator(returnType);
1220 propagateTranslationMethod_SAcheck(method.methodName());
1221 return true;
1222 default:
1223 return false;
1224 }
1225 }
1226
1227 if (method.methodName() == u"QT_TRANSLATE_NOOP"_s) {
1228 switch (argc) {
1229 case 3:
1230 addReadRegister(argv + 2, stringType); // disambiguation
1231 Q_FALLTHROUGH();
1232 case 2:
1233 addReadRegister(argv + 1, stringType); // sourceText
1234 addReadRegister(argv, stringType); // context
1235 setAccumulator(returnType);
1236 propagateTranslationMethod_SAcheck(method.methodName());
1237 return true;
1238 default:
1239 return false;
1240 }
1241 }
1242
1243 if (method.methodName() == u"qsTr"_s) {
1244 switch (argc) {
1245 case 3:
1246 addReadRegister(argv + 2, intType); // n
1247 Q_FALLTHROUGH();
1248 case 2:
1249 addReadRegister(argv + 1, stringType); // disambiguation
1250 Q_FALLTHROUGH();
1251 case 1:
1252 addReadRegister(argv, stringType); // sourceText
1253 setAccumulator(returnType);
1254 propagateTranslationMethod_SAcheck(method.methodName());
1255 return true;
1256 default:
1257 return false;
1258 }
1259 }
1260
1261 if (method.methodName() == u"QT_TR_NOOP"_s) {
1262 switch (argc) {
1263 case 2:
1264 addReadRegister(argv + 1, stringType); // disambiguation
1265 Q_FALLTHROUGH();
1266 case 1:
1267 addReadRegister(argv, stringType); // sourceText
1268 setAccumulator(returnType);
1269 propagateTranslationMethod_SAcheck(method.methodName());
1270 return true;
1271 default:
1272 return false;
1273 }
1274 }
1275
1276 if (method.methodName() == u"qsTrId"_s) {
1277 switch (argc) {
1278 case 2:
1279 addReadRegister(argv + 1, intType); // n
1280 Q_FALLTHROUGH();
1281 case 1:
1282 addReadRegister(argv, stringType); // id
1283 setAccumulator(returnType);
1284 propagateTranslationMethod_SAcheck(method.methodName());
1285 return true;
1286 default:
1287 return false;
1288 }
1289 }
1290
1291 if (method.methodName() == u"QT_TRID_NOOP"_s) {
1292 switch (argc) {
1293 case 1:
1294 addReadRegister(argv, stringType); // id
1295 setAccumulator(returnType);
1296 propagateTranslationMethod_SAcheck(method.methodName());
1297 return true;
1298 default:
1299 return false;
1300 }
1301 }
1302
1303 return false;
1304}
1305
1306void QQmlJSTypePropagator::propagateStringArgCall(QQmlJSRegisterContent base, int argv)
1307{
1308 QQmlJSMetaMethod method;
1309 method.setIsJavaScriptFunction(true);
1310 method.setMethodName(u"arg"_s);
1311 setAccumulator(m_typeResolver->returnType(method, m_typeResolver->stringType(), base));
1312 Q_ASSERT(m_state.accumulatorOut().isValid());
1313
1314 const QQmlJSScope::ConstPtr input = m_state.registers[argv].content.containedType();
1315
1316 if (input == m_typeResolver->uint32Type()
1317 || input == m_typeResolver->int64Type()
1318 || input == m_typeResolver->uint64Type()) {
1319 addReadRegister(argv, m_typeResolver->realType());
1320 return;
1321 }
1322
1323 if (m_typeResolver->isIntegral(input)) {
1324 addReadRegister(argv, m_typeResolver->int32Type());
1325 return;
1326 }
1327
1328 if (m_typeResolver->isNumeric(input)) {
1329 addReadRegister(argv, m_typeResolver->realType());
1330 return;
1331 }
1332
1333 if (input == m_typeResolver->boolType()) {
1334 addReadRegister(argv, m_typeResolver->boolType());
1335 return;
1336 }
1337
1338 addReadRegister(argv, m_typeResolver->stringType());
1339}
1340
1341bool QQmlJSTypePropagator::propagateArrayMethod(
1342 const QString &name, int argc, int argv, QQmlJSRegisterContent baseType)
1343{
1344 // TODO:
1345 // * For concat() we need to decide what kind of array to return and what kinds of arguments to
1346 // accept.
1347 // * For entries(), keys(), and values() we need iterators.
1348 // * For find(), findIndex(), sort(), every(), some(), forEach(), map(), filter(), reduce(),
1349 // and reduceRight() we need typed function pointers.
1350
1351 // TODO:
1352 // For now, every method that mutates the original array is considered to have external
1353 // side effects. We could do better by figuring out whether the array is actually backed
1354 // by an external property or has entries backed by an external property. If not, there
1355 // can't be any external side effects.
1356
1357 const auto intType = m_typeResolver->int32Type();
1358 const auto stringType = m_typeResolver->stringType();
1359 const auto baseContained = baseType.containedType();
1360 const auto elementContained = baseContained->elementType();
1361
1362 const auto setReturnType = [&](const QQmlJSScope::ConstPtr type) {
1363 QQmlJSMetaMethod method;
1364 method.setIsJavaScriptFunction(true);
1365 method.setMethodName(name);
1366 setAccumulator(m_typeResolver->returnType(method, type, baseType));
1367 };
1368
1369 if (name == u"copyWithin" && argc > 0 && argc < 4) {
1370 for (int i = 0; i < argc; ++i) {
1371 if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
1372 return false;
1373 }
1374
1375 for (int i = 0; i < argc; ++i)
1376 addReadRegister(argv + i, intType);
1377
1378 m_state.setHasExternalSideEffects();
1379 setReturnType(baseContained);
1380 return true;
1381 }
1382
1383 if (name == u"fill" && argc > 0 && argc < 4) {
1384 if (!canConvertFromTo(m_state.registers[argv].content, elementContained))
1385 return false;
1386
1387 for (int i = 1; i < argc; ++i) {
1388 if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
1389 return false;
1390 }
1391
1392 addReadRegister(argv, elementContained);
1393
1394 for (int i = 1; i < argc; ++i)
1395 addReadRegister(argv + i, intType);
1396
1397 m_state.setHasExternalSideEffects();
1398 setReturnType(baseContained);
1399 return true;
1400 }
1401
1402 if (name == u"includes" && argc > 0 && argc < 3) {
1403 if (!canConvertFromTo(m_state.registers[argv].content, elementContained))
1404 return false;
1405
1406 if (argc == 2) {
1407 if (!canConvertFromTo(m_state.registers[argv + 1].content, intType))
1408 return false;
1409 addReadRegister(argv + 1, intType);
1410 }
1411
1412 addReadRegister(argv, elementContained);
1413 setReturnType(m_typeResolver->boolType());
1414 return true;
1415 }
1416
1417 if (name == u"toString" || (name == u"join" && argc < 2)) {
1418 if (argc == 1) {
1419 if (!canConvertFromTo(m_state.registers[argv].content, stringType))
1420 return false;
1421 addReadRegister(argv, stringType);
1422 }
1423
1424 setReturnType(m_typeResolver->stringType());
1425 return true;
1426 }
1427
1428 if ((name == u"pop" || name == u"shift") && argc == 0) {
1429 m_state.setHasExternalSideEffects();
1430 setReturnType(elementContained);
1431 return true;
1432 }
1433
1434 if (name == u"push" || name == u"unshift") {
1435 for (int i = 0; i < argc; ++i) {
1436 if (!canConvertFromTo(m_state.registers[argv + i].content, elementContained))
1437 return false;
1438 }
1439
1440 for (int i = 0; i < argc; ++i)
1441 addReadRegister(argv + i, elementContained);
1442
1443 m_state.setHasExternalSideEffects();
1444 setReturnType(m_typeResolver->int32Type());
1445 return true;
1446 }
1447
1448 if (name == u"reverse" && argc == 0) {
1449 m_state.setHasExternalSideEffects();
1450 setReturnType(baseContained);
1451 return true;
1452 }
1453
1454 if (name == u"slice" && argc < 3) {
1455 for (int i = 0; i < argc; ++i) {
1456 if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
1457 return false;
1458 }
1459
1460 for (int i = 0; i < argc; ++i)
1461 addReadRegister(argv + i, intType);
1462
1463 setReturnType(baseType.containedType()->isListProperty()
1464 ? m_typeResolver->qObjectListType()
1465 : baseContained);
1466 return true;
1467 }
1468
1469 if (name == u"splice" && argc > 0) {
1470 const int startAndDeleteCount = std::min(argc, 2);
1471 for (int i = 0; i < startAndDeleteCount; ++i) {
1472 if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
1473 return false;
1474 }
1475
1476 for (int i = 2; i < argc; ++i) {
1477 if (!canConvertFromTo(m_state.registers[argv + i].content, elementContained))
1478 return false;
1479 }
1480
1481 for (int i = 0; i < startAndDeleteCount; ++i)
1482 addReadRegister(argv + i, intType);
1483
1484 for (int i = 2; i < argc; ++i)
1485 addReadRegister(argv + i, elementContained);
1486
1487 m_state.setHasExternalSideEffects();
1488 setReturnType(baseContained);
1489 return true;
1490 }
1491
1492 if ((name == u"indexOf" || name == u"lastIndexOf") && argc > 0 && argc < 3) {
1493 if (!canConvertFromTo(m_state.registers[argv].content, elementContained))
1494 return false;
1495
1496 if (argc == 2) {
1497 if (!canConvertFromTo(m_state.registers[argv + 1].content, intType))
1498 return false;
1499 addReadRegister(argv + 1, intType);
1500 }
1501
1502 addReadRegister(argv, elementContained);
1503 setReturnType(m_typeResolver->int32Type());
1504 return true;
1505 }
1506
1507 return false;
1508}
1509
1510void QQmlJSTypePropagator::generate_CallPropertyLookup(int lookupIndex, int base, int argc,
1511 int argv)
1512{
1513 generate_CallProperty(m_jsUnitGenerator->lookupNameIndex(lookupIndex), base, argc, argv);
1514}
1515
1516void QQmlJSTypePropagator::generate_CallName(int name, int argc, int argv)
1517{
1518 propagateScopeLookupCall(m_jsUnitGenerator->stringForIndex(name), argc, argv);
1519}
1520
1521void QQmlJSTypePropagator::generate_CallPossiblyDirectEval(int argc, int argv)
1522{
1523 m_state.setHasExternalSideEffects();
1524 Q_UNUSED(argc)
1525 Q_UNUSED(argv)
1526
1528}
1529
1530void QQmlJSTypePropagator::propagateScopeLookupCall(const QString &functionName, int argc, int argv)
1531{
1532 const QQmlJSRegisterContent resolvedContent
1533 = m_typeResolver->scopedType(m_function->qmlScope, functionName);
1534 if (resolvedContent.isMethod()) {
1535 const auto methods = resolvedContent.method();
1536 if (resolvedContent.scope().contains(m_typeResolver->jsGlobalObject())) {
1537 if (propagateTranslationMethod(methods, argc, argv))
1538 return;
1539 }
1540
1541 if (!methods.isEmpty()) {
1542 propagateCall(methods, argc, argv, resolvedContent.scope());
1543 return;
1544 }
1545 }
1546
1547 addError(u"method %1 cannot be resolved."_s.arg(functionName));
1548 const auto jsValue = m_typeResolver->jsValueType();
1549 QQmlJSMetaMethod method;
1550 method.setMethodName(functionName);
1551 method.setIsJavaScriptFunction(true);
1552 setAccumulator(m_typeResolver->returnType(method, jsValue, m_function->qmlScope));
1553
1554 addError(u"Cannot find function '%1'"_s.arg(functionName));
1555
1556 handleUnqualifiedAccessAndContextProperties(functionName, true);
1557}
1558
1559void QQmlJSTypePropagator::generate_CallGlobalLookup(int index, int argc, int argv)
1560{
1561 propagateScopeLookupCall(m_jsUnitGenerator->lookupName(index), argc, argv);
1562}
1563
1564void QQmlJSTypePropagator::generate_CallQmlContextPropertyLookup(int index, int argc, int argv)
1565{
1566 const QString name = m_jsUnitGenerator->lookupName(index);
1567 propagateScopeLookupCall(name, argc, argv);
1568 checkDeprecated(m_function->qmlScope.containedType(), name, true);
1569}
1570
1571void QQmlJSTypePropagator::generate_CallWithSpread(int func, int thisObject, int argc, int argv)
1572{
1573 m_state.setHasExternalSideEffects();
1574 Q_UNUSED(func)
1575 Q_UNUSED(thisObject)
1576 Q_UNUSED(argc)
1577 Q_UNUSED(argv)
1579}
1580
1581void QQmlJSTypePropagator::generate_TailCall(int func, int thisObject, int argc, int argv)
1582{
1583 m_state.setHasExternalSideEffects();
1584 Q_UNUSED(func)
1585 Q_UNUSED(thisObject)
1586 Q_UNUSED(argc)
1587 Q_UNUSED(argv)
1589}
1590
1591void QQmlJSTypePropagator::generate_Construct_SCDate(
1592 const QQmlJSMetaMethod &ctor, int argc, int argv)
1593{
1594 setAccumulator(m_typeResolver->returnType(ctor, m_typeResolver->dateTimeType(), {}));
1595
1596 if (argc == 1) {
1597 const QQmlJSRegisterContent argType = m_state.registers[argv].content;
1598 if (m_typeResolver->isNumeric(argType)) {
1599 addReadRegister(argv, m_typeResolver->realType());
1600 } else if (argType.contains(m_typeResolver->stringType())) {
1601 addReadRegister(argv, m_typeResolver->stringType());
1602 } else if (argType.contains(m_typeResolver->dateTimeType())
1603 || argType.contains(m_typeResolver->dateType())
1604 || argType.contains(m_typeResolver->timeType())) {
1605 addReadRegister(argv, m_typeResolver->dateTimeType());
1606 } else {
1607 addReadRegister(argv, m_typeResolver->jsPrimitiveType());
1608 }
1609 } else {
1610 constexpr int maxArgc = 7; // year, month, day, hours, minutes, seconds, milliseconds
1611 for (int i = 0; i < std::min(argc, maxArgc); ++i)
1612 addReadRegister(argv + i, m_typeResolver->realType());
1613 }
1614}
1615
1616void QQmlJSTypePropagator::generate_Construct_SCArray(
1617 const QQmlJSMetaMethod &ctor, int argc, int argv)
1618{
1619 if (argc == 1) {
1620 if (m_typeResolver->isNumeric(m_state.registers[argv].content)) {
1621 setAccumulator(m_typeResolver->returnType(ctor, m_typeResolver->variantListType(), {}));
1622 addReadRegister(argv, m_typeResolver->realType());
1623 } else {
1624 generate_DefineArray(argc, argv);
1625 }
1626 } else {
1627 generate_DefineArray(argc, argv);
1628 }
1629}
1630void QQmlJSTypePropagator::generate_Construct(int func, int argc, int argv)
1631{
1632 const QQmlJSRegisterContent type = m_state.registers[func].content;
1633 if (type.contains(m_typeResolver->metaObjectType())) {
1634 const QQmlJSRegisterContent valueType = type.scope();
1635 const QQmlJSScope::ConstPtr contained = type.scopeType();
1636 if (contained->isValueType() && contained->isCreatable()) {
1637 const auto extension = contained->extensionType();
1638 if (extension.extensionSpecifier == QQmlJSScope::ExtensionType) {
1639 propagateCall(
1640 extension.scope->ownMethods(extension.scope->internalName()),
1641 argc, argv, valueType);
1642 } else {
1643 propagateCall(
1644 contained->ownMethods(contained->internalName()), argc, argv, valueType);
1645 }
1646 return;
1647 }
1648 }
1649
1650 if (!type.isMethod()) {
1651 m_state.setHasExternalSideEffects();
1652 QQmlJSMetaMethod method;
1653 method.setMethodName(type.containedTypeName());
1654 method.setIsJavaScriptFunction(true);
1655 method.setIsConstructor(true);
1656 setAccumulator(m_typeResolver->returnType(method, m_typeResolver->jsValueType(), {}));
1657 return;
1658 }
1659
1660 if (const auto methods = type.method();
1661 methods == m_typeResolver->jsGlobalObject()->methods(u"Date"_s)) {
1662 Q_ASSERT(methods.length() == 1);
1663 generate_Construct_SCDate(methods[0], argc, argv);
1664 return;
1665 }
1666
1667 if (const auto methods = type.method();
1668 methods == m_typeResolver->jsGlobalObject()->methods(u"Array"_s)) {
1669 Q_ASSERT(methods.length() == 1);
1670 generate_Construct_SCArray(methods[0], argc, argv);
1671 return;
1672 }
1673
1674 m_state.setHasExternalSideEffects();
1675
1676 QStringList errors;
1677 QQmlJSMetaMethod match = bestMatchForCall(type.method(), argc, argv, &errors);
1678 if (!match.isValid())
1679 addError(u"Cannot determine matching constructor. Candidates:\n"_s + errors.join(u'\n'));
1680 setAccumulator(m_typeResolver->returnType(match, m_typeResolver->jsValueType(), {}));
1681}
1682
1683void QQmlJSTypePropagator::generate_ConstructWithSpread(int func, int argc, int argv)
1684{
1685 m_state.setHasExternalSideEffects();
1686 Q_UNUSED(func)
1687 Q_UNUSED(argc)
1688 Q_UNUSED(argv)
1690}
1691
1692void QQmlJSTypePropagator::generate_SetUnwindHandler(int offset)
1693{
1694 m_state.setHasInternalSideEffects();
1695 Q_UNUSED(offset)
1697}
1698
1699void QQmlJSTypePropagator::generate_UnwindDispatch()
1700{
1701 m_state.setHasInternalSideEffects();
1703}
1704
1705void QQmlJSTypePropagator::generate_UnwindToLabel(int level, int offset)
1706{
1707 m_state.setHasInternalSideEffects();
1708 Q_UNUSED(level)
1709 Q_UNUSED(offset)
1711}
1712
1713void QQmlJSTypePropagator::generate_DeadTemporalZoneCheck(int name)
1714{
1715 const auto fail = [this, name]() {
1716 addError(u"Cannot statically assert the dead temporal zone check for %1"_s.arg(
1717 name ? m_jsUnitGenerator->stringForIndex(name) : u"the anonymous accumulator"_s));
1718 };
1719
1720 const QQmlJSRegisterContent in = m_state.accumulatorIn();
1721 if (in.isConversion()) {
1722 const auto &inConversionOrigins = in.conversionOrigins();
1723 for (QQmlJSRegisterContent origin : inConversionOrigins) {
1724 if (!origin.contains(m_typeResolver->emptyType()))
1725 continue;
1726 fail();
1727 break;
1728 }
1729 } else if (in.contains(m_typeResolver->emptyType())) {
1730 fail();
1731 }
1732}
1733
1734void QQmlJSTypePropagator::generate_ThrowException()
1735{
1736 addReadAccumulator(m_typeResolver->jsValueType());
1737 m_state.setHasInternalSideEffects();
1738 m_state.skipInstructionsUntilNextJumpTarget = true;
1739}
1740
1741void QQmlJSTypePropagator::generate_GetException()
1742{
1744}
1745
1746void QQmlJSTypePropagator::generate_SetException()
1747{
1748 m_state.setHasInternalSideEffects();
1750}
1751
1752void QQmlJSTypePropagator::generate_CreateCallContext()
1753{
1754 m_state.setHasInternalSideEffects();
1755}
1756
1757void QQmlJSTypePropagator::generate_PushCatchContext(int index, int name)
1758{
1759 m_state.setHasInternalSideEffects();
1760 Q_UNUSED(index)
1761 Q_UNUSED(name)
1763}
1764
1765void QQmlJSTypePropagator::generate_PushWithContext()
1766{
1767 m_state.setHasInternalSideEffects();
1769}
1770
1771void QQmlJSTypePropagator::generate_PushBlockContext(int index)
1772{
1773 m_state.setHasInternalSideEffects();
1774 Q_UNUSED(index)
1776}
1777
1778void QQmlJSTypePropagator::generate_CloneBlockContext()
1779{
1780 m_state.setHasInternalSideEffects();
1782}
1783
1784void QQmlJSTypePropagator::generate_PushScriptContext(int index)
1785{
1786 m_state.setHasInternalSideEffects();
1787 Q_UNUSED(index)
1789}
1790
1791void QQmlJSTypePropagator::generate_PopScriptContext()
1792{
1793 m_state.setHasInternalSideEffects();
1795}
1796
1797void QQmlJSTypePropagator::generate_PopContext()
1798{
1799 m_state.setHasInternalSideEffects();
1800}
1801
1802void QQmlJSTypePropagator::generate_GetIterator(int iterator)
1803{
1804 const QQmlJSRegisterContent listType = m_state.accumulatorIn();
1805 if (!listType.isList()) {
1806 const QQmlJSScope::ConstPtr jsValue = m_typeResolver->jsValueType();
1807 addReadAccumulator(jsValue);
1808
1809 QQmlJSMetaProperty prop;
1810 prop.setPropertyName(u"<>"_s);
1811 prop.setTypeName(jsValue->internalName());
1812 prop.setType(jsValue);
1813 setAccumulator(m_pool->createProperty(
1814 prop, currentInstructionOffset(),
1815 QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::ListIterator,
1816 listType));
1817 return;
1818 }
1819
1820 addReadAccumulator();
1821 setAccumulator(m_typeResolver->iteratorPointer(
1822 listType, QQmlJS::AST::ForEachType(iterator), currentInstructionOffset()));
1823}
1824
1825void QQmlJSTypePropagator::generate_IteratorNext(int value, int offset)
1826{
1827 const QQmlJSRegisterContent iteratorType = m_state.accumulatorIn();
1828 addReadAccumulator();
1829 setRegister(value, m_typeResolver->merge(
1830 m_typeResolver->elementType(iteratorType),
1831 m_typeResolver->literalType(m_typeResolver->voidType())));
1832 saveRegisterStateForJump(offset);
1833 m_state.setHasInternalSideEffects();
1834}
1835
1836void QQmlJSTypePropagator::generate_IteratorNextForYieldStar(int iterator, int object, int offset)
1837{
1838 Q_UNUSED(iterator)
1839 Q_UNUSED(object)
1840 Q_UNUSED(offset)
1842}
1843
1844void QQmlJSTypePropagator::generate_IteratorClose()
1845{
1846 // Noop
1847}
1848
1849void QQmlJSTypePropagator::generate_DestructureRestElement()
1850{
1852}
1853
1854void QQmlJSTypePropagator::generate_DeleteProperty(int base, int index)
1855{
1856 Q_UNUSED(base)
1857 Q_UNUSED(index)
1859}
1860
1861void QQmlJSTypePropagator::generate_DeleteName(int name)
1862{
1863 Q_UNUSED(name)
1865}
1866
1867void QQmlJSTypePropagator::generate_TypeofName(int name)
1868{
1869 Q_UNUSED(name);
1870 setAccumulator(m_typeResolver->operationType(m_typeResolver->stringType()));
1871}
1872
1873void QQmlJSTypePropagator::generate_TypeofValue()
1874{
1875 setAccumulator(m_typeResolver->operationType(m_typeResolver->stringType()));
1876}
1877
1878void QQmlJSTypePropagator::generate_DeclareVar(int varName, int isDeletable)
1879{
1880 Q_UNUSED(varName)
1881 Q_UNUSED(isDeletable)
1883}
1884
1885void QQmlJSTypePropagator::generate_DefineArray(int argc, int args)
1886{
1887 setAccumulator(m_typeResolver->operationType(m_typeResolver->variantListType()));
1888
1889 // Track all arguments as the same type.
1890 const QQmlJSScope::ConstPtr elementType = m_typeResolver->varType();
1891 for (int i = 0; i < argc; ++i)
1892 addReadRegister(args + i, elementType);
1893}
1894
1895void QQmlJSTypePropagator::generate_DefineObjectLiteral(int internalClassId, int argc, int args)
1896{
1897 const int classSize = m_jsUnitGenerator->jsClassSize(internalClassId);
1898 Q_ASSERT(argc >= classSize);
1899
1900 // Track each element as separate type
1901 for (int i = 0; i < classSize; ++i)
1902 addReadRegister(args + i, m_typeResolver->varType());
1903
1904 for (int i = classSize; i < argc; i += 3) {
1905 // layout for remaining members is:
1906 // 0: ObjectLiteralArgument - Value|Method|Getter|Setter
1907 // We cannot do anything useful with this. Any code that would call a getter/setter/method
1908 // could not be compiled to C++. Ignore it.
1909
1910 // 1: name of argument
1911 addReadRegister(args + i + 1, m_typeResolver->stringType());
1912
1913 // 2: value of argument
1914 addReadRegister(args + i + 2, m_typeResolver->varType());
1915 }
1916
1917 setAccumulator(m_typeResolver->operationType(m_typeResolver->variantMapType()));
1918}
1919
1920void QQmlJSTypePropagator::generate_CreateClass(int classIndex, int heritage, int computedNames)
1921{
1922 Q_UNUSED(classIndex)
1923 Q_UNUSED(heritage)
1924 Q_UNUSED(computedNames)
1926}
1927
1928void QQmlJSTypePropagator::generate_CreateMappedArgumentsObject()
1929{
1931}
1932
1933void QQmlJSTypePropagator::generate_CreateUnmappedArgumentsObject()
1934{
1936}
1937
1938void QQmlJSTypePropagator::generate_CreateRestParameter(int argIndex)
1939{
1940 Q_UNUSED(argIndex)
1942}
1943
1944void QQmlJSTypePropagator::generate_ConvertThisToObject()
1945{
1946 setRegister(This, m_pool->clone(m_function->qmlScope));
1947}
1948
1949void QQmlJSTypePropagator::generate_LoadSuperConstructor()
1950{
1952}
1953
1954void QQmlJSTypePropagator::generate_ToObject()
1955{
1957}
1958
1959void QQmlJSTypePropagator::generate_Jump(int offset)
1960{
1961 saveRegisterStateForJump(offset);
1962 m_state.skipInstructionsUntilNextJumpTarget = true;
1963 m_state.setHasInternalSideEffects();
1964}
1965
1966void QQmlJSTypePropagator::generate_JumpTrue(int offset)
1967{
1968 if (!canConvertFromTo(m_state.accumulatorIn(), m_typeResolver->boolType())) {
1969 addError(u"cannot convert from %1 to boolean"_s
1970 .arg(m_state.accumulatorIn().descriptiveName()));
1971 return;
1972 }
1973 saveRegisterStateForJump(offset);
1974 addReadAccumulator(m_typeResolver->boolType());
1975 m_state.setHasInternalSideEffects();
1976}
1977
1978void QQmlJSTypePropagator::generate_JumpFalse(int offset)
1979{
1980 if (!canConvertFromTo(m_state.accumulatorIn(), m_typeResolver->boolType())) {
1981 addError(u"cannot convert from %1 to boolean"_s
1982 .arg(m_state.accumulatorIn().descriptiveName()));
1983 return;
1984 }
1985 saveRegisterStateForJump(offset);
1986 addReadAccumulator(m_typeResolver->boolType());
1987 m_state.setHasInternalSideEffects();
1988}
1989
1990void QQmlJSTypePropagator::generate_JumpNoException(int offset)
1991{
1992 saveRegisterStateForJump(offset);
1993 m_state.setHasInternalSideEffects();
1994}
1995
1996void QQmlJSTypePropagator::generate_JumpNotUndefined(int offset)
1997{
1998 Q_UNUSED(offset)
2000}
2001
2002void QQmlJSTypePropagator::generate_CheckException()
2003{
2004 m_state.setHasInternalSideEffects();
2005}
2006
2007void QQmlJSTypePropagator::recordEqualsNullType()
2008{
2009 // TODO: We can specialize this further, for QVariant, QJSValue, int, bool, whatever.
2010 if (m_state.accumulatorIn().contains(m_typeResolver->nullType())
2011 || m_state.accumulatorIn().containedType()->isReferenceType()) {
2012 addReadAccumulator();
2013 } else {
2014 addReadAccumulator(m_typeResolver->jsPrimitiveType());
2015 }
2016}
2017void QQmlJSTypePropagator::recordEqualsIntType()
2018{
2019 // We have specializations for numeric types and bool.
2020 const QQmlJSScope::ConstPtr in = m_state.accumulatorIn().containedType();
2021 if (m_state.accumulatorIn().contains(m_typeResolver->boolType())
2022 || m_typeResolver->isNumeric(m_state.accumulatorIn())) {
2023 addReadAccumulator();
2024 } else {
2025 addReadAccumulator(m_typeResolver->jsPrimitiveType());
2026 }
2027}
2028void QQmlJSTypePropagator::recordEqualsType(int lhs)
2029{
2030 const auto isNumericOrEnum = [this](QQmlJSRegisterContent content) {
2031 return content.isEnumeration() || m_typeResolver->isNumeric(content);
2032 };
2033
2034 const auto accumulatorIn = m_state.accumulatorIn();
2035 const auto lhsRegister = m_state.registers[lhs].content;
2036
2037 // If the types are primitive, we compare directly ...
2038 if (m_typeResolver->isPrimitive(accumulatorIn) || accumulatorIn.isEnumeration()) {
2039 if (accumulatorIn.contains(lhsRegister.containedType())
2040 || (isNumericOrEnum(accumulatorIn) && isNumericOrEnum(lhsRegister))
2041 || m_typeResolver->isPrimitive(lhsRegister)) {
2042 addReadRegister(lhs);
2043 addReadAccumulator();
2044 return;
2045 }
2046 }
2047
2048 const auto containedAccumulatorIn = m_typeResolver->isOptionalType(accumulatorIn)
2049 ? m_typeResolver->extractNonVoidFromOptionalType(accumulatorIn).containedType()
2050 : accumulatorIn.containedType();
2051
2052 const auto containedLhs = m_typeResolver->isOptionalType(lhsRegister)
2053 ? m_typeResolver->extractNonVoidFromOptionalType(lhsRegister).containedType()
2054 : lhsRegister.containedType();
2055
2056 // We don't modify types if the types are comparable with QObject, QUrl or var types
2057 if (QQmlJSUtils::canStrictlyCompareWithVar(m_typeResolver, containedLhs, containedAccumulatorIn)
2058 || QQmlJSUtils::canCompareWithQObject(m_typeResolver, containedLhs, containedAccumulatorIn)
2059 || QQmlJSUtils::canCompareWithQUrl(m_typeResolver, containedLhs, containedAccumulatorIn)) {
2060 addReadRegister(lhs);
2061 addReadAccumulator();
2062 return;
2063 }
2064
2065 // Otherwise they're both casted to QJSValue.
2066 // TODO: We can add more specializations here: object/null etc
2067
2068 const QQmlJSScope::ConstPtr jsval = m_typeResolver->jsValueType();
2069 addReadRegister(lhs, jsval);
2070 addReadAccumulator(jsval);
2071}
2072
2073void QQmlJSTypePropagator::recordCompareType(int lhs)
2074{
2075 // TODO: Revisit this. Does it make any sense to do a comparison on something non-numeric?
2076 // Does it pay off to record the exact number type to use?
2077
2078 const QQmlJSRegisterContent lhsContent = m_state.registers[lhs].content;
2079 const QQmlJSRegisterContent rhsContent = m_state.accumulatorIn();
2080 if (lhsContent == rhsContent) {
2081 // Do not re-track in this case. We want any manipulations on the original types to persist.
2082 // TODO: Why? Can we just use double and be done with it?
2083 addReadRegister(lhs, lhsContent);
2084 addReadAccumulator(lhsContent);
2085 } else if (m_typeResolver->isNumeric(lhsContent) && m_typeResolver->isNumeric(rhsContent)) {
2086 // If they're both numeric, we can compare them directly.
2087 // They may be casted to double, though.
2088 const QQmlJSRegisterContent merged = m_typeResolver->merge(lhsContent, rhsContent);
2089 addReadRegister(lhs, merged);
2090 addReadAccumulator(merged);
2091 } else {
2092 const QQmlJSScope::ConstPtr primitive = m_typeResolver->jsPrimitiveType();
2093 addReadRegister(lhs, primitive);
2094 addReadAccumulator(primitive);
2095 }
2096}
2097
2098void QQmlJSTypePropagator::warnAboutTypeCoercion(int lhs)
2099{
2100 Q_UNUSED(lhs);
2101}
2102
2103void QQmlJSTypePropagator::generate_CmpEqNull()
2104{
2105 recordEqualsNullType();
2106 setAccumulator(m_typeResolver->operationType(m_typeResolver->boolType()));
2107}
2108
2109void QQmlJSTypePropagator::generate_CmpNeNull()
2110{
2111 recordEqualsNullType();
2112 setAccumulator(m_typeResolver->operationType(m_typeResolver->boolType()));
2113}
2114
2115void QQmlJSTypePropagator::generate_CmpEqInt(int lhsConst)
2116{
2117 recordEqualsIntType();
2118 Q_UNUSED(lhsConst)
2119 setAccumulator(m_typeResolver->typeForBinaryOperation(
2120 QSOperator::Op::Equal, m_typeResolver->literalType(m_typeResolver->int32Type()),
2121 m_state.accumulatorIn()));
2122}
2123
2124void QQmlJSTypePropagator::generate_CmpNeInt(int lhsConst)
2125{
2126 recordEqualsIntType();
2127 Q_UNUSED(lhsConst)
2128 setAccumulator(m_typeResolver->typeForBinaryOperation(
2129 QSOperator::Op::NotEqual, m_typeResolver->literalType(m_typeResolver->int32Type()),
2130 m_state.accumulatorIn()));
2131}
2132
2133void QQmlJSTypePropagator::generate_CmpEq(int lhs)
2134{
2135 warnAboutTypeCoercion(lhs);
2136 recordEqualsType(lhs);
2137 propagateBinaryOperation(QSOperator::Op::Equal, lhs);
2138}
2139
2140void QQmlJSTypePropagator::generate_CmpNe(int lhs)
2141{
2142 warnAboutTypeCoercion(lhs);
2143 recordEqualsType(lhs);
2144 propagateBinaryOperation(QSOperator::Op::NotEqual, lhs);
2145}
2146
2147void QQmlJSTypePropagator::generate_CmpGt(int lhs)
2148{
2149 recordCompareType(lhs);
2150 propagateBinaryOperation(QSOperator::Op::Gt, lhs);
2151}
2152
2153void QQmlJSTypePropagator::generate_CmpGe(int lhs)
2154{
2155 recordCompareType(lhs);
2156 propagateBinaryOperation(QSOperator::Op::Ge, lhs);
2157}
2158
2159void QQmlJSTypePropagator::generate_CmpLt(int lhs)
2160{
2161 recordCompareType(lhs);
2162 propagateBinaryOperation(QSOperator::Op::Lt, lhs);
2163}
2164
2165void QQmlJSTypePropagator::generate_CmpLe(int lhs)
2166{
2167 recordCompareType(lhs);
2168 propagateBinaryOperation(QSOperator::Op::Le, lhs);
2169}
2170
2171void QQmlJSTypePropagator::generate_CmpStrictEqual(int lhs)
2172{
2173 recordEqualsType(lhs);
2174 propagateBinaryOperation(QSOperator::Op::StrictEqual, lhs);
2175}
2176
2177void QQmlJSTypePropagator::generate_CmpStrictNotEqual(int lhs)
2178{
2179 recordEqualsType(lhs);
2180 propagateBinaryOperation(QSOperator::Op::StrictNotEqual, lhs);
2181}
2182
2183void QQmlJSTypePropagator::generate_CmpIn(int lhs)
2184{
2185 // TODO: Most of the time we don't need the object at all, but only its metatype.
2186 // Fix this when we add support for the "in" instruction to the code generator.
2187 // Also, specialize on lhs to avoid conversion to QJSPrimitiveValue.
2188
2189 addReadRegister(lhs, m_typeResolver->jsValueType());
2190 addReadAccumulator(m_typeResolver->jsValueType());
2191
2192 propagateBinaryOperation(QSOperator::Op::In, lhs);
2193}
2194
2195void QQmlJSTypePropagator::generate_CmpInstanceOf(int lhs)
2196{
2197 Q_UNUSED(lhs)
2199}
2200
2201void QQmlJSTypePropagator::generate_As(int lhs)
2202{
2203 const QQmlJSRegisterContent input = checkedInputRegister(lhs);
2204 const QQmlJSScope::ConstPtr inContained = input.containedType();
2205
2206 QQmlJSRegisterContent output;
2207
2208 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
2209 switch (accumulatorIn.variant()) {
2210 case QQmlJSRegisterContent::Attachment:
2211 output = accumulatorIn.scope();
2212 break;
2213 case QQmlJSRegisterContent::MetaType:
2214 output = accumulatorIn.scope();
2215 if (output.containedType()->isComposite()) // Otherwise we don't need it
2216 addReadAccumulator(m_typeResolver->metaObjectType());
2217 break;
2218 default:
2219 output = accumulatorIn;
2220 break;
2221 }
2222
2223 QQmlJSScope::ConstPtr outContained = output.containedType();
2224
2225 if (outContained->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
2226 // A referece type cast can result in either the type or null.
2227 // Reference types can hold null. We don't need to special case that.
2228
2229 if (m_typeResolver->inherits(inContained, outContained))
2230 output = m_pool->clone(input);
2231 else
2232 output = m_pool->castTo(input, outContained);
2233 } else if (m_typeResolver->inherits(inContained, outContained)) {
2234 // A "slicing" cannot result in void
2235 output = m_pool->castTo(input, outContained);
2236 } else {
2237 // A value type cast can result in either the type or undefined.
2238 // Using convert() retains the variant of the input type.
2239 output = m_typeResolver->merge(
2240 m_pool->castTo(input, outContained),
2241 m_pool->castTo(input, m_typeResolver->voidType()));
2242 }
2243
2244 addReadRegister(lhs);
2245 setAccumulator(output);
2246}
2247
2248void QQmlJSTypePropagator::checkConversion(
2249 QQmlJSRegisterContent from, QQmlJSRegisterContent to)
2250{
2251 if (!canConvertFromTo(from, to)) {
2252 addError(u"cannot convert from %1 to %2"_s
2253 .arg(from.descriptiveName(), to.descriptiveName()));
2254 }
2255}
2256
2257void QQmlJSTypePropagator::generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator op)
2258{
2259 const QQmlJSRegisterContent type = m_typeResolver->typeForArithmeticUnaryOperation(
2260 op, m_state.accumulatorIn());
2261 checkConversion(m_state.accumulatorIn(), type);
2262 addReadAccumulator(type);
2263 setAccumulator(type);
2264}
2265
2266void QQmlJSTypePropagator::generate_UNot()
2267{
2268 generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Not);
2269}
2270
2271void QQmlJSTypePropagator::generate_UPlus()
2272{
2273 generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Plus);
2274}
2275
2276void QQmlJSTypePropagator::generate_UMinus()
2277{
2278 generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Minus);
2279}
2280
2281void QQmlJSTypePropagator::generate_UCompl()
2282{
2283 generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Complement);
2284}
2285
2286void QQmlJSTypePropagator::generate_Increment()
2287{
2288 generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Increment);
2289}
2290
2291void QQmlJSTypePropagator::generate_Decrement()
2292{
2293 generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Decrement);
2294}
2295
2296void QQmlJSTypePropagator::generateBinaryArithmeticOperation(QSOperator::Op op, int lhs)
2297{
2298 const QQmlJSRegisterContent type = propagateBinaryOperation(op, lhs);
2299
2300 checkConversion(checkedInputRegister(lhs), type);
2301 addReadRegister(lhs, type);
2302
2303 checkConversion(m_state.accumulatorIn(), type);
2304 addReadAccumulator(type);
2305}
2306
2307void QQmlJSTypePropagator::generateBinaryConstArithmeticOperation(QSOperator::Op op)
2308{
2309 const QQmlJSRegisterContent type = m_typeResolver->typeForBinaryOperation(
2310 op, m_state.accumulatorIn(),
2311 m_typeResolver->literalType(m_typeResolver->int32Type()));
2312
2313 checkConversion(m_state.accumulatorIn(), type);
2314 addReadAccumulator(type);
2315 setAccumulator(type);
2316}
2317
2318void QQmlJSTypePropagator::generate_Add(int lhs)
2319{
2320 generateBinaryArithmeticOperation(QSOperator::Op::Add, lhs);
2321}
2322
2323void QQmlJSTypePropagator::generate_BitAnd(int lhs)
2324{
2325 generateBinaryArithmeticOperation(QSOperator::Op::BitAnd, lhs);
2326}
2327
2328void QQmlJSTypePropagator::generate_BitOr(int lhs)
2329{
2330 generateBinaryArithmeticOperation(QSOperator::Op::BitOr, lhs);
2331}
2332
2333void QQmlJSTypePropagator::generate_BitXor(int lhs)
2334{
2335 generateBinaryArithmeticOperation(QSOperator::Op::BitXor, lhs);
2336}
2337
2338void QQmlJSTypePropagator::generate_UShr(int lhs)
2339{
2340 generateBinaryArithmeticOperation(QSOperator::Op::URShift, lhs);
2341}
2342
2343void QQmlJSTypePropagator::generate_Shr(int lhs)
2344{
2345 generateBinaryArithmeticOperation(QSOperator::Op::RShift, lhs);
2346}
2347
2348void QQmlJSTypePropagator::generate_Shl(int lhs)
2349{
2350 generateBinaryArithmeticOperation(QSOperator::Op::LShift, lhs);
2351}
2352
2353void QQmlJSTypePropagator::generate_BitAndConst(int rhsConst)
2354{
2355 Q_UNUSED(rhsConst)
2356 generateBinaryConstArithmeticOperation(QSOperator::Op::BitAnd);
2357}
2358
2359void QQmlJSTypePropagator::generate_BitOrConst(int rhsConst)
2360{
2361 Q_UNUSED(rhsConst)
2362 generateBinaryConstArithmeticOperation(QSOperator::Op::BitOr);
2363}
2364
2365void QQmlJSTypePropagator::generate_BitXorConst(int rhsConst)
2366{
2367 Q_UNUSED(rhsConst)
2368 generateBinaryConstArithmeticOperation(QSOperator::Op::BitXor);
2369}
2370
2371void QQmlJSTypePropagator::generate_UShrConst(int rhsConst)
2372{
2373 Q_UNUSED(rhsConst)
2374 generateBinaryConstArithmeticOperation(QSOperator::Op::URShift);
2375}
2376
2377void QQmlJSTypePropagator::generate_ShrConst(int rhsConst)
2378{
2379 Q_UNUSED(rhsConst)
2380 generateBinaryConstArithmeticOperation(QSOperator::Op::RShift);
2381}
2382
2383void QQmlJSTypePropagator::generate_ShlConst(int rhsConst)
2384{
2385 Q_UNUSED(rhsConst)
2386 generateBinaryConstArithmeticOperation(QSOperator::Op::LShift);
2387}
2388
2389void QQmlJSTypePropagator::generate_Exp(int lhs)
2390{
2391 generateBinaryArithmeticOperation(QSOperator::Op::Exp, lhs);
2392}
2393
2394void QQmlJSTypePropagator::generate_Mul(int lhs)
2395{
2396 generateBinaryArithmeticOperation(QSOperator::Op::Mul, lhs);
2397}
2398
2399void QQmlJSTypePropagator::generate_Div(int lhs)
2400{
2401 generateBinaryArithmeticOperation(QSOperator::Op::Div, lhs);
2402}
2403
2404void QQmlJSTypePropagator::generate_Mod(int lhs)
2405{
2406 generateBinaryArithmeticOperation(QSOperator::Op::Mod, lhs);
2407}
2408
2409void QQmlJSTypePropagator::generate_Sub(int lhs)
2410{
2411 generateBinaryArithmeticOperation(QSOperator::Op::Sub, lhs);
2412}
2413
2414void QQmlJSTypePropagator::generate_InitializeBlockDeadTemporalZone(int firstReg, int count)
2415{
2416 setAccumulator(m_typeResolver->literalType(m_typeResolver->emptyType()));
2417 for (int reg = firstReg, end = firstReg + count; reg < end; ++reg)
2418 setRegister(reg, m_typeResolver->literalType(m_typeResolver->emptyType()));
2419}
2420
2421void QQmlJSTypePropagator::generate_ThrowOnNullOrUndefined()
2422{
2424}
2425
2426void QQmlJSTypePropagator::generate_GetTemplateObject(int index)
2427{
2428 Q_UNUSED(index)
2430}
2431
2432QV4::Moth::ByteCodeHandler::Verdict
2433QQmlJSTypePropagator::startInstruction(QV4::Moth::Instr::Type type)
2434{
2435 if (m_state.jumpTargets.contains(currentInstructionOffset())) {
2436 if (m_state.skipInstructionsUntilNextJumpTarget) {
2437 // When re-surfacing from dead code, all registers are invalid.
2438 m_state.registers.clear();
2439 m_state.skipInstructionsUntilNextJumpTarget = false;
2440 }
2441 } else if (m_state.skipInstructionsUntilNextJumpTarget
2442 && !instructionManipulatesContext(type)) {
2443 return SkipInstruction;
2444 }
2445
2446 const int currentOffset = currentInstructionOffset();
2447
2448 // If we reach an instruction that is a target of a jump earlier, then we must check that the
2449 // register state at the origin matches the current state. If not, then we may have to inject
2450 // conversion code (communicated to code gen via m_state.typeConversions). For
2451 // example:
2452 //
2453 // function blah(x: number) { return x > 10 ? 10 : x}
2454 //
2455 // translates to a situation where in the "true" case, we load an integer into the accumulator
2456 // and in the else case a number (x). When the control flow is joined, the types don't match and
2457 // we need to make sure that the int is converted to a double just before the jump.
2458 for (auto originRegisterStateIt =
2459 m_jumpOriginRegisterStateByTargetInstructionOffset.constFind(currentOffset);
2460 originRegisterStateIt != m_jumpOriginRegisterStateByTargetInstructionOffset.constEnd()
2461 && originRegisterStateIt.key() == currentOffset;
2462 ++originRegisterStateIt) {
2463 auto stateToMerge = *originRegisterStateIt;
2464 for (auto registerIt = stateToMerge.registers.constBegin(),
2465 end = stateToMerge.registers.constEnd();
2466 registerIt != end; ++registerIt) {
2467 const int registerIndex = registerIt.key();
2468
2469 const VirtualRegister &newType = registerIt.value();
2470 if (!newType.content.isValid()) {
2471 addError(u"When reached from offset %1, %2 is undefined"_s
2472 .arg(stateToMerge.originatingOffset)
2473 .arg(registerName(registerIndex)));
2474 return SkipInstruction;
2475 }
2476
2477 auto currentRegister = m_state.registers.find(registerIndex);
2478 if (currentRegister != m_state.registers.end())
2479 mergeRegister(registerIndex, newType, currentRegister.value());
2480 else
2481 mergeRegister(registerIndex, newType, newType);
2482 }
2483 }
2484
2485 return ProcessInstruction;
2486}
2487
2488bool QQmlJSTypePropagator::populatesAccumulator(QV4::Moth::Instr::Type instr) const
2489{
2490 switch (instr) {
2491 case QV4::Moth::Instr::Type::CheckException:
2492 case QV4::Moth::Instr::Type::CloneBlockContext:
2493 case QV4::Moth::Instr::Type::ConvertThisToObject:
2494 case QV4::Moth::Instr::Type::CreateCallContext:
2495 case QV4::Moth::Instr::Type::DeadTemporalZoneCheck:
2496 case QV4::Moth::Instr::Type::Debug:
2497 case QV4::Moth::Instr::Type::DeclareVar:
2498 case QV4::Moth::Instr::Type::IteratorClose:
2499 case QV4::Moth::Instr::Type::IteratorNext:
2500 case QV4::Moth::Instr::Type::IteratorNextForYieldStar:
2501 case QV4::Moth::Instr::Type::Jump:
2502 case QV4::Moth::Instr::Type::JumpFalse:
2503 case QV4::Moth::Instr::Type::JumpNoException:
2504 case QV4::Moth::Instr::Type::JumpNotUndefined:
2505 case QV4::Moth::Instr::Type::JumpTrue:
2506 case QV4::Moth::Instr::Type::MoveConst:
2507 case QV4::Moth::Instr::Type::MoveReg:
2508 case QV4::Moth::Instr::Type::MoveRegExp:
2509 case QV4::Moth::Instr::Type::PopContext:
2510 case QV4::Moth::Instr::Type::PushBlockContext:
2511 case QV4::Moth::Instr::Type::PushCatchContext:
2512 case QV4::Moth::Instr::Type::PushScriptContext:
2513 case QV4::Moth::Instr::Type::Resume:
2514 case QV4::Moth::Instr::Type::Ret:
2515 case QV4::Moth::Instr::Type::SetException:
2516 case QV4::Moth::Instr::Type::SetLookup:
2517 case QV4::Moth::Instr::Type::SetUnwindHandler:
2518 case QV4::Moth::Instr::Type::StoreElement:
2519 case QV4::Moth::Instr::Type::StoreLocal:
2520 case QV4::Moth::Instr::Type::StoreNameSloppy:
2521 case QV4::Moth::Instr::Type::StoreNameStrict:
2522 case QV4::Moth::Instr::Type::StoreProperty:
2523 case QV4::Moth::Instr::Type::StoreReg:
2524 case QV4::Moth::Instr::Type::StoreScopedLocal:
2525 case QV4::Moth::Instr::Type::StoreSuperProperty:
2526 case QV4::Moth::Instr::Type::ThrowException:
2527 case QV4::Moth::Instr::Type::ThrowOnNullOrUndefined:
2528 case QV4::Moth::Instr::Type::UnwindDispatch:
2529 case QV4::Moth::Instr::Type::UnwindToLabel:
2530 case QV4::Moth::Instr::Type::Yield:
2531 case QV4::Moth::Instr::Type::YieldStar:
2532 return false;
2533 case QV4::Moth::Instr::Type::Add:
2534 case QV4::Moth::Instr::Type::As:
2535 case QV4::Moth::Instr::Type::BitAnd:
2536 case QV4::Moth::Instr::Type::BitAndConst:
2537 case QV4::Moth::Instr::Type::BitOr:
2538 case QV4::Moth::Instr::Type::BitOrConst:
2539 case QV4::Moth::Instr::Type::BitXor:
2540 case QV4::Moth::Instr::Type::BitXorConst:
2541 case QV4::Moth::Instr::Type::CallGlobalLookup:
2542 case QV4::Moth::Instr::Type::CallName:
2543 case QV4::Moth::Instr::Type::CallPossiblyDirectEval:
2544 case QV4::Moth::Instr::Type::CallProperty:
2545 case QV4::Moth::Instr::Type::CallPropertyLookup:
2546 case QV4::Moth::Instr::Type::CallQmlContextPropertyLookup:
2547 case QV4::Moth::Instr::Type::CallValue:
2548 case QV4::Moth::Instr::Type::CallWithReceiver:
2549 case QV4::Moth::Instr::Type::CallWithSpread:
2550 case QV4::Moth::Instr::Type::CmpEq:
2551 case QV4::Moth::Instr::Type::CmpEqInt:
2552 case QV4::Moth::Instr::Type::CmpEqNull:
2553 case QV4::Moth::Instr::Type::CmpGe:
2554 case QV4::Moth::Instr::Type::CmpGt:
2555 case QV4::Moth::Instr::Type::CmpIn:
2556 case QV4::Moth::Instr::Type::CmpInstanceOf:
2557 case QV4::Moth::Instr::Type::CmpLe:
2558 case QV4::Moth::Instr::Type::CmpLt:
2559 case QV4::Moth::Instr::Type::CmpNe:
2560 case QV4::Moth::Instr::Type::CmpNeInt:
2561 case QV4::Moth::Instr::Type::CmpNeNull:
2562 case QV4::Moth::Instr::Type::CmpStrictEqual:
2563 case QV4::Moth::Instr::Type::CmpStrictNotEqual:
2564 case QV4::Moth::Instr::Type::Construct:
2565 case QV4::Moth::Instr::Type::ConstructWithSpread:
2566 case QV4::Moth::Instr::Type::CreateClass:
2567 case QV4::Moth::Instr::Type::CreateMappedArgumentsObject:
2568 case QV4::Moth::Instr::Type::CreateRestParameter:
2569 case QV4::Moth::Instr::Type::CreateUnmappedArgumentsObject:
2570 case QV4::Moth::Instr::Type::Decrement:
2571 case QV4::Moth::Instr::Type::DefineArray:
2572 case QV4::Moth::Instr::Type::DefineObjectLiteral:
2573 case QV4::Moth::Instr::Type::DeleteName:
2574 case QV4::Moth::Instr::Type::DeleteProperty:
2575 case QV4::Moth::Instr::Type::DestructureRestElement:
2576 case QV4::Moth::Instr::Type::Div:
2577 case QV4::Moth::Instr::Type::Exp:
2578 case QV4::Moth::Instr::Type::GetException:
2579 case QV4::Moth::Instr::Type::GetIterator:
2580 case QV4::Moth::Instr::Type::GetLookup:
2581 case QV4::Moth::Instr::Type::GetOptionalLookup:
2582 case QV4::Moth::Instr::Type::GetTemplateObject:
2583 case QV4::Moth::Instr::Type::Increment:
2584 case QV4::Moth::Instr::Type::InitializeBlockDeadTemporalZone:
2585 case QV4::Moth::Instr::Type::LoadClosure:
2586 case QV4::Moth::Instr::Type::LoadConst:
2587 case QV4::Moth::Instr::Type::LoadElement:
2588 case QV4::Moth::Instr::Type::LoadFalse:
2589 case QV4::Moth::Instr::Type::LoadGlobalLookup:
2590 case QV4::Moth::Instr::Type::LoadImport:
2591 case QV4::Moth::Instr::Type::LoadInt:
2592 case QV4::Moth::Instr::Type::LoadLocal:
2593 case QV4::Moth::Instr::Type::LoadName:
2594 case QV4::Moth::Instr::Type::LoadNull:
2595 case QV4::Moth::Instr::Type::LoadOptionalProperty:
2596 case QV4::Moth::Instr::Type::LoadProperty:
2597 case QV4::Moth::Instr::Type::LoadQmlContextPropertyLookup:
2598 case QV4::Moth::Instr::Type::LoadReg:
2599 case QV4::Moth::Instr::Type::LoadRuntimeString:
2600 case QV4::Moth::Instr::Type::LoadScopedLocal:
2601 case QV4::Moth::Instr::Type::LoadSuperConstructor:
2602 case QV4::Moth::Instr::Type::LoadSuperProperty:
2603 case QV4::Moth::Instr::Type::LoadTrue:
2604 case QV4::Moth::Instr::Type::LoadUndefined:
2605 case QV4::Moth::Instr::Type::LoadZero:
2606 case QV4::Moth::Instr::Type::Mod:
2607 case QV4::Moth::Instr::Type::Mul:
2608 case QV4::Moth::Instr::Type::PushWithContext:
2609 case QV4::Moth::Instr::Type::Shl:
2610 case QV4::Moth::Instr::Type::ShlConst:
2611 case QV4::Moth::Instr::Type::Shr:
2612 case QV4::Moth::Instr::Type::ShrConst:
2613 case QV4::Moth::Instr::Type::Sub:
2614 case QV4::Moth::Instr::Type::TailCall:
2615 case QV4::Moth::Instr::Type::ToObject:
2616 case QV4::Moth::Instr::Type::TypeofName:
2617 case QV4::Moth::Instr::Type::TypeofValue:
2618 case QV4::Moth::Instr::Type::UCompl:
2619 case QV4::Moth::Instr::Type::UMinus:
2620 case QV4::Moth::Instr::Type::UNot:
2621 case QV4::Moth::Instr::Type::UPlus:
2622 case QV4::Moth::Instr::Type::UShr:
2623 case QV4::Moth::Instr::Type::UShrConst:
2624 return true;
2625 default:
2626 Q_UNREACHABLE_RETURN(false);
2627 }
2628}
2629
2630bool QQmlJSTypePropagator::isNoop(QV4::Moth::Instr::Type instr) const
2631{
2632 switch (instr) {
2633 case QV4::Moth::Instr::Type::DeadTemporalZoneCheck:
2634 case QV4::Moth::Instr::Type::IteratorClose:
2635 return true;
2636 default:
2637 return false;
2638 }
2639}
2640
2641void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr)
2642{
2643 InstructionAnnotation &currentInstruction = m_state.annotations[currentInstructionOffset()];
2644 currentInstruction.changedRegister = m_state.changedRegister();
2645 currentInstruction.changedRegisterIndex = m_state.changedRegisterIndex();
2646 currentInstruction.readRegisters = m_state.takeReadRegisters();
2647 currentInstruction.hasExternalSideEffects = m_state.hasExternalSideEffects();
2648 currentInstruction.hasInternalSideEffects = m_state.hasInternalSideEffects();
2649 currentInstruction.isRename = m_state.isRename();
2650
2651 bool populates = populatesAccumulator(instr);
2652 int changedIndex = m_state.changedRegisterIndex();
2653
2654 // TODO: Find a way to deal with instructions that change multiple registers
2655 if (instr != QV4::Moth::Instr::Type::InitializeBlockDeadTemporalZone) {
2656 Q_ASSERT((populates && changedIndex == Accumulator && m_state.accumulatorOut().isValid())
2657 || (!populates && changedIndex != Accumulator));
2658 }
2659
2660 if (!m_logger->currentFunctionHasCompileError() && !isNoop(instr)) {
2661 // An instruction needs to have side effects or write to another register or be a known
2662 // noop. Anything else is a problem.
2663 Q_ASSERT(m_state.hasInternalSideEffects() || changedIndex != InvalidRegister);
2664 }
2665
2666 if (changedIndex != InvalidRegister) {
2667 Q_ASSERT(m_logger->currentFunctionHasCompileError() || m_state.changedRegister().isValid());
2668 VirtualRegister &r = m_state.registers[changedIndex];
2669 r.content = m_state.changedRegister();
2670 r.canMove = false;
2671 r.affectedBySideEffects = m_state.isRename()
2672 && m_state.isRegisterAffectedBySideEffects(m_state.renameSourceRegisterIndex());
2673 m_state.clearChangedRegister();
2674 }
2675
2676 m_state.resetSideEffects();
2677 m_state.setIsRename(false);
2678 m_state.setReadRegisters(VirtualRegisters());
2679 m_state.instructionHasError = false;
2680}
2681
2682QQmlJSRegisterContent QQmlJSTypePropagator::propagateBinaryOperation(QSOperator::Op op, int lhs)
2683{
2684 auto lhsRegister = checkedInputRegister(lhs);
2685 if (!lhsRegister.isValid())
2686 return QQmlJSRegisterContent();
2687
2688 const QQmlJSRegisterContent type = m_typeResolver->typeForBinaryOperation(
2689 op, lhsRegister, m_state.accumulatorIn());
2690
2691 setAccumulator(type);
2692 return type;
2693}
2694
2695static bool deepCompare(const QQmlJSRegisterContent &a, const QQmlJSRegisterContent &b)
2696{
2697 if (!a.isValid() && !b.isValid())
2698 return true;
2699
2700 return a.containedType() == b.containedType()
2701 && a.variant() == b.variant()
2702 && deepCompare(a.scope(), b.scope());
2703}
2704
2705void QQmlJSTypePropagator::saveRegisterStateForJump(int offset)
2706{
2707 auto jumpToOffset = offset + nextInstructionOffset();
2708 ExpectedRegisterState state;
2709 state.registers = m_state.registers;
2710 state.originatingOffset = currentInstructionOffset();
2711 m_state.jumpTargets.insert(jumpToOffset);
2712 if (offset < 0) {
2713 // We're jumping backwards. We won't get to merge the register states in this pass anymore.
2714
2715 const auto registerStates =
2716 m_jumpOriginRegisterStateByTargetInstructionOffset.equal_range(jumpToOffset);
2717 for (auto it = registerStates.first; it != registerStates.second; ++it) {
2718 if (it->registers.keys() != state.registers.keys())
2719 continue;
2720
2721 const auto valuesIt = it->registers.values();
2722 const auto valuesState = state.registers.values();
2723
2724 bool different = false;
2725 for (qsizetype i = 0, end = valuesIt.size(); i != end; ++i) {
2726 const auto &valueIt = valuesIt[i];
2727 const auto &valueState = valuesState[i];
2728 if (valueIt.affectedBySideEffects != valueState.affectedBySideEffects
2729 || valueIt.canMove != valueState.canMove
2730 || valueIt.isShadowable != valueState.isShadowable
2731 || !deepCompare(valueIt.content, valueState.content)) {
2732 different = true;
2733 break;
2734 }
2735 }
2736
2737 if (!different)
2738 return; // We've seen the same register state before. No need for merging.
2739 }
2740
2741 // The register state at the target offset needs to be resolved in a further pass.
2742 m_state.needsMorePasses = true;
2743 }
2744 m_jumpOriginRegisterStateByTargetInstructionOffset.insert(jumpToOffset, state);
2745}
2746
2747QString QQmlJSTypePropagator::registerName(int registerIndex) const
2748{
2749 switch (registerIndex) {
2750 case InvalidRegister:
2751 return u"invalid"_s;
2752 case CurrentFunction:
2753 return u"function"_s;
2754 case Context:
2755 return u"context"_s;
2756 case Accumulator:
2757 return u"accumulator"_s;
2758 case This:
2759 return u"this"_s;
2760 case Argc:
2761 return u"argc"_s;
2762 case NewTarget:
2763 return u"newTarget"_s;
2764 default:
2765 break;
2766 }
2767
2768 if (isArgument(registerIndex))
2769 return u"argument %1"_s.arg(registerIndex - FirstArgument);
2770
2771 return u"temporary register %1"_s.arg(
2772 registerIndex - FirstArgument - m_function->argumentTypes.size());
2773}
2774
2775QQmlJSRegisterContent QQmlJSTypePropagator::checkedInputRegister(int reg)
2776{
2777 const auto regIt = m_state.registers.find(reg);
2778 if (regIt != m_state.registers.end())
2779 return regIt.value().content;
2780
2781 switch (reg) {
2782 case CurrentFunction:
2783 return m_typeResolver->syntheticType(m_typeResolver->functionType());
2784 case Context:
2785 return m_typeResolver->syntheticType(m_typeResolver->jsValueType());
2786 case Accumulator:
2787 addError(u"Type error: no value found in accumulator"_s);
2788 return {};
2789 case This:
2790 return m_function->qmlScope;
2791 case Argc:
2792 return m_typeResolver->syntheticType(m_typeResolver->int32Type());
2793 case NewTarget:
2794 // over-approximation: needed in qmllint to not crash on `eval()`-calls
2795 return m_typeResolver->syntheticType(m_typeResolver->varType());
2796 default:
2797 break;
2798 }
2799
2800 if (isArgument(reg))
2801 return argumentType(reg);
2802
2803 addError(u"Type error: could not infer the type of an expression"_s);
2804 return {};
2805}
2806
2807bool QQmlJSTypePropagator::canConvertFromTo(
2808 QQmlJSRegisterContent from, QQmlJSRegisterContent to)
2809{
2810 return m_typeResolver->canConvertFromTo(from, to);
2811}
2812
2813bool QQmlJSTypePropagator::canConvertFromTo(
2814 QQmlJSRegisterContent from, const QQmlJSScope::ConstPtr &to)
2815{
2816 return m_typeResolver->canConvertFromTo(from.containedType(), to);
2817}
2818
2819QT_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()