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