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

source code of qtdeclarative/src/qmlcompiler/qqmljstypepropagator.cpp