Revision control
Copy as Markdown
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
// Qt-Security score:significant
#include "qqmljsscope_p.h"
#include "qqmljstypepropagator_p.h"
#include "qqmljsutils_p.h"
#include <private/qv4compilerscanfunctions_p.h>
#include <QtQmlCompiler/private/qqmlsasourcelocation_p.h>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
/*!
* \internal
* \class QQmlJSTypePropagator
*
* QQmlJSTypePropagator is the initial pass that performs the type inference and
* annotates every register in use at any instruction with the possible types it
* may hold. This includes information on how and in what scope the values are
* retrieved. These annotations may be used by further compile passes for
* refinement or code generation.
*/
QQmlJSCompilePass::BlocksAndAnnotations QQmlJSTypePropagator::run(const Function *function)
{
m_function = function;
m_returnType = m_function->returnType;
// We are the only pass that merges register contents. See QQmlJSTypeResolver::m_mergeCache.
m_typeResolver->clearMergeCache();
// We cannot assume anything about how a script string will be used
if (m_returnType.containedType() == m_typeResolver->qQmlScriptStringType())
return {};
do {
// Reset the error if we need to do another pass
if (m_state.needsMorePasses)
m_logger->rollback();
m_logger->startTransaction();
m_prevStateAnnotations = m_state.annotations;
m_state = PassState();
m_state.annotations = m_annotations;
m_state.State::operator=(initialState(m_function));
reset();
decode(m_function->code.constData(), static_cast<uint>(m_function->code.size()));
// If we have found unresolved backwards jumps, we need to start over with a fresh state.
// Mind that m_jumpOriginRegisterStateByTargetInstructionOffset is retained in that case.
// This means that we won't start over for the same reason again.
} while (m_state.needsMorePasses);
m_logger->commit();
return { std::move(m_basicBlocks), std::move(m_state.annotations) };
}
#define INSTR_PROLOGUE_NOT_IMPLEMENTED() \
addError(u"Instruction \"%1\" not implemented"_s.arg(QString::fromUtf8(__func__))); \
return;
#define INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC() \
addError(u"Instruction \"%1\" not implemented"_s.arg(QString::fromUtf8(__func__))); \
setVarAccumulatorAndError(); /* Keep sane state after error */ \
return;
#define INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE() \
m_logger->log(u"Instruction \"%1\" not implemented"_s.arg(QString::fromUtf8(__func__)), \
qmlCompiler, QQmlJS::SourceLocation()); \
return;
void QQmlJSTypePropagator::generate_Ret()
{
if (m_function->isSignalHandler) {
// Signal handlers cannot return anything.
} else if (m_state.accumulatorIn().contains(m_typeResolver->voidType())) {
// You can always return undefined.
} else if (!m_returnType.isValid() && m_state.accumulatorIn().isValid()) {
addError(u"function without return type annotation returns %1. This may prevent proper "_s
u"compilation to Cpp."_s.arg(m_state.accumulatorIn().descriptiveName()));
return;
} else if (!canConvertFromTo(m_state.accumulatorIn(), m_returnType)) {
addError(u"cannot convert from %1 to %2"_s
.arg(m_state.accumulatorIn().descriptiveName(),
m_returnType.descriptiveName()));
return;
}
if (m_returnType.isValid()) {
// We need to preserve any possible undefined value as that resets the property.
if (m_typeResolver->canHoldUndefined(m_state.accumulatorIn()))
addReadAccumulator();
else
addReadAccumulator(m_returnType);
}
m_state.setHasInternalSideEffects();
m_state.skipInstructionsUntilNextJumpTarget = true;
}
void QQmlJSTypePropagator::generate_Debug()
{
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
void QQmlJSTypePropagator::generate_LoadConst(int index)
{
auto encodedConst = m_jsUnitGenerator->constant(index);
setAccumulator(m_typeResolver->literalType(m_typeResolver->typeForConst(encodedConst)));
}
void QQmlJSTypePropagator::generate_LoadZero()
{
setAccumulator(m_typeResolver->literalType(m_typeResolver->int32Type()));
}
void QQmlJSTypePropagator::generate_LoadTrue()
{
setAccumulator(m_typeResolver->literalType(m_typeResolver->boolType()));
}
void QQmlJSTypePropagator::generate_LoadFalse()
{
setAccumulator(m_typeResolver->literalType(m_typeResolver->boolType()));
}
void QQmlJSTypePropagator::generate_LoadNull()
{
setAccumulator(m_typeResolver->literalType(m_typeResolver->nullType()));
}
void QQmlJSTypePropagator::generate_LoadUndefined()
{
setAccumulator(m_typeResolver->literalType(m_typeResolver->voidType()));
}
void QQmlJSTypePropagator::generate_LoadInt(int)
{
setAccumulator(m_typeResolver->literalType(m_typeResolver->int32Type()));
}
void QQmlJSTypePropagator::generate_MoveConst(int constIndex, int destTemp)
{
auto encodedConst = m_jsUnitGenerator->constant(constIndex);
setRegister(destTemp, m_typeResolver->literalType(m_typeResolver->typeForConst(encodedConst)));
}
void QQmlJSTypePropagator::generate_LoadReg(int reg)
{
// Do not re-track the register. We're not manipulating it.
m_state.setIsRename(true);
const QQmlJSRegisterContent content = checkedInputRegister(reg);
m_state.addReadRegister(reg, content);
m_state.setRegister(Accumulator, content);
}
void QQmlJSTypePropagator::generate_StoreReg(int reg)
{
// Do not re-track the register. We're not manipulating it.
m_state.setIsRename(true);
m_state.addReadAccumulator(m_state.accumulatorIn());
m_state.setRegister(reg, m_state.accumulatorIn());
}
void QQmlJSTypePropagator::generate_MoveReg(int srcReg, int destReg)
{
Q_ASSERT(destReg != InvalidRegister);
// Do not re-track the register. We're not manipulating it.
m_state.setIsRename(true);
const QQmlJSRegisterContent content = checkedInputRegister(srcReg);
m_state.addReadRegister(srcReg, content);
m_state.setRegister(destReg, content);
}
void QQmlJSTypePropagator::generate_LoadImport(int index)
{
Q_UNUSED(index)
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_LoadLocal(int index)
{
// TODO: In order to accurately track locals we'd need to track JavaScript contexts first.
// This could be done by populating the initial JS context and implementing the various
// Push and Pop operations. For now, this is pretty barren.
QQmlJSMetaProperty local;
local.setType(m_typeResolver->jsValueType());
local.setIndex(index);
setAccumulator(m_pool->createProperty(
local, QQmlJSRegisterContent::InvalidLookupIndex,
QQmlJSRegisterContent::InvalidLookupIndex,
QQmlJSRegisterContent::Property, QQmlJSRegisterContent()));
}
void QQmlJSTypePropagator::generate_StoreLocal(int index)
{
Q_UNUSED(index)
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
void QQmlJSTypePropagator::generate_LoadScopedLocal(int scope, int index)
{
Q_UNUSED(scope)
Q_UNUSED(index)
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_StoreScopedLocal(int scope, int index)
{
Q_UNUSED(scope)
Q_UNUSED(index)
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
void QQmlJSTypePropagator::generate_LoadRuntimeString(int stringId)
{
Q_UNUSED(stringId)
setAccumulator(m_typeResolver->literalType(m_typeResolver->stringType()));
}
void QQmlJSTypePropagator::generate_MoveRegExp(int regExpId, int destReg)
{
Q_UNUSED(regExpId)
m_state.setRegister(destReg, m_typeResolver->literalType(m_typeResolver->regexpType()));
}
void QQmlJSTypePropagator::generate_LoadClosure(int value)
{
Q_UNUSED(value)
// TODO: Check the function at index and see whether it's a generator to return another type
// instead.
setAccumulator(m_typeResolver->literalType(m_typeResolver->functionType()));
}
void QQmlJSTypePropagator::generate_LoadName(int nameIndex)
{
const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
setAccumulator(m_typeResolver->scopedType(m_function->qmlScope, name));
if (!m_state.accumulatorOut().isValid()) {
addError(u"Cannot find name "_s + name);
setVarAccumulatorAndError();
}
}
void QQmlJSTypePropagator::generate_LoadGlobalLookup(int index)
{
generate_LoadName(m_jsUnitGenerator->lookupNameIndex(index));
}
void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isMethod) const
{
Q_UNUSED(name);
Q_UNUSED(isMethod);
}
void QQmlJSTypePropagator::handleUnqualifiedAccessAndContextProperties(
const QString &name, bool isMethod) const
{
Q_UNUSED(name);
Q_UNUSED(isMethod);
}
void QQmlJSTypePropagator::checkDeprecated(QQmlJSScope::ConstPtr scope, const QString &name,
bool isMethod) const
{
Q_UNUSED(scope);
Q_UNUSED(name);
Q_UNUSED(isMethod);
}
bool QQmlJSTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope, const QString &name) const
{
const auto property = scope->property(name);
return property.isValid();
}
void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index)
{
// LoadQmlContextPropertyLookup does not use accumulatorIn. It always refers to the scope.
// Any import namespaces etc. are handled via LoadProperty or GetLookup.
const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index);
const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
setAccumulator(m_typeResolver->scopedType(m_function->qmlScope, name, index));
if (!m_state.accumulatorOut().isValid() && m_typeResolver->isPrefix(name)) {
setAccumulator(m_pool->createImportNamespace(
nameIndex, m_typeResolver->voidType(), QQmlJSRegisterContent::ModulePrefix,
m_function->qmlScope));
return;
}
checkDeprecated(m_function->qmlScope.containedType(), name, false);
const QQmlJSRegisterContent accumulatorOut = m_state.accumulatorOut();
if (!accumulatorOut.isValid()) {
addError(u"Cannot access value for name "_s + name);
handleUnqualifiedAccessAndContextProperties(name, false);
setVarAccumulatorAndError();
return;
}
const QQmlJSScope::ConstPtr retrieved
= m_typeResolver->genericType(accumulatorOut.containedType());
if (retrieved.isNull()) {
// It should really be valid.
// We get the generic type from aotContext->loadQmlContextPropertyIdLookup().
addError(u"Cannot determine generic type for "_s + name);
return;
}
if (accumulatorOut.variant() == QQmlJSRegisterContent::ObjectById
&& !retrieved->isReferenceType()) {
addError(u"Cannot retrieve a non-object type by ID: "_s + name);
return;
}
}
/*!
\internal
As far as type propagation is involved, StoreNameSloppy and
StoreNameStrict are completely the same
StoreNameStrict is rejecting a few writes (where the variable was not
defined before) that would work in a sloppy context in JS, but the
compiler would always reject this. And for type propagation, this does
not matter at all.
\a nameIndex is the index in the string table corresponding to
the name which we are storing
*/
void QQmlJSTypePropagator::generate_StoreNameCommon(int nameIndex)
{
const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
const QQmlJSRegisterContent type = m_typeResolver->scopedType(m_function->qmlScope, name);
const QQmlJSRegisterContent in = m_state.accumulatorIn();
if (!type.isValid()) {
handleUnqualifiedAccess(name, false);
addError(u"Cannot find name "_s + name);
return;
}
if (!type.isProperty()) {
QString message = type.isMethod() ? u"Cannot assign to method %1"_s
: u"Cannot assign to non-property %1"_s;
// The interpreter treats methods as read-only properties in its error messages
// and we lack a better fitting category. We might want to revisit this later.
m_logger->log(message.arg(name), qmlReadOnlyProperty,
currentSourceLocation());
addError(u"Cannot assign to non-property "_s + name);
return;
}
if (!type.isWritable() && !type.isList()) {
addError(u"Can't assign to read-only property %1"_s.arg(name));
m_logger->log(u"Cannot assign to read-only property %1"_s.arg(name), qmlReadOnlyProperty,
currentSourceLocation());
return;
}
if (!canConvertFromTo(in, type)) {
addError(u"cannot convert from %1 to %2"_s
.arg(in.descriptiveName(), type.descriptiveName()));
}
if (m_typeResolver->canHoldUndefined(in) && !m_typeResolver->canHoldUndefined(type)) {
if (in.contains(m_typeResolver->voidType()))
addReadAccumulator(m_typeResolver->varType());
else
addReadAccumulator();
} else {
addReadAccumulator(type);
}
m_state.setHasExternalSideEffects();
}
void QQmlJSTypePropagator::generate_StoreNameSloppy(int nameIndex)
{
return generate_StoreNameCommon(nameIndex);
}
void QQmlJSTypePropagator::generate_StoreNameStrict(int name)
{
return generate_StoreNameCommon(name);
}
bool QQmlJSTypePropagator::checkForEnumProblems(
QQmlJSRegisterContent base, const QString &propertyName)
{
if (base.isEnumeration()) {
const auto metaEn = base.enumeration();
if (!metaEn.hasKey(propertyName)) {
addError(u"\"%1\" is not an entry of enum \"%2\"."_s
.arg(propertyName, metaEn.name()));
return true;
}
}
return false;
}
void QQmlJSTypePropagator::generate_LoadElement(int base)
{
const QQmlJSRegisterContent in = m_state.accumulatorIn();
const QQmlJSRegisterContent baseRegister = m_state.registers[base].content;
const auto fallback = [&]() {
const QQmlJSScope::ConstPtr jsValue = m_typeResolver->jsValueType();
addReadAccumulator(jsValue);
addReadRegister(base, jsValue);
QQmlJSMetaProperty property;
property.setPropertyName(u"[]"_s);
property.setTypeName(jsValue->internalName());
property.setType(jsValue);
setAccumulator(m_pool->createProperty(
property, QQmlJSRegisterContent::InvalidLookupIndex,
QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::ListValue,
m_typeResolver->convert(m_typeResolver->elementType(baseRegister), jsValue)));
};
if (baseRegister.isList()) {
addReadRegister(base, m_typeResolver->arrayPrototype());
} else if (baseRegister.contains(m_typeResolver->stringType())) {
addReadRegister(base, m_typeResolver->stringType());
} else {
fallback();
return;
}
if (m_typeResolver->isNumeric(in)) {
const auto contained = in.containedType();
if (m_typeResolver->isSignedInteger(contained))
addReadAccumulator(m_typeResolver->sizeType());
else if (m_typeResolver->isUnsignedInteger(contained))
addReadAccumulator(m_typeResolver->uint32Type());
else
addReadAccumulator(m_typeResolver->realType());
} else if (m_typeResolver->isNumeric(m_typeResolver->extractNonVoidFromOptionalType(in))) {
addReadAccumulator();
} else {
fallback();
return;
}
// We can end up with undefined.
setAccumulator(m_typeResolver->merge(
m_typeResolver->elementType(baseRegister),
m_typeResolver->literalType(m_typeResolver->voidType())));
}
void QQmlJSTypePropagator::generate_StoreElement(int base, int index)
{
const QQmlJSRegisterContent baseRegister = m_state.registers[base].content;
const QQmlJSRegisterContent indexRegister = checkedInputRegister(index);
if (!baseRegister.isList()
|| !m_typeResolver->isNumeric(indexRegister)) {
const auto jsValue = m_typeResolver->jsValueType();
addReadAccumulator(jsValue);
addReadRegister(base, jsValue);
addReadRegister(index, jsValue);
// Writing to a JS array can have side effects all over the place since it's
// passed by reference.
m_state.setHasExternalSideEffects();
return;
}
const auto contained = indexRegister.containedType();
if (m_typeResolver->isSignedInteger(contained))
addReadRegister(index, m_typeResolver->int32Type());
else if (m_typeResolver->isUnsignedInteger(contained))
addReadRegister(index, m_typeResolver->uint32Type());
else
addReadRegister(index, m_typeResolver->realType());
addReadRegister(base, m_typeResolver->arrayPrototype());
addReadAccumulator(m_typeResolver->elementType(baseRegister));
// If we're writing a QQmlListProperty backed by a container somewhere else,
// that has side effects.
// If we're writing to a list retrieved from a property, that _should_ have side effects,
// but currently the QML engine doesn't implement them.
// TODO: Figure out the above and accurately set the flag.
m_state.setHasExternalSideEffects();
}
bool QQmlJSTypePropagator::handleImportNamespaceLookup(const QString &propertyName)
{
const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
if (m_typeResolver->isPrefix(propertyName)) {
Q_ASSERT(accumulatorIn.isValid());
if (!accumulatorIn.containedType()->isReferenceType()) {
setVarAccumulatorAndError();
return true;
}
addReadAccumulator();
setAccumulator(m_pool->createImportNamespace(
m_jsUnitGenerator->getStringId(propertyName),
accumulatorIn.containedType(),
QQmlJSRegisterContent::ModulePrefix,
accumulatorIn));
return true;
}
return false;
}
void QQmlJSTypePropagator::handleLookupError(const QString &propertyName)
{
const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
setVarAccumulatorAndError();
if (checkForEnumProblems(accumulatorIn, propertyName))
return;
addError(u"Cannot load property %1 from %2."_s
.arg(propertyName, accumulatorIn.descriptiveName()));
}
void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName, int lookupIndex)
{
const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
setAccumulator(
m_typeResolver->memberType(
accumulatorIn,
accumulatorIn.isImportNamespace()
? m_jsUnitGenerator->stringForIndex(accumulatorIn.importNamespace())
+ u'.' + propertyName
: propertyName, lookupIndex));
if (!m_state.accumulatorOut().isValid() && handleImportNamespaceLookup(propertyName))
return;
if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::Singleton
&& accumulatorIn.variant() == QQmlJSRegisterContent::ModulePrefix
&& !isQmlScopeObject(accumulatorIn.scope())) {
m_logger->log(
u"Cannot access singleton as a property of an object. Did you want to access an attached object?"_s,
qmlAccessSingleton, currentSourceLocation());
setAccumulator(QQmlJSRegisterContent());
} else if (m_state.accumulatorOut().isEnumeration()) {
switch (accumulatorIn.variant()) {
case QQmlJSRegisterContent::MetaType:
case QQmlJSRegisterContent::Attachment:
case QQmlJSRegisterContent::Enum:
case QQmlJSRegisterContent::ModulePrefix:
case QQmlJSRegisterContent::Singleton:
break; // OK, can look up enums on that thing
default:
setAccumulator(QQmlJSRegisterContent());
}
}
if (m_state.instructionHasError || !m_state.accumulatorOut().isValid()) {
handleLookupError(propertyName);
return;
}
if (m_state.accumulatorOut().isMethod() && m_state.accumulatorOut().method().size() != 1) {
addError(u"Cannot determine overloaded method on loadProperty"_s);
return;
}
if (m_state.accumulatorOut().isProperty()) {
const QQmlJSScope::ConstPtr mathObject
= m_typeResolver->jsGlobalObject()->property(u"Math"_s).type();
if (accumulatorIn.contains(mathObject)) {
QQmlJSMetaProperty prop;
prop.setPropertyName(propertyName);
prop.setTypeName(u"double"_s);
prop.setType(m_typeResolver->realType());
setAccumulator(
m_pool->createProperty(
prop, accumulatorIn.resultLookupIndex(), lookupIndex,
// Use pre-determined scope type here to avoid adjusting it later.
QQmlJSRegisterContent::Property, m_state.accumulatorOut().scope())
);
return;
}
if (m_state.accumulatorOut().contains(m_typeResolver->voidType())) {
addError(u"Type %1 does not have a property %2 for reading"_s
.arg(accumulatorIn.descriptiveName(), propertyName));
return;
}
if (!m_state.accumulatorOut().property().type()) {
m_logger->log(
QString::fromLatin1("Type of property \"%2\" not found").arg(propertyName),
qmlMissingType, currentSourceLocation());
}
}
switch (m_state.accumulatorOut().variant()) {
case QQmlJSRegisterContent::Enum:
case QQmlJSRegisterContent::Singleton:
// For reading enums or singletons, we don't need to access anything, unless it's an
// import namespace. Then we need the name.
if (accumulatorIn.isImportNamespace())
addReadAccumulator();
break;
default:
addReadAccumulator();
break;
}
}
void QQmlJSTypePropagator::generate_LoadProperty(int nameIndex)
{
propagatePropertyLookup(m_jsUnitGenerator->stringForIndex(nameIndex));
}
void QQmlJSTypePropagator::generate_LoadOptionalProperty(int name, int offset)
{
Q_UNUSED(name);
Q_UNUSED(offset);
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_GetLookup(int index)
{
propagatePropertyLookup(m_jsUnitGenerator->lookupName(index), index);
}
void QQmlJSTypePropagator::generate_GetOptionalLookup(int index, int offset)
{
Q_UNUSED(offset);
saveRegisterStateForJump(offset);
propagatePropertyLookup(m_jsUnitGenerator->lookupName(index), index);
}
void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base)
{
auto callBase = m_state.registers[base].content;
const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
QQmlJSRegisterContent property = m_typeResolver->memberType(callBase, propertyName);
if (!property.isProperty()) {
QString name = callBase.descriptiveName();
if (!name.isEmpty())
name += u' ';
addError(u"Type %1does not have a property %2 for writing"_s.arg(name, propertyName));
return;
}
if (property.containedType().isNull()) {
addError(u"Cannot determine type for property %1 of type %2"_s.arg(
propertyName, callBase.descriptiveName()));
return;
}
if (!property.isWritable() && !property.containedType()->isListProperty()) {
addError(u"Can't assign to read-only property %1"_s.arg(propertyName));
m_logger->log(u"Cannot assign to read-only property %1"_s.arg(propertyName),
qmlReadOnlyProperty, currentSourceLocation());
return;
}
if (!canConvertFromTo(m_state.accumulatorIn(), property)) {
addError(u"cannot convert from %1 to %2"_s
.arg(m_state.accumulatorIn().descriptiveName(), property.descriptiveName()));
return;
}
// If the input can hold undefined we must not coerce it to the property type
// as that might eliminate an undefined value. For example, undefined -> string
// becomes "undefined".
// We need the undefined value for either resetting the property if that is supported
// or generating an exception otherwise. Therefore we explicitly require the value to
// be given as QVariant. This triggers the QVariant fallback path that's also used for
// shadowable properties. QVariant can hold undefined and the lookup functions will
// handle that appropriately.
const QQmlJSScope::ConstPtr varType = m_typeResolver->varType();
const QQmlJSRegisterContent readType = m_typeResolver->canHoldUndefined(m_state.accumulatorIn())
? m_typeResolver->convert(property, varType)
: std::move(property);
addReadAccumulator(readType);
addReadRegister(base);
m_state.setHasExternalSideEffects();
}
void QQmlJSTypePropagator::generate_SetLookup(int index, int base)
{
generate_StoreProperty(m_jsUnitGenerator->lookupNameIndex(index), base);
}
void QQmlJSTypePropagator::generate_LoadSuperProperty(int property)
{
Q_UNUSED(property)
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_StoreSuperProperty(int property)
{
Q_UNUSED(property)
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
void QQmlJSTypePropagator::generate_Yield()
{
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
void QQmlJSTypePropagator::generate_YieldStar()
{
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
void QQmlJSTypePropagator::generate_Resume(int)
{
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
void QQmlJSTypePropagator::generate_CallValue(int name, int argc, int argv)
{
m_state.setHasExternalSideEffects();
Q_UNUSED(name)
Q_UNUSED(argc)
Q_UNUSED(argv)
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_CallWithReceiver(int name, int thisObject, int argc, int argv)
{
m_state.setHasExternalSideEffects();
Q_UNUSED(name)
Q_UNUSED(thisObject)
Q_UNUSED(argc)
Q_UNUSED(argv)
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
bool QQmlJSTypePropagator::isLoggingMethod(const QString &consoleMethod)
{
return consoleMethod == u"log" || consoleMethod == u"debug" || consoleMethod == u"info"
|| consoleMethod == u"warn" || consoleMethod == u"error";
}
void QQmlJSTypePropagator::generate_CallProperty_SCMath(
const QString &name, int base, int argc, int argv)
{
// If we call a method on the Math object we don't need the actual Math object. We do need
// to transfer the type information to the code generator so that it knows that this is the
// Math object. Read the base register as void. void isn't stored, and the place where it's
// created will be optimized out if there are no other readers. The code generator can
// retrieve the original type and determine that it was the Math object.
addReadRegister(base, m_typeResolver->voidType());
QQmlJSRegisterContent math = m_state.registers[base].content;
const QList<QQmlJSMetaMethod> methods = math.containedType()->ownMethods(name);
if (methods.isEmpty()) {
setVarAccumulatorAndError();
std::optional<QQmlJSFixSuggestion> fixSuggestion = QQmlJSUtils::didYouMean(
name, math.containedType()->methods().keys(), m_logger->filePath(),
currentSourceLocation());
m_logger->log(u"Member \"%1\" not found on Math object"_s.arg(name),
qmlMissingProperty, currentSourceLocation(),
true, true, std::move(fixSuggestion));
return;
}
Q_ASSERT(methods.length() == 1);
// Declare the Math object as base type of itself so that it gets cloned and won't be
// adjusted later. This is what we do with all method calls.
QQmlJSRegisterContent realType = m_typeResolver->returnType(
methods[0], m_typeResolver->realType(),
m_typeResolver->baseType(math.containedType(), math));
for (int i = 0; i < argc; ++i)
addReadRegister(argv + i, realType);
setAccumulator(realType);
}
void QQmlJSTypePropagator::generate_CallProperty_SCconsole(
const QString &name, int base, int argc, int argv)
{
// If we call a method on the console object we don't need the console object.
addReadRegister(base, m_typeResolver->voidType());
if (argc > 0) {
const QQmlJSRegisterContent firstContent = m_state.registers[argv].content;
const QQmlJSScope::ConstPtr firstArg = firstContent.containedType();
switch (firstArg->accessSemantics()) {
case QQmlJSScope::AccessSemantics::Reference:
// We cannot know whether this will be a logging category at run time.
// Therefore we always pass any object types as special last argument.
addReadRegister(argv, m_typeResolver->genericType(firstArg));
break;
case QQmlJSScope::AccessSemantics::Sequence:
addReadRegister(argv);
break;
default:
addReadRegister(argv, m_typeResolver->stringType());
break;
}
}
for (int i = 1; i < argc; ++i) {
const QQmlJSRegisterContent argContent = m_state.registers[argv + i].content;
const QQmlJSScope::ConstPtr arg = argContent.containedType();
if (arg->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence)
addReadRegister(argv + i);
else
addReadRegister(argv + i, m_typeResolver->stringType());
}
// It's debatable whether the console API should be considered an external side effect.
// You can certainly qInstallMessageHandler and then react to the message and change
// some property in an object exposed to the currently running method. However, we might
// disregard such a thing as abuse of the API. For now, the console API is considered to
// have external side effects, though.
m_state.setHasExternalSideEffects();
QQmlJSRegisterContent console = m_state.registers[base].content;
QList<QQmlJSMetaMethod> methods = console.containedType()->ownMethods(name);
Q_ASSERT(methods.length() == 1);
// Declare the console object as base type of itself so that it gets cloned and won't be
// adjusted later. This is what we do with all method calls.
setAccumulator(m_typeResolver->returnType(
methods[0], m_typeResolver->voidType(),
m_typeResolver->baseType(console.containedType(), console)));
}
void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int argc, int argv)
{
Q_ASSERT(m_state.registers.contains(base));
const auto callBase = m_state.registers[base].content;
const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
if (callBase.contains(m_typeResolver->mathObject())) {
generate_CallProperty_SCMath(propertyName, base, argc, argv);
return;
}
if (callBase.contains(m_typeResolver->consoleObject()) && isLoggingMethod(propertyName)) {
generate_CallProperty_SCconsole(propertyName, base, argc, argv);
return;
}
const auto baseType = callBase.containedType();
const auto member = m_typeResolver->memberType(callBase, propertyName);
if (!member.isMethod()) {
if (callBase.contains(m_typeResolver->jsValueType())
|| callBase.contains(m_typeResolver->varType())) {
const auto jsValueType = m_typeResolver->jsValueType();
addReadRegister(base, jsValueType);
for (int i = 0; i < argc; ++i)
addReadRegister(argv + i, jsValueType);
m_state.setHasExternalSideEffects();
QQmlJSMetaMethod method;
method.setIsJavaScriptFunction(true);
method.setMethodName(propertyName);
method.setMethodType(QQmlJSMetaMethod::MethodType::Method);
setAccumulator(m_typeResolver->returnType(
method, m_typeResolver->jsValueType(), callBase));
return;
}
setVarAccumulatorAndError();
addError(u"Type %1 does not have a property %2 for calling"_s
.arg(callBase.descriptiveName(), propertyName));
if (callBase.isType() && isCallingProperty(callBase.type(), propertyName))
return;
if (checkForEnumProblems(callBase, propertyName))
return;
std::optional<QQmlJSFixSuggestion> fixSuggestion;
if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->methods().keys(),
m_logger->filePath(), currentSourceLocation());
suggestion.has_value()) {
fixSuggestion = suggestion;
}
if (baseType->isFullyResolved() || baseType->isScript()) {
m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(
propertyName, callBase.containedTypeName()),
qmlMissingProperty, currentSourceLocation(), true, true, fixSuggestion);
}
return;
}
checkDeprecated(baseType, propertyName, true);
addReadRegister(base);
if (callBase.contains(m_typeResolver->stringType())) {
if (propertyName == u"arg"_s && argc == 1) {
propagateStringArgCall(callBase, argv);
return;
}
}
if (baseType->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
&& member.scope().contains(m_typeResolver->arrayPrototype())
&& propagateArrayMethod(propertyName, argc, argv, callBase)) {
return;
}
propagateCall(member.method(), argc, argv, member.scope());
}
QQmlJSMetaMethod QQmlJSTypePropagator::bestMatchForCall(const QList<QQmlJSMetaMethod> &methods,
int argc, int argv, QStringList *errors)
{
QQmlJSMetaMethod javascriptFunction;
QQmlJSMetaMethod candidate;
bool hasMultipleCandidates = false;
for (const auto &method : methods) {
// If we encounter a JavaScript function, use this as a fallback if no other method matches
if (method.isJavaScriptFunction() && !javascriptFunction.isValid())
javascriptFunction = method;
if (method.returnType().isNull() && !method.returnTypeName().isEmpty()) {
errors->append(u"return type %1 cannot be resolved"_s
.arg(method.returnTypeName()));
continue;
}
const auto arguments = method.parameters();
if (argc != arguments.size()) {
errors->append(
u"Function expects %1 arguments, but %2 were provided"_s.arg(arguments.size())
.arg(argc));
continue;
}
bool fuzzyMatch = true;
bool exactMatch = true;
for (int i = 0; i < argc; ++i) {
const auto argumentType = arguments[i].type();
if (argumentType.isNull()) {
errors->append(
u"type %1 for argument %2 cannot be resolved"_s.arg(arguments[i].typeName())
.arg(i));
exactMatch = false;
fuzzyMatch = false;
break;
}
const auto content = m_state.registers[argv + i].content;
if (content.contains(argumentType))
continue;
exactMatch = false;
if (canConvertFromTo(content, argumentType))
continue;
// We can try to call a method that expects a derived type.
if (argumentType->isReferenceType()
&& m_typeResolver->inherits(
argumentType->baseType(), content.containedType())) {
continue;
}
errors->append(
u"argument %1 contains %2 but is expected to contain the type %3"_s.arg(i).arg(
content.descriptiveName(), arguments[i].typeName()));
fuzzyMatch = false;
break;
}
if (exactMatch) {
return method;
} else if (fuzzyMatch) {
if (!candidate.isValid())
candidate = method;
else
hasMultipleCandidates = true;
}
}
if (hasMultipleCandidates)
return QQmlJSMetaMethod();
return candidate.isValid() ? candidate : javascriptFunction;
}
void QQmlJSTypePropagator::setAccumulator(QQmlJSRegisterContent content)
{
setRegister(Accumulator, content);
}
void QQmlJSTypePropagator::setRegister(int index, QQmlJSRegisterContent content)
{
// If we've come to the same conclusion before, let's not track the type again.
auto it = m_prevStateAnnotations.find(currentInstructionOffset());
if (it != m_prevStateAnnotations.end()) {
QQmlJSRegisterContent lastTry = it->second.changedRegister;
if (lastTry.contains(content.containedType())) {
m_state.setRegister(index, lastTry);
return;
}
}
m_state.setRegister(index, content);
}
/*! \internal
* Merges the types of two variations of the register at \index
* When the code branches and merges, the same register can carry one of multiple types.
* For example:
*
* let a
* if (something)
* a = 12
* else
* a = "stringstring"
* console.log(a)
*
* At the point where we log the value we need a type that can hold both, the number and
* the string because we don't know which branch was taken before. mergeRegister chooses a
* type that can hold both variants.
*
* Since the type propagator can run multiple passes over the same code (for loops with back
* jumps), we need to reproduce previous resolutions of the merge where they still fit. To that
* effect, we check m_prevStateAnnotations here.
*/
void QQmlJSTypePropagator::mergeRegister(
int index, const VirtualRegister &a, const VirtualRegister &b)
{
const VirtualRegister merged = {
(a.content == b.content) ? a.content : m_typeResolver->merge(a.content, b.content),
a.canMove && b.canMove,
a.affectedBySideEffects || b.affectedBySideEffects,
a.isShadowable || b.isShadowable,
};
Q_ASSERT(merged.content.isValid());
if (!merged.content.isConversion()) {
// The registers were the same. We're already tracking them.
m_state.annotations[currentInstructionOffset()].typeConversions[index] = merged;
m_state.registers[index] = merged;
return;
}
auto tryPrevStateConversion = [this](int index, const VirtualRegister &merged) -> bool {
auto it = m_prevStateAnnotations.find(currentInstructionOffset());
if (it == m_prevStateAnnotations.end())
return false;
auto conversion = it->second.typeConversions.find(index);
if (conversion == it->second.typeConversions.end())
return false;
const VirtualRegister &lastTry = conversion.value();
Q_ASSERT(lastTry.content.isValid());
if (!lastTry.content.isConversion())
return false;
if (lastTry.content.conversionResultType() != merged.content.conversionResultType()
|| lastTry.content.conversionOrigins() != merged.content.conversionOrigins()
|| lastTry.canMove != merged.canMove
|| lastTry.affectedBySideEffects != merged.affectedBySideEffects
|| lastTry.isShadowable != merged.isShadowable) {
return false;
}
// We don't need to track it again if we've come to the same conclusion before.
m_state.annotations[currentInstructionOffset()].typeConversions[index] = lastTry;
// Do not reset the side effects
Q_ASSERT(!m_state.registers[index].affectedBySideEffects || lastTry.affectedBySideEffects);
m_state.registers[index] = lastTry;
return true;
};
if (!tryPrevStateConversion(index, merged)) {
// if a != b, we have already re-tracked it.
const VirtualRegister cloned = {
(a == b) ? m_pool->clone(merged.content) : merged.content,
merged.canMove,
merged.affectedBySideEffects,
merged.isShadowable,
};
Q_ASSERT(cloned.content.isValid());
m_state.annotations[currentInstructionOffset()].typeConversions[index] = cloned;
m_state.registers[index] = cloned;
}
}
void QQmlJSTypePropagator::addReadRegister(int index)
{
// Explicitly pass the same type through without conversion
m_state.addReadRegister(index, m_state.registers[index].content);
}
void QQmlJSTypePropagator::addReadRegister(int index, QQmlJSRegisterContent convertTo)
{
if (m_state.registers[index].content == convertTo) {
// Explicitly pass the same type through without conversion
m_state.addReadRegister(index, convertTo);
} else {
m_state.addReadRegister(
index, m_typeResolver->convert(m_state.registers[index].content, convertTo));
}
}
void QQmlJSTypePropagator::addReadRegister(int index, const QQmlJSScope::ConstPtr &convertTo)
{
m_state.addReadRegister(
index, m_typeResolver->convert(m_state.registers[index].content, convertTo));
}
void QQmlJSTypePropagator::propagateCall(
const QList<QQmlJSMetaMethod> &methods, int argc, int argv,
QQmlJSRegisterContent scope)
{
QStringList errors;
const QQmlJSMetaMethod match = bestMatchForCall(methods, argc, argv, &errors);
if (!match.isValid()) {
setVarAccumulatorAndError();
if (methods.size() == 1) {
// Cannot have multiple fuzzy matches if there is only one method
Q_ASSERT(errors.size() == 1);
addError(errors.first());
} else if (errors.size() < methods.size()) {
addError(u"Multiple matching overrides found. Cannot determine the right one."_s);
} else {
addError(u"No matching override found. Candidates:\n"_s + errors.join(u'\n'));
}
return;
}
QQmlJSScope::ConstPtr returnType;
if (match.isJavaScriptFunction())
returnType = m_typeResolver->jsValueType();
else if (match.isConstructor())
returnType = scope.containedType();
else
returnType = match.returnType();
setAccumulator(m_typeResolver->returnType(match, returnType, scope));
if (!m_state.accumulatorOut().isValid())
addError(u"Cannot store return type of method %1()."_s.arg(match.methodName()));
const auto types = match.parameters();
for (int i = 0; i < argc; ++i) {
if (i < types.size()) {
const QQmlJSScope::ConstPtr type = match.isJavaScriptFunction()
? m_typeResolver->jsValueType()
: QQmlJSScope::ConstPtr(types.at(i).type());
if (!type.isNull()) {
addReadRegister(argv + i, type);
continue;
}
}
addReadRegister(argv + i, m_typeResolver->jsValueType());
}
m_state.setHasExternalSideEffects();
}
void QQmlJSTypePropagator::propagateTranslationMethod_SAcheck(const QString &methodName)
{
Q_UNUSED(methodName);
}
bool QQmlJSTypePropagator::propagateTranslationMethod(
const QList<QQmlJSMetaMethod> &methods, int argc, int argv)
{
if (methods.size() != 1)
return false;
const QQmlJSMetaMethod method = methods.front();
const QQmlJSScope::ConstPtr intType = m_typeResolver->int32Type();
const QQmlJSScope::ConstPtr stringType = m_typeResolver->stringType();
const QQmlJSRegisterContent returnType = m_typeResolver->returnType(
method, m_typeResolver->stringType(), m_typeResolver->jsGlobalObjectContent());
if (method.methodName() == u"qsTranslate"_s) {
switch (argc) {
case 4:
addReadRegister(argv + 3, intType); // n
Q_FALLTHROUGH();
case 3:
addReadRegister(argv + 2, stringType); // disambiguation
Q_FALLTHROUGH();
case 2:
addReadRegister(argv + 1, stringType); // sourceText
addReadRegister(argv, stringType); // context
setAccumulator(returnType);
propagateTranslationMethod_SAcheck(method.methodName());
return true;
default:
return false;
}
}
if (method.methodName() == u"QT_TRANSLATE_NOOP"_s) {
switch (argc) {
case 3:
addReadRegister(argv + 2, stringType); // disambiguation
Q_FALLTHROUGH();
case 2:
addReadRegister(argv + 1, stringType); // sourceText
addReadRegister(argv, stringType); // context
setAccumulator(returnType);
propagateTranslationMethod_SAcheck(method.methodName());
return true;
default:
return false;
}
}
if (method.methodName() == u"qsTr"_s) {
switch (argc) {
case 3:
addReadRegister(argv + 2, intType); // n
Q_FALLTHROUGH();
case 2:
addReadRegister(argv + 1, stringType); // disambiguation
Q_FALLTHROUGH();
case 1:
addReadRegister(argv, stringType); // sourceText
setAccumulator(returnType);
propagateTranslationMethod_SAcheck(method.methodName());
return true;
default:
return false;
}
}
if (method.methodName() == u"QT_TR_NOOP"_s) {
switch (argc) {
case 2:
addReadRegister(argv + 1, stringType); // disambiguation
Q_FALLTHROUGH();
case 1:
addReadRegister(argv, stringType); // sourceText
setAccumulator(returnType);
propagateTranslationMethod_SAcheck(method.methodName());
return true;
default:
return false;
}
}
if (method.methodName() == u"qsTrId"_s) {
switch (argc) {
case 2:
addReadRegister(argv + 1, intType); // n
Q_FALLTHROUGH();
case 1:
addReadRegister(argv, stringType); // id
setAccumulator(returnType);
propagateTranslationMethod_SAcheck(method.methodName());
return true;
default:
return false;
}
}
if (method.methodName() == u"QT_TRID_NOOP"_s) {
switch (argc) {
case 1:
addReadRegister(argv, stringType); // id
setAccumulator(returnType);
propagateTranslationMethod_SAcheck(method.methodName());
return true;
default:
return false;
}
}
return false;
}
void QQmlJSTypePropagator::propagateStringArgCall(QQmlJSRegisterContent base, int argv)
{
QQmlJSMetaMethod method;
method.setIsJavaScriptFunction(true);
method.setMethodName(u"arg"_s);
setAccumulator(m_typeResolver->returnType(method, m_typeResolver->stringType(), base));
Q_ASSERT(m_state.accumulatorOut().isValid());
const QQmlJSScope::ConstPtr input = m_state.registers[argv].content.containedType();
if (input == m_typeResolver->uint32Type()
|| input == m_typeResolver->int64Type()
|| input == m_typeResolver->uint64Type()) {
addReadRegister(argv, m_typeResolver->realType());
return;
}
if (m_typeResolver->isIntegral(input)) {
addReadRegister(argv, m_typeResolver->int32Type());
return;
}
if (m_typeResolver->isNumeric(input)) {
addReadRegister(argv, m_typeResolver->realType());
return;
}
if (input == m_typeResolver->boolType()) {
addReadRegister(argv, m_typeResolver->boolType());
return;
}
addReadRegister(argv, m_typeResolver->stringType());
}
bool QQmlJSTypePropagator::propagateArrayMethod(
const QString &name, int argc, int argv, QQmlJSRegisterContent baseType)
{
// TODO:
// * For concat() we need to decide what kind of array to return and what kinds of arguments to
// accept.
// * For entries(), keys(), and values() we need iterators.
// * For find(), findIndex(), sort(), every(), some(), forEach(), map(), filter(), reduce(),
// and reduceRight() we need typed function pointers.
// TODO:
// For now, every method that mutates the original array is considered to have external
// side effects. We could do better by figuring out whether the array is actually backed
// by an external property or has entries backed by an external property. If not, there
// can't be any external side effects.
const auto intType = m_typeResolver->int32Type();
const auto stringType = m_typeResolver->stringType();
const auto baseContained = baseType.containedType();
const auto elementContained = baseContained->elementType();
const auto setReturnType = [&](const QQmlJSScope::ConstPtr type) {
QQmlJSMetaMethod method;
method.setIsJavaScriptFunction(true);
method.setMethodName(name);
setAccumulator(m_typeResolver->returnType(method, type, baseType));
};
if (name == u"copyWithin" && argc > 0 && argc < 4) {
for (int i = 0; i < argc; ++i) {
if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
return false;
}
for (int i = 0; i < argc; ++i)
addReadRegister(argv + i, intType);
m_state.setHasExternalSideEffects();
setReturnType(baseContained);
return true;
}
if (name == u"fill" && argc > 0 && argc < 4) {
if (!canConvertFromTo(m_state.registers[argv].content, elementContained))
return false;
for (int i = 1; i < argc; ++i) {
if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
return false;
}
addReadRegister(argv, elementContained);
for (int i = 1; i < argc; ++i)
addReadRegister(argv + i, intType);
m_state.setHasExternalSideEffects();
setReturnType(baseContained);
return true;
}
if (name == u"includes" && argc > 0 && argc < 3) {
if (!canConvertFromTo(m_state.registers[argv].content, elementContained))
return false;
if (argc == 2) {
if (!canConvertFromTo(m_state.registers[argv + 1].content, intType))
return false;
addReadRegister(argv + 1, intType);
}
addReadRegister(argv, elementContained);
setReturnType(m_typeResolver->boolType());
return true;
}
if (name == u"toString" || (name == u"join" && argc < 2)) {
if (argc == 1) {
if (!canConvertFromTo(m_state.registers[argv].content, stringType))
return false;
addReadRegister(argv, stringType);
}
setReturnType(m_typeResolver->stringType());
return true;
}
if ((name == u"pop" || name == u"shift") && argc == 0) {
m_state.setHasExternalSideEffects();
setReturnType(elementContained);
return true;
}
if (name == u"push" || name == u"unshift") {
for (int i = 0; i < argc; ++i) {
if (!canConvertFromTo(m_state.registers[argv + i].content, elementContained))
return false;
}
for (int i = 0; i < argc; ++i)
addReadRegister(argv + i, elementContained);
m_state.setHasExternalSideEffects();
setReturnType(m_typeResolver->int32Type());
return true;
}
if (name == u"reverse" && argc == 0) {
m_state.setHasExternalSideEffects();
setReturnType(baseContained);
return true;
}
if (name == u"slice" && argc < 3) {
for (int i = 0; i < argc; ++i) {
if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
return false;
}
for (int i = 0; i < argc; ++i)
addReadRegister(argv + i, intType);
setReturnType(baseType.containedType()->isListProperty()
? m_typeResolver->qObjectListType()
: baseContained);
return true;
}
if (name == u"splice" && argc > 0) {
const int startAndDeleteCount = std::min(argc, 2);
for (int i = 0; i < startAndDeleteCount; ++i) {
if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
return false;
}
for (int i = 2; i < argc; ++i) {
if (!canConvertFromTo(m_state.registers[argv + i].content, elementContained))
return false;
}
for (int i = 0; i < startAndDeleteCount; ++i)
addReadRegister(argv + i, intType);
for (int i = 2; i < argc; ++i)
addReadRegister(argv + i, elementContained);
m_state.setHasExternalSideEffects();
setReturnType(baseContained);
return true;
}
if ((name == u"indexOf" || name == u"lastIndexOf") && argc > 0 && argc < 3) {
if (!canConvertFromTo(m_state.registers[argv].content, elementContained))
return false;
if (argc == 2) {
if (!canConvertFromTo(m_state.registers[argv + 1].content, intType))
return false;
addReadRegister(argv + 1, intType);
}
addReadRegister(argv, elementContained);
setReturnType(m_typeResolver->int32Type());
return true;
}
return false;
}
void QQmlJSTypePropagator::generate_CallPropertyLookup(int lookupIndex, int base, int argc,
int argv)
{
generate_CallProperty(m_jsUnitGenerator->lookupNameIndex(lookupIndex), base, argc, argv);
}
void QQmlJSTypePropagator::generate_CallName(int name, int argc, int argv)
{
propagateScopeLookupCall(m_jsUnitGenerator->stringForIndex(name), argc, argv);
}
void QQmlJSTypePropagator::generate_CallPossiblyDirectEval(int argc, int argv)
{
m_state.setHasExternalSideEffects();
Q_UNUSED(argc)
Q_UNUSED(argv)
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::propagateScopeLookupCall(const QString &functionName, int argc, int argv)
{
const QQmlJSRegisterContent resolvedContent
= m_typeResolver->scopedType(m_function->qmlScope, functionName);
if (resolvedContent.isMethod()) {
const auto methods = resolvedContent.method();
if (resolvedContent.scope().contains(m_typeResolver->jsGlobalObject())) {
if (propagateTranslationMethod(methods, argc, argv))
return;
}
if (!methods.isEmpty()) {
propagateCall(methods, argc, argv, resolvedContent.scope());
return;
}
}
addError(u"method %1 cannot be resolved."_s.arg(functionName));
const auto jsValue = m_typeResolver->jsValueType();
QQmlJSMetaMethod method;
method.setMethodName(functionName);
method.setIsJavaScriptFunction(true);
setAccumulator(m_typeResolver->returnType(method, jsValue, m_function->qmlScope));
addError(u"Cannot find function '%1'"_s.arg(functionName));
handleUnqualifiedAccessAndContextProperties(functionName, true);
}
void QQmlJSTypePropagator::generate_CallGlobalLookup(int index, int argc, int argv)
{
propagateScopeLookupCall(m_jsUnitGenerator->lookupName(index), argc, argv);
}
void QQmlJSTypePropagator::generate_CallQmlContextPropertyLookup(int index, int argc, int argv)
{
const QString name = m_jsUnitGenerator->lookupName(index);
propagateScopeLookupCall(name, argc, argv);
checkDeprecated(m_function->qmlScope.containedType(), name, true);
}
void QQmlJSTypePropagator::generate_CallWithSpread(int func, int thisObject, int argc, int argv)
{
m_state.setHasExternalSideEffects();
Q_UNUSED(func)
Q_UNUSED(thisObject)
Q_UNUSED(argc)
Q_UNUSED(argv)
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_TailCall(int func, int thisObject, int argc, int argv)
{
m_state.setHasExternalSideEffects();
Q_UNUSED(func)
Q_UNUSED(thisObject)
Q_UNUSED(argc)
Q_UNUSED(argv)
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
void QQmlJSTypePropagator::generate_Construct_SCDate(
const QQmlJSMetaMethod &ctor, int argc, int argv)
{
setAccumulator(m_typeResolver->returnType(ctor, m_typeResolver->dateTimeType(), {}));
if (argc == 1) {
const QQmlJSRegisterContent argType = m_state.registers[argv].content;
if (m_typeResolver->isNumeric(argType)) {
addReadRegister(argv, m_typeResolver->realType());
} else if (argType.contains(m_typeResolver->stringType())) {
addReadRegister(argv, m_typeResolver->stringType());
} else if (argType.contains(m_typeResolver->dateTimeType())
|| argType.contains(m_typeResolver->dateType())
|| argType.contains(m_typeResolver->timeType())) {
addReadRegister(argv, m_typeResolver->dateTimeType());
} else {
addReadRegister(argv, m_typeResolver->jsPrimitiveType());
}
} else {
constexpr int maxArgc = 7; // year, month, day, hours, minutes, seconds, milliseconds
for (int i = 0; i < std::min(argc, maxArgc); ++i)
addReadRegister(argv + i, m_typeResolver->realType());
}
}
void QQmlJSTypePropagator::generate_Construct_SCArray(
const QQmlJSMetaMethod &ctor, int argc, int argv)
{
if (argc == 1) {
if (m_typeResolver->isNumeric(m_state.registers[argv].content)) {
setAccumulator(m_typeResolver->returnType(ctor, m_typeResolver->variantListType(), {}));
addReadRegister(argv, m_typeResolver->realType());
} else {
generate_DefineArray(argc, argv);
}
} else {
generate_DefineArray(argc, argv);
}
}
void QQmlJSTypePropagator::generate_Construct(int func, int argc, int argv)
{
const QQmlJSRegisterContent type = m_state.registers[func].content;
if (type.contains(m_typeResolver->metaObjectType())) {
const QQmlJSRegisterContent valueType = type.scope();
const QQmlJSScope::ConstPtr contained = type.scopeType();
if (contained->isValueType() && contained->isCreatable()) {
const auto extension = contained->extensionType();
if (extension.extensionSpecifier == QQmlJSScope::ExtensionType) {
propagateCall(
extension.scope->ownMethods(extension.scope->internalName()),
argc, argv, valueType);
} else {
propagateCall(
contained->ownMethods(contained->internalName()), argc, argv, valueType);
}
return;
}
}
if (!type.isMethod()) {
m_state.setHasExternalSideEffects();
QQmlJSMetaMethod method;
method.setMethodName(type.containedTypeName());
method.setIsJavaScriptFunction(true);
method.setIsConstructor(true);
setAccumulator(m_typeResolver->returnType(method, m_typeResolver->jsValueType(), {}));
return;
}
if (const auto methods = type.method();
methods == m_typeResolver->jsGlobalObject()->methods(u"Date"_s)) {
Q_ASSERT(methods.length() == 1);
generate_Construct_SCDate(methods[0], argc, argv);
return;
}
if (const auto methods = type.method();
methods == m_typeResolver->jsGlobalObject()->methods(u"Array"_s)) {
Q_ASSERT(methods.length() == 1);
generate_Construct_SCArray(methods[0], argc, argv);
return;
}
m_state.setHasExternalSideEffects();
QStringList errors;
QQmlJSMetaMethod match = bestMatchForCall(type.method(), argc, argv, &errors);
if (!match.isValid())
addError(u"Cannot determine matching constructor. Candidates:\n"_s + errors.join(u'\n'));
setAccumulator(m_typeResolver->returnType(match, m_typeResolver->jsValueType(), {}));
}
void QQmlJSTypePropagator::generate_ConstructWithSpread(int func, int argc, int argv)
{
m_state.setHasExternalSideEffects();
Q_UNUSED(func)
Q_UNUSED(argc)
Q_UNUSED(argv)
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_SetUnwindHandler(int offset)
{
m_state.setHasInternalSideEffects();
Q_UNUSED(offset)
INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE();
}
void QQmlJSTypePropagator::generate_UnwindDispatch()
{
m_state.setHasInternalSideEffects();
INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE();
}
void QQmlJSTypePropagator::generate_UnwindToLabel(int level, int offset)
{
m_state.setHasInternalSideEffects();
Q_UNUSED(level)
Q_UNUSED(offset)
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
void QQmlJSTypePropagator::generate_DeadTemporalZoneCheck(int name)
{
const auto fail = [this, name]() {
addError(u"Cannot statically assert the dead temporal zone check for %1"_s.arg(
name ? m_jsUnitGenerator->stringForIndex(name) : u"the anonymous accumulator"_s));
};
const QQmlJSRegisterContent in = m_state.accumulatorIn();
if (in.isConversion()) {
const auto &inConversionOrigins = in.conversionOrigins();
for (QQmlJSRegisterContent origin : inConversionOrigins) {
if (!origin.contains(m_typeResolver->emptyType()))
continue;
fail();
break;
}
} else if (in.contains(m_typeResolver->emptyType())) {
fail();
}
}
void QQmlJSTypePropagator::generate_ThrowException()
{
addReadAccumulator(m_typeResolver->jsValueType());
m_state.setHasInternalSideEffects();
m_state.skipInstructionsUntilNextJumpTarget = true;
}
void QQmlJSTypePropagator::generate_GetException()
{
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_SetException()
{
m_state.setHasInternalSideEffects();
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
void QQmlJSTypePropagator::generate_CreateCallContext()
{
m_state.setHasInternalSideEffects();
}
void QQmlJSTypePropagator::generate_PushCatchContext(int index, int name)
{
m_state.setHasInternalSideEffects();
Q_UNUSED(index)
Q_UNUSED(name)
INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE();
}
void QQmlJSTypePropagator::generate_PushWithContext()
{
m_state.setHasInternalSideEffects();
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_PushBlockContext(int index)
{
m_state.setHasInternalSideEffects();
Q_UNUSED(index)
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
void QQmlJSTypePropagator::generate_CloneBlockContext()
{
m_state.setHasInternalSideEffects();
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
void QQmlJSTypePropagator::generate_PushScriptContext(int index)
{
m_state.setHasInternalSideEffects();
Q_UNUSED(index)
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
void QQmlJSTypePropagator::generate_PopScriptContext()
{
m_state.setHasInternalSideEffects();
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
void QQmlJSTypePropagator::generate_PopContext()
{
m_state.setHasInternalSideEffects();
}
void QQmlJSTypePropagator::generate_GetIterator(int iterator)
{
const QQmlJSRegisterContent listType = m_state.accumulatorIn();
if (!listType.isList()) {
const QQmlJSScope::ConstPtr jsValue = m_typeResolver->jsValueType();
addReadAccumulator(jsValue);
QQmlJSMetaProperty prop;
prop.setPropertyName(u"<>"_s);
prop.setTypeName(jsValue->internalName());
prop.setType(jsValue);
setAccumulator(m_pool->createProperty(
prop, currentInstructionOffset(),
QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::ListIterator,
listType));
return;
}
addReadAccumulator();
setAccumulator(m_typeResolver->iteratorPointer(
listType, QQmlJS::AST::ForEachType(iterator), currentInstructionOffset()));
}
void QQmlJSTypePropagator::generate_IteratorNext(int value, int offset)
{
const QQmlJSRegisterContent iteratorType = m_state.accumulatorIn();
addReadAccumulator();
setRegister(value, m_typeResolver->merge(
m_typeResolver->elementType(iteratorType),
m_typeResolver->literalType(m_typeResolver->voidType())));
saveRegisterStateForJump(offset);
m_state.setHasInternalSideEffects();
}
void QQmlJSTypePropagator::generate_IteratorNextForYieldStar(int iterator, int object, int offset)
{
Q_UNUSED(iterator)
Q_UNUSED(object)
Q_UNUSED(offset)
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_IteratorClose()
{
// Noop
}
void QQmlJSTypePropagator::generate_DestructureRestElement()
{
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_DeleteProperty(int base, int index)
{
Q_UNUSED(base)
Q_UNUSED(index)
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_DeleteName(int name)
{
Q_UNUSED(name)
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_TypeofName(int name)
{
Q_UNUSED(name);
setAccumulator(m_typeResolver->operationType(m_typeResolver->stringType()));
}
void QQmlJSTypePropagator::generate_TypeofValue()
{
setAccumulator(m_typeResolver->operationType(m_typeResolver->stringType()));
}
void QQmlJSTypePropagator::generate_DeclareVar(int varName, int isDeletable)
{
Q_UNUSED(varName)
Q_UNUSED(isDeletable)
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
void QQmlJSTypePropagator::generate_DefineArray(int argc, int args)
{
setAccumulator(m_typeResolver->operationType(m_typeResolver->variantListType()));
// Track all arguments as the same type.
const QQmlJSScope::ConstPtr elementType = m_typeResolver->varType();
for (int i = 0; i < argc; ++i)
addReadRegister(args + i, elementType);
}
void QQmlJSTypePropagator::generate_DefineObjectLiteral(int internalClassId, int argc, int args)
{
const int classSize = m_jsUnitGenerator->jsClassSize(internalClassId);
Q_ASSERT(argc >= classSize);
// Track each element as separate type
for (int i = 0; i < classSize; ++i)
addReadRegister(args + i, m_typeResolver->varType());
for (int i = classSize; i < argc; i += 3) {
// layout for remaining members is:
// 0: ObjectLiteralArgument - Value|Method|Getter|Setter
// We cannot do anything useful with this. Any code that would call a getter/setter/method
// could not be compiled to C++. Ignore it.
// 1: name of argument
addReadRegister(args + i + 1, m_typeResolver->stringType());
// 2: value of argument
addReadRegister(args + i + 2, m_typeResolver->varType());
}
setAccumulator(m_typeResolver->operationType(m_typeResolver->variantMapType()));
}
void QQmlJSTypePropagator::generate_CreateClass(int classIndex, int heritage, int computedNames)
{
Q_UNUSED(classIndex)
Q_UNUSED(heritage)
Q_UNUSED(computedNames)
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_CreateMappedArgumentsObject()
{
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_CreateUnmappedArgumentsObject()
{
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_CreateRestParameter(int argIndex)
{
Q_UNUSED(argIndex)
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_ConvertThisToObject()
{
setRegister(This, m_pool->clone(m_function->qmlScope));
}
void QQmlJSTypePropagator::generate_LoadSuperConstructor()
{
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_ToObject()
{
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_Jump(int offset)
{
saveRegisterStateForJump(offset);
m_state.skipInstructionsUntilNextJumpTarget = true;
m_state.setHasInternalSideEffects();
}
void QQmlJSTypePropagator::generate_JumpTrue(int offset)
{
if (!canConvertFromTo(m_state.accumulatorIn(), m_typeResolver->boolType())) {
addError(u"cannot convert from %1 to boolean"_s
.arg(m_state.accumulatorIn().descriptiveName()));
return;
}
saveRegisterStateForJump(offset);
addReadAccumulator(m_typeResolver->boolType());
m_state.setHasInternalSideEffects();
}
void QQmlJSTypePropagator::generate_JumpFalse(int offset)
{
if (!canConvertFromTo(m_state.accumulatorIn(), m_typeResolver->boolType())) {
addError(u"cannot convert from %1 to boolean"_s
.arg(m_state.accumulatorIn().descriptiveName()));
return;
}
saveRegisterStateForJump(offset);
addReadAccumulator(m_typeResolver->boolType());
m_state.setHasInternalSideEffects();
}
void QQmlJSTypePropagator::generate_JumpNoException(int offset)
{
saveRegisterStateForJump(offset);
m_state.setHasInternalSideEffects();
}
void QQmlJSTypePropagator::generate_JumpNotUndefined(int offset)
{
Q_UNUSED(offset)
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
void QQmlJSTypePropagator::generate_CheckException()
{
m_state.setHasInternalSideEffects();
}
void QQmlJSTypePropagator::recordEqualsNullType()
{
// TODO: We can specialize this further, for QVariant, QJSValue, int, bool, whatever.
if (m_state.accumulatorIn().contains(m_typeResolver->nullType())
|| m_state.accumulatorIn().containedType()->isReferenceType()) {
addReadAccumulator();
} else {
addReadAccumulator(m_typeResolver->jsPrimitiveType());
}
}
void QQmlJSTypePropagator::recordEqualsIntType()
{
// We have specializations for numeric types and bool.
const QQmlJSScope::ConstPtr in = m_state.accumulatorIn().containedType();
if (m_state.accumulatorIn().contains(m_typeResolver->boolType())
|| m_typeResolver->isNumeric(m_state.accumulatorIn())) {
addReadAccumulator();
} else {
addReadAccumulator(m_typeResolver->jsPrimitiveType());
}
}
void QQmlJSTypePropagator::recordEqualsType(int lhs)
{
const auto isNumericOrEnum = [this](QQmlJSRegisterContent content) {
return content.isEnumeration() || m_typeResolver->isNumeric(content);
};
const auto accumulatorIn = m_state.accumulatorIn();
const auto lhsRegister = m_state.registers[lhs].content;
// If the types are primitive, we compare directly ...
if (m_typeResolver->isPrimitive(accumulatorIn) || accumulatorIn.isEnumeration()) {
if (accumulatorIn.contains(lhsRegister.containedType())
|| (isNumericOrEnum(accumulatorIn) && isNumericOrEnum(lhsRegister))
|| m_typeResolver->isPrimitive(lhsRegister)) {
addReadRegister(lhs);
addReadAccumulator();
return;
}
}
const auto containedAccumulatorIn = m_typeResolver->isOptionalType(accumulatorIn)
? m_typeResolver->extractNonVoidFromOptionalType(accumulatorIn).containedType()
: accumulatorIn.containedType();
const auto containedLhs = m_typeResolver->isOptionalType(lhsRegister)
? m_typeResolver->extractNonVoidFromOptionalType(lhsRegister).containedType()
: lhsRegister.containedType();
// We don't modify types if the types are comparable with QObject, QUrl or var types
if (QQmlJSUtils::canStrictlyCompareWithVar(m_typeResolver, containedLhs, containedAccumulatorIn)
|| QQmlJSUtils::canCompareWithQObject(m_typeResolver, containedLhs, containedAccumulatorIn)
|| QQmlJSUtils::canCompareWithQUrl(m_typeResolver, containedLhs, containedAccumulatorIn)) {
addReadRegister(lhs);
addReadAccumulator();
return;
}
// Otherwise they're both casted to QJSValue.
// TODO: We can add more specializations here: object/null etc
const QQmlJSScope::ConstPtr jsval = m_typeResolver->jsValueType();
addReadRegister(lhs, jsval);
addReadAccumulator(jsval);
}
void QQmlJSTypePropagator::recordCompareType(int lhs)
{
// TODO: Revisit this. Does it make any sense to do a comparison on something non-numeric?
// Does it pay off to record the exact number type to use?
const QQmlJSRegisterContent lhsContent = m_state.registers[lhs].content;
const QQmlJSRegisterContent rhsContent = m_state.accumulatorIn();
if (lhsContent == rhsContent) {
// Do not re-track in this case. We want any manipulations on the original types to persist.
// TODO: Why? Can we just use double and be done with it?
addReadRegister(lhs, lhsContent);
addReadAccumulator(lhsContent);
} else if (m_typeResolver->isNumeric(lhsContent) && m_typeResolver->isNumeric(rhsContent)) {
// If they're both numeric, we can compare them directly.
// They may be casted to double, though.
const QQmlJSRegisterContent merged = m_typeResolver->merge(lhsContent, rhsContent);
addReadRegister(lhs, merged);
addReadAccumulator(merged);
} else {
const QQmlJSScope::ConstPtr primitive = m_typeResolver->jsPrimitiveType();
addReadRegister(lhs, primitive);
addReadAccumulator(primitive);
}
}
void QQmlJSTypePropagator::warnAboutTypeCoercion(int lhs)
{
Q_UNUSED(lhs);
}
void QQmlJSTypePropagator::generate_CmpEqNull()
{
recordEqualsNullType();
setAccumulator(m_typeResolver->operationType(m_typeResolver->boolType()));
}
void QQmlJSTypePropagator::generate_CmpNeNull()
{
recordEqualsNullType();
setAccumulator(m_typeResolver->operationType(m_typeResolver->boolType()));
}
void QQmlJSTypePropagator::generate_CmpEqInt(int lhsConst)
{
recordEqualsIntType();
Q_UNUSED(lhsConst)
setAccumulator(m_typeResolver->typeForBinaryOperation(
QSOperator::Op::Equal, m_typeResolver->literalType(m_typeResolver->int32Type()),
m_state.accumulatorIn()));
}
void QQmlJSTypePropagator::generate_CmpNeInt(int lhsConst)
{
recordEqualsIntType();
Q_UNUSED(lhsConst)
setAccumulator(m_typeResolver->typeForBinaryOperation(
QSOperator::Op::NotEqual, m_typeResolver->literalType(m_typeResolver->int32Type()),
m_state.accumulatorIn()));
}
void QQmlJSTypePropagator::generate_CmpEq(int lhs)
{
warnAboutTypeCoercion(lhs);
recordEqualsType(lhs);
propagateBinaryOperation(QSOperator::Op::Equal, lhs);
}
void QQmlJSTypePropagator::generate_CmpNe(int lhs)
{
warnAboutTypeCoercion(lhs);
recordEqualsType(lhs);
propagateBinaryOperation(QSOperator::Op::NotEqual, lhs);
}
void QQmlJSTypePropagator::generate_CmpGt(int lhs)
{
recordCompareType(lhs);
propagateBinaryOperation(QSOperator::Op::Gt, lhs);
}
void QQmlJSTypePropagator::generate_CmpGe(int lhs)
{
recordCompareType(lhs);
propagateBinaryOperation(QSOperator::Op::Ge, lhs);
}
void QQmlJSTypePropagator::generate_CmpLt(int lhs)
{
recordCompareType(lhs);
propagateBinaryOperation(QSOperator::Op::Lt, lhs);
}
void QQmlJSTypePropagator::generate_CmpLe(int lhs)
{
recordCompareType(lhs);
propagateBinaryOperation(QSOperator::Op::Le, lhs);
}
void QQmlJSTypePropagator::generate_CmpStrictEqual(int lhs)
{
recordEqualsType(lhs);
propagateBinaryOperation(QSOperator::Op::StrictEqual, lhs);
}
void QQmlJSTypePropagator::generate_CmpStrictNotEqual(int lhs)
{
recordEqualsType(lhs);
propagateBinaryOperation(QSOperator::Op::StrictNotEqual, lhs);
}
void QQmlJSTypePropagator::generate_CmpIn(int lhs)
{
// TODO: Most of the time we don't need the object at all, but only its metatype.
// Fix this when we add support for the "in" instruction to the code generator.
// Also, specialize on lhs to avoid conversion to QJSPrimitiveValue.
addReadRegister(lhs, m_typeResolver->jsValueType());
addReadAccumulator(m_typeResolver->jsValueType());
propagateBinaryOperation(QSOperator::Op::In, lhs);
}
void QQmlJSTypePropagator::generate_CmpInstanceOf(int lhs)
{
Q_UNUSED(lhs)
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
void QQmlJSTypePropagator::generate_As(int lhs)
{
const QQmlJSRegisterContent input = checkedInputRegister(lhs);
const QQmlJSScope::ConstPtr inContained = input.containedType();
QQmlJSRegisterContent output;
const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
switch (accumulatorIn.variant()) {
case QQmlJSRegisterContent::Attachment:
output = accumulatorIn.scope();
break;
case QQmlJSRegisterContent::MetaType:
output = accumulatorIn.scope();
if (output.containedType()->isComposite()) // Otherwise we don't need it
addReadAccumulator(m_typeResolver->metaObjectType());
break;
default:
output = accumulatorIn;
break;
}
QQmlJSScope::ConstPtr outContained = output.containedType();
if (outContained->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
// A referece type cast can result in either the type or null.
// Reference types can hold null. We don't need to special case that.
if (m_typeResolver->inherits(inContained, outContained))
output = m_pool->clone(input);
else
output = m_pool->castTo(input, outContained);
} else if (m_typeResolver->inherits(inContained, outContained)) {
// A "slicing" cannot result in void
output = m_pool->castTo(input, outContained);
} else {
// A value type cast can result in either the type or undefined.
// Using convert() retains the variant of the input type.
output = m_typeResolver->merge(
m_pool->castTo(input, outContained),
m_pool->castTo(input, m_typeResolver->voidType()));
}
addReadRegister(lhs);
setAccumulator(output);
}
void QQmlJSTypePropagator::checkConversion(
QQmlJSRegisterContent from, QQmlJSRegisterContent to)
{
if (!canConvertFromTo(from, to)) {
addError(u"cannot convert from %1 to %2"_s
.arg(from.descriptiveName(), to.descriptiveName()));
}
}
void QQmlJSTypePropagator::generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator op)
{
const QQmlJSRegisterContent type = m_typeResolver->typeForArithmeticUnaryOperation(
op, m_state.accumulatorIn());
checkConversion(m_state.accumulatorIn(), type);
addReadAccumulator(type);
setAccumulator(type);
}
void QQmlJSTypePropagator::generate_UNot()
{
generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Not);
}
void QQmlJSTypePropagator::generate_UPlus()
{
generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Plus);
}
void QQmlJSTypePropagator::generate_UMinus()
{
generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Minus);
}
void QQmlJSTypePropagator::generate_UCompl()
{
generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Complement);
}
void QQmlJSTypePropagator::generate_Increment()
{
generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Increment);
}
void QQmlJSTypePropagator::generate_Decrement()
{
generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Decrement);
}
void QQmlJSTypePropagator::generateBinaryArithmeticOperation(QSOperator::Op op, int lhs)
{
const QQmlJSRegisterContent type = propagateBinaryOperation(op, lhs);
checkConversion(checkedInputRegister(lhs), type);
addReadRegister(lhs, type);
checkConversion(m_state.accumulatorIn(), type);
addReadAccumulator(type);
}
void QQmlJSTypePropagator::generateBinaryConstArithmeticOperation(QSOperator::Op op)
{
const QQmlJSRegisterContent type = m_typeResolver->typeForBinaryOperation(
op, m_state.accumulatorIn(),
m_typeResolver->literalType(m_typeResolver->int32Type()));
checkConversion(m_state.accumulatorIn(), type);
addReadAccumulator(type);
setAccumulator(type);
}
void QQmlJSTypePropagator::generate_Add(int lhs)
{
generateBinaryArithmeticOperation(QSOperator::Op::Add, lhs);
}
void QQmlJSTypePropagator::generate_BitAnd(int lhs)
{
generateBinaryArithmeticOperation(QSOperator::Op::BitAnd, lhs);
}
void QQmlJSTypePropagator::generate_BitOr(int lhs)
{
generateBinaryArithmeticOperation(QSOperator::Op::BitOr, lhs);
}
void QQmlJSTypePropagator::generate_BitXor(int lhs)
{
generateBinaryArithmeticOperation(QSOperator::Op::BitXor, lhs);
}
void QQmlJSTypePropagator::generate_UShr(int lhs)
{
generateBinaryArithmeticOperation(QSOperator::Op::URShift, lhs);
}
void QQmlJSTypePropagator::generate_Shr(int lhs)
{
generateBinaryArithmeticOperation(QSOperator::Op::RShift, lhs);
}
void QQmlJSTypePropagator::generate_Shl(int lhs)
{
generateBinaryArithmeticOperation(QSOperator::Op::LShift, lhs);
}
void QQmlJSTypePropagator::generate_BitAndConst(int rhsConst)
{
Q_UNUSED(rhsConst)
generateBinaryConstArithmeticOperation(QSOperator::Op::BitAnd);
}
void QQmlJSTypePropagator::generate_BitOrConst(int rhsConst)
{
Q_UNUSED(rhsConst)
generateBinaryConstArithmeticOperation(QSOperator::Op::BitOr);
}
void QQmlJSTypePropagator::generate_BitXorConst(int rhsConst)
{
Q_UNUSED(rhsConst)
generateBinaryConstArithmeticOperation(QSOperator::Op::BitXor);
}
void QQmlJSTypePropagator::generate_UShrConst(int rhsConst)
{
Q_UNUSED(rhsConst)
generateBinaryConstArithmeticOperation(QSOperator::Op::URShift);
}
void QQmlJSTypePropagator::generate_ShrConst(int rhsConst)
{
Q_UNUSED(rhsConst)
generateBinaryConstArithmeticOperation(QSOperator::Op::RShift);
}
void QQmlJSTypePropagator::generate_ShlConst(int rhsConst)
{
Q_UNUSED(rhsConst)
generateBinaryConstArithmeticOperation(QSOperator::Op::LShift);
}
void QQmlJSTypePropagator::generate_Exp(int lhs)
{
generateBinaryArithmeticOperation(QSOperator::Op::Exp, lhs);
}
void QQmlJSTypePropagator::generate_Mul(int lhs)
{
generateBinaryArithmeticOperation(QSOperator::Op::Mul, lhs);
}
void QQmlJSTypePropagator::generate_Div(int lhs)
{
generateBinaryArithmeticOperation(QSOperator::Op::Div, lhs);
}
void QQmlJSTypePropagator::generate_Mod(int lhs)
{
generateBinaryArithmeticOperation(QSOperator::Op::Mod, lhs);
}
void QQmlJSTypePropagator::generate_Sub(int lhs)
{
generateBinaryArithmeticOperation(QSOperator::Op::Sub, lhs);
}
void QQmlJSTypePropagator::generate_InitializeBlockDeadTemporalZone(int firstReg, int count)
{
setAccumulator(m_typeResolver->literalType(m_typeResolver->emptyType()));
for (int reg = firstReg, end = firstReg + count; reg < end; ++reg)
setRegister(reg, m_typeResolver->literalType(m_typeResolver->emptyType()));
}
void QQmlJSTypePropagator::generate_ThrowOnNullOrUndefined()
{
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
void QQmlJSTypePropagator::generate_GetTemplateObject(int index)
{
Q_UNUSED(index)
INSTR_PROLOGUE_NOT_IMPLEMENTED_POPULATES_ACC();
}
QV4::Moth::ByteCodeHandler::Verdict
QQmlJSTypePropagator::startInstruction(QV4::Moth::Instr::Type type)
{
if (m_state.jumpTargets.contains(currentInstructionOffset())) {
if (m_state.skipInstructionsUntilNextJumpTarget) {
// When re-surfacing from dead code, all registers are invalid.
m_state.registers.clear();
m_state.skipInstructionsUntilNextJumpTarget = false;
}
} else if (m_state.skipInstructionsUntilNextJumpTarget
&& !instructionManipulatesContext(type)) {
return SkipInstruction;
}
const int currentOffset = currentInstructionOffset();
// If we reach an instruction that is a target of a jump earlier, then we must check that the
// register state at the origin matches the current state. If not, then we may have to inject
// conversion code (communicated to code gen via m_state.typeConversions). For
// example:
//
// function blah(x: number) { return x > 10 ? 10 : x}
//
// translates to a situation where in the "true" case, we load an integer into the accumulator
// and in the else case a number (x). When the control flow is joined, the types don't match and
// we need to make sure that the int is converted to a double just before the jump.
for (auto originRegisterStateIt =
m_jumpOriginRegisterStateByTargetInstructionOffset.constFind(currentOffset);
originRegisterStateIt != m_jumpOriginRegisterStateByTargetInstructionOffset.constEnd()
&& originRegisterStateIt.key() == currentOffset;
++originRegisterStateIt) {
auto stateToMerge = *originRegisterStateIt;
for (auto registerIt = stateToMerge.registers.constBegin(),
end = stateToMerge.registers.constEnd();
registerIt != end; ++registerIt) {
const int registerIndex = registerIt.key();
const VirtualRegister &newType = registerIt.value();
if (!newType.content.isValid()) {
addError(u"When reached from offset %1, %2 is undefined"_s
.arg(stateToMerge.originatingOffset)
.arg(registerName(registerIndex)));
return SkipInstruction;
}
auto currentRegister = m_state.registers.find(registerIndex);
if (currentRegister != m_state.registers.end())
mergeRegister(registerIndex, newType, currentRegister.value());
else
mergeRegister(registerIndex, newType, newType);
}
}
return ProcessInstruction;
}
bool QQmlJSTypePropagator::populatesAccumulator(QV4::Moth::Instr::Type instr) const
{
switch (instr) {
case QV4::Moth::Instr::Type::CheckException:
case QV4::Moth::Instr::Type::CloneBlockContext:
case QV4::Moth::Instr::Type::ConvertThisToObject:
case QV4::Moth::Instr::Type::CreateCallContext:
case QV4::Moth::Instr::Type::DeadTemporalZoneCheck:
case QV4::Moth::Instr::Type::Debug:
case QV4::Moth::Instr::Type::DeclareVar:
case QV4::Moth::Instr::Type::IteratorClose:
case QV4::Moth::Instr::Type::IteratorNext:
case QV4::Moth::Instr::Type::IteratorNextForYieldStar:
case QV4::Moth::Instr::Type::Jump:
case QV4::Moth::Instr::Type::JumpFalse:
case QV4::Moth::Instr::Type::JumpNoException:
case QV4::Moth::Instr::Type::JumpNotUndefined:
case QV4::Moth::Instr::Type::JumpTrue:
case QV4::Moth::Instr::Type::MoveConst:
case QV4::Moth::Instr::Type::MoveReg:
case QV4::Moth::Instr::Type::MoveRegExp:
case QV4::Moth::Instr::Type::PopContext:
case QV4::Moth::Instr::Type::PushBlockContext:
case QV4::Moth::Instr::Type::PushCatchContext:
case QV4::Moth::Instr::Type::PushScriptContext:
case QV4::Moth::Instr::Type::Resume:
case QV4::Moth::Instr::Type::Ret:
case QV4::Moth::Instr::Type::SetException:
case QV4::Moth::Instr::Type::SetLookup:
case QV4::Moth::Instr::Type::SetUnwindHandler:
case QV4::Moth::Instr::Type::StoreElement:
case QV4::Moth::Instr::Type::StoreLocal:
case QV4::Moth::Instr::Type::StoreNameSloppy:
case QV4::Moth::Instr::Type::StoreNameStrict:
case QV4::Moth::Instr::Type::StoreProperty:
case QV4::Moth::Instr::Type::StoreReg:
case QV4::Moth::Instr::Type::StoreScopedLocal:
case QV4::Moth::Instr::Type::StoreSuperProperty:
case QV4::Moth::Instr::Type::ThrowException:
case QV4::Moth::Instr::Type::ThrowOnNullOrUndefined:
case QV4::Moth::Instr::Type::UnwindDispatch:
case QV4::Moth::Instr::Type::UnwindToLabel:
case QV4::Moth::Instr::Type::Yield:
case QV4::Moth::Instr::Type::YieldStar:
return false;
case QV4::Moth::Instr::Type::Add:
case QV4::Moth::Instr::Type::As:
case QV4::Moth::Instr::Type::BitAnd:
case QV4::Moth::Instr::Type::BitAndConst:
case QV4::Moth::Instr::Type::BitOr:
case QV4::Moth::Instr::Type::BitOrConst:
case QV4::Moth::Instr::Type::BitXor:
case QV4::Moth::Instr::Type::BitXorConst:
case QV4::Moth::Instr::Type::CallGlobalLookup:
case QV4::Moth::Instr::Type::CallName:
case QV4::Moth::Instr::Type::CallPossiblyDirectEval:
case QV4::Moth::Instr::Type::CallProperty:
case QV4::Moth::Instr::Type::CallPropertyLookup:
case QV4::Moth::Instr::Type::CallQmlContextPropertyLookup:
case QV4::Moth::Instr::Type::CallValue:
case QV4::Moth::Instr::Type::CallWithReceiver:
case QV4::Moth::Instr::Type::CallWithSpread:
case QV4::Moth::Instr::Type::CmpEq:
case QV4::Moth::Instr::Type::CmpEqInt:
case QV4::Moth::Instr::Type::CmpEqNull:
case QV4::Moth::Instr::Type::CmpGe:
case QV4::Moth::Instr::Type::CmpGt:
case QV4::Moth::Instr::Type::CmpIn:
case QV4::Moth::Instr::Type::CmpInstanceOf:
case QV4::Moth::Instr::Type::CmpLe:
case QV4::Moth::Instr::Type::CmpLt:
case QV4::Moth::Instr::Type::CmpNe:
case QV4::Moth::Instr::Type::CmpNeInt:
case QV4::Moth::Instr::Type::CmpNeNull:
case QV4::Moth::Instr::Type::CmpStrictEqual:
case QV4::Moth::Instr::Type::CmpStrictNotEqual:
case QV4::Moth::Instr::Type::Construct:
case QV4::Moth::Instr::Type::ConstructWithSpread:
case QV4::Moth::Instr::Type::CreateClass:
case QV4::Moth::Instr::Type::CreateMappedArgumentsObject:
case QV4::Moth::Instr::Type::CreateRestParameter:
case QV4::Moth::Instr::Type::CreateUnmappedArgumentsObject:
case QV4::Moth::Instr::Type::Decrement:
case QV4::Moth::Instr::Type::DefineArray:
case QV4::Moth::Instr::Type::DefineObjectLiteral:
case QV4::Moth::Instr::Type::DeleteName:
case QV4::Moth::Instr::Type::DeleteProperty:
case QV4::Moth::Instr::Type::DestructureRestElement:
case QV4::Moth::Instr::Type::Div:
case QV4::Moth::Instr::Type::Exp:
case QV4::Moth::Instr::Type::GetException:
case QV4::Moth::Instr::Type::GetIterator:
case QV4::Moth::Instr::Type::GetLookup:
case QV4::Moth::Instr::Type::GetOptionalLookup:
case QV4::Moth::Instr::Type::GetTemplateObject:
case QV4::Moth::Instr::Type::Increment:
case QV4::Moth::Instr::Type::InitializeBlockDeadTemporalZone:
case QV4::Moth::Instr::Type::LoadClosure:
case QV4::Moth::Instr::Type::LoadConst:
case QV4::Moth::Instr::Type::LoadElement:
case QV4::Moth::Instr::Type::LoadFalse:
case QV4::Moth::Instr::Type::LoadGlobalLookup:
case QV4::Moth::Instr::Type::LoadImport:
case QV4::Moth::Instr::Type::LoadInt:
case QV4::Moth::Instr::Type::LoadLocal:
case QV4::Moth::Instr::Type::LoadName:
case QV4::Moth::Instr::Type::LoadNull:
case QV4::Moth::Instr::Type::LoadOptionalProperty:
case QV4::Moth::Instr::Type::LoadProperty:
case QV4::Moth::Instr::Type::LoadQmlContextPropertyLookup:
case QV4::Moth::Instr::Type::LoadReg:
case QV4::Moth::Instr::Type::LoadRuntimeString:
case QV4::Moth::Instr::Type::LoadScopedLocal:
case QV4::Moth::Instr::Type::LoadSuperConstructor:
case QV4::Moth::Instr::Type::LoadSuperProperty:
case QV4::Moth::Instr::Type::LoadTrue:
case QV4::Moth::Instr::Type::LoadUndefined:
case QV4::Moth::Instr::Type::LoadZero:
case QV4::Moth::Instr::Type::Mod:
case QV4::Moth::Instr::Type::Mul:
case QV4::Moth::Instr::Type::PushWithContext:
case QV4::Moth::Instr::Type::Shl:
case QV4::Moth::Instr::Type::ShlConst:
case QV4::Moth::Instr::Type::Shr:
case QV4::Moth::Instr::Type::ShrConst:
case QV4::Moth::Instr::Type::Sub:
case QV4::Moth::Instr::Type::TailCall:
case QV4::Moth::Instr::Type::ToObject:
case QV4::Moth::Instr::Type::TypeofName:
case QV4::Moth::Instr::Type::TypeofValue:
case QV4::Moth::Instr::Type::UCompl:
case QV4::Moth::Instr::Type::UMinus:
case QV4::Moth::Instr::Type::UNot:
case QV4::Moth::Instr::Type::UPlus:
case QV4::Moth::Instr::Type::UShr:
case QV4::Moth::Instr::Type::UShrConst:
return true;
default:
Q_UNREACHABLE_RETURN(false);
}
}
bool QQmlJSTypePropagator::isNoop(QV4::Moth::Instr::Type instr) const
{
switch (instr) {
case QV4::Moth::Instr::Type::DeadTemporalZoneCheck:
case QV4::Moth::Instr::Type::IteratorClose:
return true;
default:
return false;
}
}
void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr)
{
InstructionAnnotation ¤tInstruction = m_state.annotations[currentInstructionOffset()];
currentInstruction.changedRegister = m_state.changedRegister();
currentInstruction.changedRegisterIndex = m_state.changedRegisterIndex();
currentInstruction.readRegisters = m_state.takeReadRegisters();
currentInstruction.hasExternalSideEffects = m_state.hasExternalSideEffects();
currentInstruction.hasInternalSideEffects = m_state.hasInternalSideEffects();
currentInstruction.isRename = m_state.isRename();
bool populates = populatesAccumulator(instr);
int changedIndex = m_state.changedRegisterIndex();
// TODO: Find a way to deal with instructions that change multiple registers
if (instr != QV4::Moth::Instr::Type::InitializeBlockDeadTemporalZone) {
Q_ASSERT((populates && changedIndex == Accumulator && m_state.accumulatorOut().isValid())
|| (!populates && changedIndex != Accumulator));
}
if (!m_logger->currentFunctionHasCompileError() && !isNoop(instr)) {
// An instruction needs to have side effects or write to another register or be a known
// noop. Anything else is a problem.
Q_ASSERT(m_state.hasInternalSideEffects() || changedIndex != InvalidRegister);
}
if (changedIndex != InvalidRegister) {
Q_ASSERT(m_logger->currentFunctionHasCompileError() || m_state.changedRegister().isValid());
VirtualRegister &r = m_state.registers[changedIndex];
r.content = m_state.changedRegister();
r.canMove = false;
r.affectedBySideEffects = m_state.isRename()
&& m_state.isRegisterAffectedBySideEffects(m_state.renameSourceRegisterIndex());
m_state.clearChangedRegister();
}
m_state.resetSideEffects();
m_state.setIsRename(false);
m_state.setReadRegisters(VirtualRegisters());
m_state.instructionHasError = false;
}
QQmlJSRegisterContent QQmlJSTypePropagator::propagateBinaryOperation(QSOperator::Op op, int lhs)
{
auto lhsRegister = checkedInputRegister(lhs);
if (!lhsRegister.isValid())
return QQmlJSRegisterContent();
const QQmlJSRegisterContent type = m_typeResolver->typeForBinaryOperation(
op, lhsRegister, m_state.accumulatorIn());
setAccumulator(type);
return type;
}
static bool deepCompare(const QQmlJSRegisterContent &a, const QQmlJSRegisterContent &b)
{
if (!a.isValid() && !b.isValid())
return true;
return a.containedType() == b.containedType()
&& a.variant() == b.variant()
&& deepCompare(a.scope(), b.scope());
}
void QQmlJSTypePropagator::saveRegisterStateForJump(int offset)
{
auto jumpToOffset = offset + nextInstructionOffset();
ExpectedRegisterState state;
state.registers = m_state.registers;
state.originatingOffset = currentInstructionOffset();
m_state.jumpTargets.insert(jumpToOffset);
if (offset < 0) {
// We're jumping backwards. We won't get to merge the register states in this pass anymore.
const auto registerStates =
m_jumpOriginRegisterStateByTargetInstructionOffset.equal_range(jumpToOffset);
for (auto it = registerStates.first; it != registerStates.second; ++it) {
if (it->registers.keys() != state.registers.keys())
continue;
const auto valuesIt = it->registers.values();
const auto valuesState = state.registers.values();
bool different = false;
for (qsizetype i = 0, end = valuesIt.size(); i != end; ++i) {
const auto &valueIt = valuesIt[i];
const auto &valueState = valuesState[i];
if (valueIt.affectedBySideEffects != valueState.affectedBySideEffects
|| valueIt.canMove != valueState.canMove
|| valueIt.isShadowable != valueState.isShadowable
|| !deepCompare(valueIt.content, valueState.content)) {
different = true;
break;
}
}
if (!different)
return; // We've seen the same register state before. No need for merging.
}
// The register state at the target offset needs to be resolved in a further pass.
m_state.needsMorePasses = true;
}
m_jumpOriginRegisterStateByTargetInstructionOffset.insert(jumpToOffset, state);
}
QString QQmlJSTypePropagator::registerName(int registerIndex) const
{
switch (registerIndex) {
case InvalidRegister:
return u"invalid"_s;
case CurrentFunction:
return u"function"_s;
case Context:
return u"context"_s;
case Accumulator:
return u"accumulator"_s;
case This:
return u"this"_s;
case Argc:
return u"argc"_s;
case NewTarget:
return u"newTarget"_s;
default:
break;
}
if (isArgument(registerIndex))
return u"argument %1"_s.arg(registerIndex - FirstArgument);
return u"temporary register %1"_s.arg(
registerIndex - FirstArgument - m_function->argumentTypes.size());
}
QQmlJSRegisterContent QQmlJSTypePropagator::checkedInputRegister(int reg)
{
const auto regIt = m_state.registers.find(reg);
if (regIt != m_state.registers.end())
return regIt.value().content;
switch (reg) {
case CurrentFunction:
return m_typeResolver->syntheticType(m_typeResolver->functionType());
case Context:
return m_typeResolver->syntheticType(m_typeResolver->jsValueType());
case Accumulator:
addError(u"Type error: no value found in accumulator"_s);
return {};
case This:
return m_function->qmlScope;
case Argc:
return m_typeResolver->syntheticType(m_typeResolver->int32Type());
case NewTarget:
// over-approximation: needed in qmllint to not crash on `eval()`-calls
return m_typeResolver->syntheticType(m_typeResolver->varType());
default:
break;
}
if (isArgument(reg))
return argumentType(reg);
addError(u"Type error: could not infer the type of an expression"_s);
return {};
}
bool QQmlJSTypePropagator::canConvertFromTo(
QQmlJSRegisterContent from, QQmlJSRegisterContent to)
{
return m_typeResolver->canConvertFromTo(from, to);
}
bool QQmlJSTypePropagator::canConvertFromTo(
QQmlJSRegisterContent from, const QQmlJSScope::ConstPtr &to)
{
return m_typeResolver->canConvertFromTo(from.containedType(), to);
}
QT_END_NAMESPACE