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