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