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