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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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