Revision control
Copy as Markdown
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant
#include "qqmlirbuilder_p.h"
#include <private/qv4staticvalue_p.h>
#include <private/qv4compileddata_p.h>
#include <private/qqmljsparser_p.h>
#include <private/qqmljslexer_p.h>
#include <private/qv4compilerscanfunctions_p.h>
#include <QCoreApplication>
#include <QCryptographicHash>
#include <QtCore/qtyperevision.h>
#include <cmath>
#include <iterator>
QT_USE_NAMESPACE
using namespace Qt::StringLiterals;
static const quint32 emptyStringIndex = 0;
using namespace QmlIR;
using namespace QQmlJS;
#define COMPILE_EXCEPTION(location, desc) \
{ \
recordError(location, desc); \
if (!shouldContinue()) \
return false; \
}
void Object::simplifyRequiredProperties() {
// if a property of the current object was marked as required
// do not store that information in the ExtraData
// but rather mark the property as required
QSet<int> required;
for (auto it = this->requiredPropertyExtraDataBegin(); it != this->requiredPropertyExtraDataEnd(); ++it)
required.insert(it->nameIndex);
if (required.isEmpty())
return;
for (auto it = this->propertiesBegin(); it != this->propertiesEnd(); ++it) {
auto requiredIt = required.find(it->nameIndex());
if (requiredIt != required.end()) {
it->setIsRequired(true);
required.erase(requiredIt);
}
}
QmlIR::RequiredPropertyExtraData *prev = nullptr;
auto current = this->requiredPropertyExtraDatas->first;
while (current) {
if (required.contains(current->nameIndex))
prev = current;
else
requiredPropertyExtraDatas->unlink(prev, current);
current = current->next;
}
}
/*!
\internal
Reorders the alias data within the linked list of this object so that
aliases targeting other aliases on the same object come after their
targets. This ensures that dependencies are resolved and appended to
the property cache first, which is necessary because the runtime
assumes that the property cache position matches the alias table index.
Only considers same-object dependencies (where the alias's idIndex
resolves to idNameIndex). Cross-object dependencies are handled by
the multi-pass resolution in QQmlCOmponentAndAliasResolver.
*/
void Object::sortAliasDependencies(const Document *doc, QList<QQmlJS::DiagnosticMessage> *errors)
{
using AliasArray = QVarLengthArray<Alias *, 8>;
AliasArray ordered;
ordered.reserve(aliasCount());
// if the default property is an alias, we need to later update the default property index
Alias *defaultPropertyAlias = nullptr;
qsizetype aliasCounter = 0;
// Collect aliases as nodes in a graph. Non-local ones are already ordered.
AliasArray nodes;
for (Alias *a = firstAlias(); a; ++aliasCounter, a = a->next) {
if (defaultPropertyIsAlias && aliasCounter == indexOfDefaultPropertyOrAlias) {
defaultPropertyAlias = a;
}
if (a->idIndex() == idNameIndex && idNameIndex != 0)
nodes.append(a);
else
ordered.append(a);
}
// Nothing to sort here.
if (nodes.isEmpty())
return;
// Collect dependencies as edges between nodes
QVarLengthArray<qsizetype, 8> edges(nodes.size(), -1);
for (qsizetype i = 0, end = nodes.size(); i < end; ++i) {
const QStringView propValue = doc->stringAt(nodes[i]->propertyNameIndex());
const int dotIdx = propValue.indexOf(QLatin1Char('.'));
const QStringView targetName = dotIdx != -1 ? propValue.left(dotIdx) : propValue;
if (targetName.isEmpty())
continue;
for (qsizetype j = 0; j < end; ++j) {
if (j != i && doc->stringAt(nodes[j]->nameIndex()) == targetName) {
edges[i] = j;
break;
}
}
}
// Simple DFS-based topological sort
for (qsizetype i = 0, end = nodes.size(); i < end; ++i) {
// Skip already inserted nodes
if (nodes[i] == nullptr)
continue;
// Follow the dependency chain to find the root.
QVarLengthArray<qsizetype, 8> chain;
for (qsizetype j = edges[i]; j != -1 && nodes[j]; j = edges[j]) {
if (!chain.contains(j)) {
chain.append(j);
continue;
}
const QV4::CompiledData::Location &location = nodes[j]->location();
QQmlJS::DiagnosticMessage error;
error.loc.startLine = location.line();
error.loc.startColumn = location.column();
error.loc.offset = QQmlJS::SourceLocation::offsetFrom(
doc->code, location.line(), location.column());
error.message = QCoreApplication::translate("QQmlParser", "Cyclic alias");
errors->append(std::move(error));
return;
}
// Emit in reverse (dependency first)
for (qsizetype k = chain.size() - 1; k >= 0; --k)
ordered.append(std::exchange(nodes[chain[k]], nullptr));
ordered.append(std::exchange(nodes[i], nullptr));
}
// Apply the sorted order to the alias list.
setFirstAlias(ordered[0]);
for (qsizetype i = 0, end = ordered.size() - 1; i < end; ++i)
ordered[i]->next = ordered[i + 1];
ordered.last()->next = nullptr;
if (defaultPropertyAlias) {
auto it = std::find(ordered.constBegin(), ordered.constEnd(), defaultPropertyAlias);
indexOfDefaultPropertyOrAlias = std::distance(ordered.constBegin(), it);
}
}
bool Parameter::initType(
QV4::CompiledData::ParameterType *paramType,
const QString &typeName, int typeNameIndex,
QV4::CompiledData::ParameterType::Flag listFlag)
{
auto builtinType = stringToBuiltinType(typeName);
if (builtinType == QV4::CompiledData::CommonType::Invalid) {
if (typeName.isEmpty()) {
paramType->set(listFlag, 0);
return false;
}
Q_ASSERT(quint32(typeNameIndex) < (1u << 31));
paramType->set(listFlag, typeNameIndex);
} else {
Q_ASSERT(quint32(builtinType) < (1u << 31));
paramType->set(listFlag | QV4::CompiledData::ParameterType::Common,
static_cast<quint32>(builtinType));
}
return true;
}
QV4::CompiledData::CommonType Parameter::stringToBuiltinType(const QString &typeName)
{
static const struct TypeNameToType {
const char *name;
size_t nameLength;
QV4::CompiledData::CommonType type;
} propTypeNameToTypes[] = {
{ "void", strlen("void"), QV4::CompiledData::CommonType::Void },
{ "int", strlen("int"), QV4::CompiledData::CommonType::Int },
{ "bool", strlen("bool"), QV4::CompiledData::CommonType::Bool },
{ "double", strlen("double"), QV4::CompiledData::CommonType::Real },
{ "real", strlen("real"), QV4::CompiledData::CommonType::Real },
{ "string", strlen("string"), QV4::CompiledData::CommonType::String },
{ "url", strlen("url"), QV4::CompiledData::CommonType::Url },
{ "date", strlen("date"), QV4::CompiledData::CommonType::DateTime },
{ "regexp", strlen("regexp"), QV4::CompiledData::CommonType::RegExp },
{ "rect", strlen("rect"), QV4::CompiledData::CommonType::Rect },
{ "point", strlen("point"), QV4::CompiledData::CommonType::Point },
{ "size", strlen("size"), QV4::CompiledData::CommonType::Size },
{ "variant", strlen("variant"), QV4::CompiledData::CommonType::Var },
{ "var", strlen("var"), QV4::CompiledData::CommonType::Var }
};
static const int propTypeNameToTypesCount = sizeof(propTypeNameToTypes) /
sizeof(propTypeNameToTypes[0]);
for (int typeIndex = 0; typeIndex < propTypeNameToTypesCount; ++typeIndex) {
const TypeNameToType *t = propTypeNameToTypes + typeIndex;
if (typeName == QLatin1String(t->name, static_cast<int>(t->nameLength))) {
return t->type;
}
}
return QV4::CompiledData::CommonType::Invalid;
}
void Object::init(QQmlJS::MemoryPool *pool, int typeNameIndex, int idIndex,
const QV4::CompiledData::Location &loc)
{
Q_ASSERT(loc.line() > 0 && loc.column() > 0);
inheritedTypeNameIndex = typeNameIndex;
location = loc;
idNameIndex = idIndex;
id = -1;
indexOfDefaultPropertyOrAlias = -1;
defaultPropertyIsAlias = false;
flags = QV4::CompiledData::Object::NoFlag;
properties = pool->New<PoolList<Property> >();
aliases = pool->New<PoolList<Alias> >();
qmlEnums = pool->New<PoolList<Enum>>();
qmlSignals = pool->New<PoolList<Signal> >();
bindings = pool->New<PoolList<Binding> >();
functions = pool->New<PoolList<Function> >();
functionsAndExpressions = pool->New<PoolList<CompiledFunctionOrExpression> >();
inlineComponents = pool->New<PoolList<InlineComponent>>();
requiredPropertyExtraDatas = pool->New<PoolList<RequiredPropertyExtraData>>();
declarationsOverride = nullptr;
}
QString IRBuilder::sanityCheckFunctionNames(Object *obj, QQmlJS::SourceLocation *errorLocation)
{
QSet<int> functionNames;
for (auto functionit = obj->functionsBegin(); functionit != obj->functionsEnd(); ++functionit) {
Function *f = functionit.ptr;
errorLocation->startLine = f->location.line();
errorLocation->startColumn = f->location.column();
if (f->isQmlFunction) {
if (functionNames.contains(f->nameIndex))
return tr("Duplicate method name");
functionNames.insert(f->nameIndex);
}
for (auto signalit = obj->signalsBegin(); signalit != obj->signalsEnd(); ++signalit) {
QmlIR::Signal *s = signalit.ptr;
if (s->nameIndex == f->nameIndex)
return tr("Duplicate method name");
}
const QString name = stringAt(f->nameIndex);
Q_ASSERT(!f->isQmlFunction || !name.isEmpty());
if (!name.isEmpty() && name.at(0).isUpper())
return tr("Method names cannot begin with an upper case letter");
if (QV4::Compiler::Codegen::isNameGlobal(name))
return tr("Illegal method name");
}
return QString(); // no error
}
QString Object::appendEnum(Enum *enumeration)
{
Object *target = declarationsOverride;
if (!target)
target = this;
for (Enum *e = qmlEnums->first; e; e = e->next) {
if (e->nameIndex == enumeration->nameIndex)
return tr("Duplicate scoped enum name");
}
target->qmlEnums->append(enumeration);
return QString(); // no error
}
QString Object::appendSignal(Signal *signal)
{
Object *target = declarationsOverride;
if (!target)
target = this;
for (Signal *s = qmlSignals->first; s; s = s->next) {
if (s->nameIndex == signal->nameIndex)
return tr("Duplicate signal name");
}
target->qmlSignals->append(signal);
return QString(); // no error
}
QString Object::appendProperty(Property *prop, const QString &propertyName, bool isDefaultProperty, const QQmlJS::SourceLocation &defaultToken, QQmlJS::SourceLocation *errorLocation)
{
Object *target = declarationsOverride;
if (!target)
target = this;
for (Property *p = target->properties->first; p; p = p->next)
if (p->nameIndex() == prop->nameIndex())
return tr("Duplicate property name");
for (Alias *a = target->aliases->first; a; a = a->next)
if (a->nameIndex() == prop->nameIndex())
return tr("Property duplicates alias name");
if (propertyName.constData()->isUpper())
return tr("Property names cannot begin with an upper case letter");
const int index = target->properties->append(prop);
if (isDefaultProperty) {
if (target->indexOfDefaultPropertyOrAlias != -1) {
*errorLocation = defaultToken;
return tr("Duplicate default property");
}
target->indexOfDefaultPropertyOrAlias = index;
}
return QString(); // no error
}
QString Object::appendAlias(Alias *alias, const QString &aliasName, bool isDefaultProperty, const QQmlJS::SourceLocation &defaultToken, QQmlJS::SourceLocation *errorLocation)
{
Object *target = declarationsOverride;
if (!target)
target = this;
const auto aliasWithSameName = std::find_if(target->aliases->begin(), target->aliases->end(), [&alias](const Alias &targetAlias){
return targetAlias.nameIndex() == alias->nameIndex();
});
if (aliasWithSameName != target->aliases->end())
return tr("Duplicate alias name");
const auto aliasSameAsProperty = std::find_if(target->properties->begin(), target->properties->end(), [&alias](const Property &targetProp){
return targetProp.nameIndex() == alias->nameIndex();
});
if (aliasSameAsProperty != target->properties->end())
return tr("Alias has same name as existing property");
if (aliasName.constData()->isUpper())
return tr("Alias names cannot begin with an upper case letter");
const int index = target->aliases->append(alias);
if (isDefaultProperty) {
if (target->indexOfDefaultPropertyOrAlias != -1) {
*errorLocation = defaultToken;
return tr("Duplicate default property");
}
target->indexOfDefaultPropertyOrAlias = index;
target->defaultPropertyIsAlias = true;
}
return QString(); // no error
}
void Object::appendFunction(QmlIR::Function *f)
{
// Unlike properties, a function definition inside a grouped property does not go into
// the surrounding object. It's been broken since the Qt 5 era, and the semantics
// seems super confusing, so it wouldn't make sense to support that.
Q_ASSERT(!declarationsOverride);
functions->append(f);
}
void Object::appendInlineComponent(InlineComponent *ic)
{
inlineComponents->append(ic);
}
void Object::appendRequiredPropertyExtraData(RequiredPropertyExtraData *extraData)
{
requiredPropertyExtraDatas->append(extraData);
}
QString Object::appendBinding(Binding *b, bool isListBinding)
{
const bool bindingToDefaultProperty = (b->propertyNameIndex == quint32(0));
if (!isListBinding
&& !bindingToDefaultProperty
&& b->type() != QV4::CompiledData::Binding::Type_GroupedProperty
&& b->type() != QV4::CompiledData::Binding::Type_AttachedProperty
&& !b->hasFlag(QV4::CompiledData::Binding::IsOnAssignment)) {
Binding *existing = findBinding(b->propertyNameIndex);
if (existing
&& existing->type() != QV4::CompiledData::Binding::Type_GroupedProperty
&& existing->type() != QV4::CompiledData::Binding::Type_AttachedProperty
&& !existing->hasFlag(QV4::CompiledData::Binding::IsOnAssignment)) {
return tr("Property value set multiple times");
}
}
if (bindingToDefaultProperty)
bindings->append(b);
else
bindings->prepend(b);
return QString(); // no error
}
Binding *Object::findBinding(quint32 nameIndex) const
{
for (Binding *b = bindings->first; b; b = b->next)
if (b->propertyNameIndex == nameIndex)
return b;
return nullptr;
}
QString Object::bindingAsString(Document *doc, int scriptIndex) const
{
CompiledFunctionOrExpression *foe = functionsAndExpressions->slowAt(scriptIndex);
QQmlJS::AST::Node *node = foe->node;
if (QQmlJS::AST::ExpressionStatement *exprStmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(node))
node = exprStmt->expression;
QQmlJS::SourceLocation start = node->firstSourceLocation();
QQmlJS::SourceLocation end = node->lastSourceLocation();
return doc->code.mid(start.offset, end.offset + end.length - start.offset);
}
QStringList Signal::parameterStringList(const QV4::Compiler::StringTableGenerator *stringPool) const
{
QStringList result;
result.reserve(parameters->count);
for (Parameter *param = parameters->first; param; param = param->next)
result << stringPool->stringForIndex(param->nameIndex);
return result;
}
Document::Document(const QString &fileName, const QString &finalUrl, bool debugMode)
: jsModule(fileName, finalUrl, debugMode)
, program(nullptr)
, jsGenerator(&jsModule)
{
}
ScriptDirectivesCollector::ScriptDirectivesCollector(Document *doc)
: document(doc)
, engine(&doc->jsParserEngine)
, jsGenerator(&doc->jsGenerator)
{
}
void ScriptDirectivesCollector::pragmaLibrary()
{
document->jsModule.unitFlags |= QV4::CompiledData::Unit::IsSharedLibrary;
}
void ScriptDirectivesCollector::importFile(const QString &jsfile, const QString &module, int lineNumber, int column)
{
QV4::CompiledData::Import *import = engine->pool()->New<QV4::CompiledData::Import>();
import->type = QV4::CompiledData::Import::ImportScript;
import->uriIndex = jsGenerator->registerString(jsfile);
import->qualifierIndex = jsGenerator->registerString(module);
import->location.set(lineNumber, column);
document->imports << import;
}
void ScriptDirectivesCollector::importModule(const QString &uri, const QString &version, const QString &module, int lineNumber, int column)
{
QV4::CompiledData::Import *import = engine->pool()->New<QV4::CompiledData::Import>();
import->type = QV4::CompiledData::Import::ImportLibrary;
import->uriIndex = jsGenerator->registerString(uri);
import->version = IRBuilder::extractVersion(version);
import->qualifierIndex = jsGenerator->registerString(module);
import->location.set(lineNumber, column);
document->imports << import;
}
IRBuilder::IRBuilder(FailureMode failureMode)
: _object(nullptr)
, _propertyDeclaration(nullptr)
, pool(nullptr)
, jsGenerator(nullptr)
, m_failureMode(failureMode)
{
}
bool IRBuilder::generateFromQml(const QString &code, const QString &url, Document *output,
QV4::Compiler::CodegenWarningInterface *wInterface)
{
QQmlJS::AST::UiProgram *program = nullptr;
{
QQmlJS::Lexer lexer(&output->jsParserEngine);
lexer.setCode(code, /*line = */ 1);
QQmlJS::Parser parser(&output->jsParserEngine);
const bool parseResult = parser.parse();
const auto diagnosticMessages = parser.diagnosticMessages();
if (!parseResult || !diagnosticMessages.isEmpty()) {
// Extract errors from the parser
for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) {
if (m.isWarning()) {
wInterface->reportParserWarnings(url, m.loc, m.message);
continue;
}
errors << m;
}
if (!errors.isEmpty() || !parseResult)
return false;
}
program = parser.ast();
Q_ASSERT(program);
}
output->code = code;
output->program = program;
qSwap(_imports, output->imports);
qSwap(_pragmas, output->pragmas);
qSwap(_objects, output->objects);
this->pool = output->jsParserEngine.pool();
this->jsGenerator = &output->jsGenerator;
Q_ASSERT(registerString(QString()) == emptyStringIndex);
sourceCode = code;
accept(program->headers);
if (program->members->next) {
QQmlJS::SourceLocation loc = program->members->next->firstSourceLocation();
recordError(loc, QCoreApplication::translate("QQmlParser", "Unexpected object definition"));
if (!shouldContinue())
return false;
}
QQmlJS::AST::UiObjectDefinition *rootObject = QQmlJS::AST::cast<QQmlJS::AST::UiObjectDefinition*>(program->members->member);
Q_ASSERT(rootObject);
int rootObjectIndex = -1;
if (defineQMLObject(&rootObjectIndex, rootObject)) {
Q_ASSERT(rootObjectIndex == 0);
}
qSwap(_imports, output->imports);
qSwap(_pragmas, output->pragmas);
qSwap(_objects, output->objects);
for (Object *object: std::as_const(output->objects)) {
object->simplifyRequiredProperties();
// Reorder aliases so that same-object dependencies come before their
// dependents. This later ensures the property cache ordering matches
// the alias table, which the runtime relies on.
object->sortAliasDependencies(output, &errors);
}
return errors.isEmpty();
}
bool IRBuilder::visit(QQmlJS::AST::UiProgram *)
{
Q_ASSERT(!"should not happen");
return false;
}
bool IRBuilder::visit(QQmlJS::AST::UiObjectDefinition *node)
{
// The grammar can't distinguish between two different definitions here:
// Item { ... }
// versus
// font { ... }
// The former is a new binding with no property name and "Item" as type name,
// and the latter is a binding to the font property with no type name but
// only initializer.
QQmlJS::AST::UiQualifiedId *lastId = node->qualifiedTypeNameId;
while (lastId->next)
lastId = lastId->next;
bool isType = lastId->name.data()->isUpper();
if (isType) {
int idx = 0;
if (!defineQMLObject(&idx, node))
return false;
const QQmlJS::SourceLocation nameLocation = node->qualifiedTypeNameId->identifierToken;
appendBinding(nameLocation, nameLocation, emptyStringIndex, idx);
} else {
int idx = 0;
const QQmlJS::SourceLocation location = node->qualifiedTypeNameId->firstSourceLocation();
if (!defineQMLObject(
&idx, /*qualfied type name id*/nullptr,
{ location.startLine, location.startColumn }, node->initializer,
/*declarations should go here*/_object)) {
return false;
}
appendBinding(node->qualifiedTypeNameId, idx);
}
return false;
}
bool IRBuilder::visit(QQmlJS::AST::UiInlineComponent *ast)
{
int idx = -1;
if (insideInlineComponent) {
recordError(ast->firstSourceLocation(), QLatin1String("Nested inline components are not supported"));
if (!shouldContinue())
return false;
}
if (inlineComponentsNames.contains(ast->name.toString())) {
recordError(ast->firstSourceLocation(), QLatin1String("Inline component names must be unique per file"));
if (!shouldContinue())
return false;
} else {
inlineComponentsNames.insert(ast->name.toString());
}
{
QScopedValueRollback<bool> rollBack {insideInlineComponent, true};
if (!defineQMLObject(&idx, ast->component))
return false;
}
Q_ASSERT(idx > 0);
Object* definedObject = _objects.at(idx);
definedObject->flags |= QV4::CompiledData::Object::IsInlineComponentRoot;
definedObject->flags |= QV4::CompiledData::Object::IsPartOfInlineComponent;
auto inlineComponent = New<InlineComponent>();
inlineComponent->nameIndex = registerString(ast->name.toString());
inlineComponent->objectIndex = idx;
auto location = ast->firstSourceLocation();
inlineComponent->location.set(location.startLine, location.startColumn);
_object->appendInlineComponent(inlineComponent);
return false;
}
bool IRBuilder::visit(QQmlJS::AST::UiObjectBinding *node)
{
int idx = 0;
const QQmlJS::SourceLocation location = node->qualifiedTypeNameId->firstSourceLocation();
if (!defineQMLObject(&idx, node->qualifiedTypeNameId,
{ location.startLine, location.startColumn }, node->initializer)) {
return false;
}
appendBinding(node->qualifiedId, idx, node->hasOnToken);
return false;
}
bool IRBuilder::visit(QQmlJS::AST::UiScriptBinding *node)
{
appendBinding(node->qualifiedId, node->statement, node);
return false;
}
bool IRBuilder::visit(QQmlJS::AST::UiArrayBinding *node)
{
const QQmlJS::SourceLocation qualifiedNameLocation = node->qualifiedId->identifierToken;
Object *object = nullptr;
QQmlJS::AST::UiQualifiedId *name = node->qualifiedId;
if (!resolveQualifiedId(&name, &object))
return false;
qSwap(_object, object);
const int propertyNameIndex = registerString(name->name.toString());
if (bindingsTarget()->findBinding(propertyNameIndex) != nullptr) {
recordError(name->identifierToken, tr("Property value set multiple times"));
if (!shouldContinue())
return false;
}
QVarLengthArray<QQmlJS::AST::UiArrayMemberList *, 16> memberList;
QQmlJS::AST::UiArrayMemberList *member = node->members;
while (member) {
memberList.append(member);
member = member->next;
}
for (int i = memberList.size() - 1; i >= 0; --i) {
member = memberList.at(i);
QQmlJS::AST::UiObjectDefinition *def = QQmlJS::AST::cast<QQmlJS::AST::UiObjectDefinition*>(member->member);
int idx = 0;
if (!defineQMLObject(&idx, def))
return false;
appendBinding(qualifiedNameLocation, name->identifierToken, propertyNameIndex, idx, /*isListItem*/ true);
}
qSwap(_object, object);
return false;
}
void IRBuilder::accept(QQmlJS::AST::Node *node)
{
QQmlJS::AST::Node::accept(node, this);
}
bool IRBuilder::defineQMLObject(
int *objectIndex, QQmlJS::AST::UiQualifiedId *qualifiedTypeNameId,
const QV4::CompiledData::Location &location, QQmlJS::AST::UiObjectInitializer *initializer,
Object *declarationsOverride)
{
if (QQmlJS::AST::UiQualifiedId *lastName = qualifiedTypeNameId) {
while (lastName->next)
lastName = lastName->next;
if (!lastName->name.constData()->isUpper()) {
recordError(lastName->identifierToken, tr("Expected type name"));
if (!shouldContinue())
return false;
}
}
Object *obj = New<Object>();
_objects.append(obj);
*objectIndex = _objects.size() - 1;
qSwap(_object, obj);
_object->init(pool, registerString(asString(qualifiedTypeNameId)), emptyStringIndex, location);
_object->declarationsOverride = declarationsOverride;
if (insideInlineComponent) {
_object->flags |= QV4::CompiledData::Object::IsPartOfInlineComponent;
}
// A new object is also a boundary for property declarations.
Property *declaration = nullptr;
qSwap(_propertyDeclaration, declaration);
accept(initializer);
qSwap(_propertyDeclaration, declaration);
qSwap(_object, obj);
if (!errors.isEmpty() && !shouldContinue())
return false;
QQmlJS::SourceLocation loc;
QString error = sanityCheckFunctionNames(obj, &loc);
if (!error.isEmpty()) {
recordError(loc, error);
if (!shouldContinue())
return false;
}
return true;
}
bool IRBuilder::visit(QQmlJS::AST::UiImport *node)
{
QString uri;
QV4::CompiledData::Import *import = New<QV4::CompiledData::Import>();
if (!node->fileName.isNull()) {
uri = node->fileName.toString();
if (uri.endsWith(QLatin1String(".js")) || uri.endsWith(QLatin1String(".mjs"))) {
import->type = QV4::CompiledData::Import::ImportScript;
} else {
import->type = QV4::CompiledData::Import::ImportFile;
}
} else {
import->type = QV4::CompiledData::Import::ImportLibrary;
uri = asString(node->importUri);
}
import->qualifierIndex = emptyStringIndex;
// Qualifier
if (!node->importId.isNull()) {
QString qualifier = node->importId.toString();
if (!qualifier.at(0).isUpper()) {
recordError(node->importIdToken, QCoreApplication::translate("QQmlParser","Invalid import qualifier '%1': must start with an uppercase letter").arg(qualifier));
if (!shouldContinue())
return false;
}
if (qualifier == QLatin1String("Qt")) {
recordError(node->importIdToken, QCoreApplication::translate("QQmlParser","Reserved name \"Qt\" cannot be used as an qualifier"));
if (!shouldContinue())
return false;
}
import->qualifierIndex = registerString(qualifier);
// Check for script qualifier clashes
bool isScript = import->type == QV4::CompiledData::Import::ImportScript;
for (int ii = 0; ii < _imports.size(); ++ii) {
const QV4::CompiledData::Import *other = _imports.at(ii);
bool otherIsScript = other->type == QV4::CompiledData::Import::ImportScript;
if ((isScript || otherIsScript) && qualifier == jsGenerator->stringForIndex(other->qualifierIndex)) {
recordError(node->importIdToken, QCoreApplication::translate("QQmlParser","Script import qualifiers must be unique."));
if (!shouldContinue())
return false;
}
}
} else if (import->type == QV4::CompiledData::Import::ImportScript) {
recordError(node->fileNameToken, QCoreApplication::translate("QQmlParser","Script import requires a qualifier"));
if (!shouldContinue())
return false;
}
if (node->version) {
import->version = node->version->version;
} else {
// Otherwise initialize the major and minor version to invalid to signal "latest".
import->version = QTypeRevision();
}
import->location.set(node->importToken.startLine, node->importToken.startColumn);
import->uriIndex = registerString(uri);
_imports.append(import);
return false;
}
template<typename Argument>
struct PragmaParser
{
static bool run(IRBuilder *builder, QQmlJS::AST::UiPragma *node, Pragma *pragma)
{
Q_ASSERT(builder);
Q_ASSERT(node);
Q_ASSERT(pragma);
bool isValid = true;
if (!isUnique(builder)) {
builder->recordError(
node->pragmaToken, QCoreApplication::translate(
"QQmlParser", "Multiple %1 pragmas found").arg(name()));
if (!builder->shouldContinue())
return false;
isValid = false;
}
pragma->type = type();
if (QQmlJS::AST::UiPragmaValueList *bad = assign(pragma, node->values)) {
builder->recordError(
node->pragmaToken, QCoreApplication::translate(
"QQmlParser", "Unknown %1 '%2' in pragma").arg(name(), bad->value));
if (!builder->shouldContinue())
return false;
isValid = false;
}
return isValid;
}
private:
static constexpr Pragma::PragmaType type()
{
if constexpr (std::is_same_v<Argument, Pragma::ComponentBehaviorValue>) {
return Pragma::ComponentBehavior;
} else if constexpr (std::is_same_v<Argument, Pragma::ListPropertyAssignBehaviorValue>) {
return Pragma::ListPropertyAssignBehavior;
} else if constexpr (std::is_same_v<Argument, Pragma::FunctionSignatureBehaviorValue>) {
return Pragma::FunctionSignatureBehavior;
} else if constexpr (std::is_same_v<Argument, Pragma::NativeMethodBehaviorValue>) {
return Pragma::NativeMethodBehavior;
} else if constexpr (std::is_same_v<Argument, Pragma::ValueTypeBehaviorValue>) {
return Pragma::ValueTypeBehavior;
}
Q_UNREACHABLE_RETURN(Pragma::PragmaType(-1));
}
template<typename F>
static QQmlJS::AST::UiPragmaValueList *iterateValues(
QQmlJS::AST::UiPragmaValueList *input, F &&process)
{
for (QQmlJS::AST::UiPragmaValueList *i = input; i; i = i->next) {
if (!process(i->value))
return i;
}
return nullptr;
}
static QQmlJS::AST::UiPragmaValueList *assign(
Pragma *pragma, QQmlJS::AST::UiPragmaValueList *values)
{
// We could use QMetaEnum here to make the code more compact,
// but it's probably more expensive.
if constexpr (std::is_same_v<Argument, Pragma::ComponentBehaviorValue>) {
return iterateValues(values, [pragma](QStringView value) {
if (value == "Unbound"_L1) {
pragma->componentBehavior = Pragma::Unbound;
return true;
}
if (value == "Bound"_L1) {
pragma->componentBehavior = Pragma::Bound;
return true;
}
return false;
});
} else if constexpr (std::is_same_v<Argument, Pragma::ListPropertyAssignBehaviorValue>) {
return iterateValues(values, [pragma](QStringView value) {
if (value == "Append"_L1) {
pragma->listPropertyAssignBehavior = Pragma::Append;
return true;
}
if (value == "Replace"_L1) {
pragma->listPropertyAssignBehavior = Pragma::Replace;
return true;
}
if (value == "ReplaceIfNotDefault"_L1) {
pragma->listPropertyAssignBehavior = Pragma::ReplaceIfNotDefault;
return true;
}
return false;
});
} else if constexpr (std::is_same_v<Argument, Pragma::FunctionSignatureBehaviorValue>) {
return iterateValues(values, [pragma](QStringView value) {
if (value == "Ignored"_L1) {
pragma->functionSignatureBehavior = Pragma::Ignored;
return true;
}
if (value == "Enforced"_L1) {
pragma->functionSignatureBehavior = Pragma::Enforced;
return true;
}
return false;
});
} else if constexpr (std::is_same_v<Argument, Pragma::NativeMethodBehaviorValue>) {
return iterateValues(values, [pragma](QStringView value) {
if (value == "AcceptThisObject"_L1) {
pragma->nativeMethodBehavior = Pragma::AcceptThisObject;
return true;
}
if (value == "RejectThisObject"_L1) {
pragma->nativeMethodBehavior = Pragma::RejectThisObject;
return true;
}
return false;
});
} else if constexpr (std::is_same_v<Argument, Pragma::ValueTypeBehaviorValue>) {
pragma->valueTypeBehavior = Pragma::ValueTypeBehaviorValues().toInt();
return iterateValues(values, [pragma](QStringView value) {
const auto setFlag = [pragma](Pragma::ValueTypeBehaviorValue flag, bool value) {
pragma->valueTypeBehavior
= Pragma::ValueTypeBehaviorValues(pragma->valueTypeBehavior)
.setFlag(flag, value).toInt();
};
if (value == "Reference"_L1) {
setFlag(Pragma::Copy, false);
return true;
}
if (value == "Copy"_L1) {
setFlag(Pragma::Copy, true);
return true;
}
if (value == "Inaddressable"_L1) {
setFlag(Pragma::Addressable, false);
return true;
}
if (value == "Addressable"_L1) {
setFlag(Pragma::Addressable, true);
return true;
}
if (value == "Inassertable"_L1) {
setFlag(Pragma::Assertable, false);
return true;
}
if (value == "Assertable"_L1) {
setFlag(Pragma::Assertable, true);
return true;
}
return false;
});
}
Q_UNREACHABLE_RETURN(nullptr);
}
static bool isUnique(const IRBuilder *builder)
{
for (const Pragma *prev : builder->_pragmas) {
if (prev->type == type())
return false;
}
return true;
};
static QLatin1StringView name()
{
switch (type()) {
case Pragma::ListPropertyAssignBehavior:
return "list property assign behavior"_L1;
case Pragma::ComponentBehavior:
return "component behavior"_L1;
case Pragma::FunctionSignatureBehavior:
return "function signature behavior"_L1;
case Pragma::NativeMethodBehavior:
return "native method behavior"_L1;
case Pragma::ValueTypeBehavior:
return "value type behavior"_L1;
default:
break;
}
Q_UNREACHABLE_RETURN(QLatin1StringView());
}
};
bool IRBuilder::visit(QQmlJS::AST::UiPragma *node)
{
Pragma *pragma = New<Pragma>();
bool isValid = true;
if (!node->name.isNull()) {
if (node->name == "Singleton"_L1) {
pragma->type = Pragma::Singleton;
} else if (node->name == "Strict"_L1) {
pragma->type = Pragma::Strict;
} else if (node->name == "ComponentBehavior"_L1) {
if (!PragmaParser<Pragma::ComponentBehaviorValue>::run(this, node, pragma)) {
if (!shouldContinue())
return false;
isValid = false;
}
} else if (node->name == "ListPropertyAssignBehavior"_L1) {
if (!PragmaParser<Pragma::ListPropertyAssignBehaviorValue>::run(this, node, pragma)) {
if (!shouldContinue())
return false;
isValid = false;
}
} else if (node->name == "FunctionSignatureBehavior"_L1) {
if (!PragmaParser<Pragma::FunctionSignatureBehaviorValue>::run(this, node, pragma)) {
if (!shouldContinue())
return false;
isValid = false;
}
} else if (node->name == "NativeMethodBehavior"_L1) {
if (!PragmaParser<Pragma::NativeMethodBehaviorValue>::run(this, node, pragma)) {
if (!shouldContinue())
return false;
isValid = false;
}
} else if (node->name == "ValueTypeBehavior"_L1) {
if (!PragmaParser<Pragma::ValueTypeBehaviorValue>::run(this, node, pragma)) {
if (!shouldContinue())
return false;
isValid = false;
}
} else if (node->name == "Translator"_L1) {
pragma->type = Pragma::Translator;
pragma->translationContextIndex = registerString(node->values->value.toString());
} else {
recordError(node->pragmaToken, QCoreApplication::translate(
"QQmlParser", "Unknown pragma '%1'").arg(node->name));
if (!shouldContinue())
return false;
isValid = false;
}
} else {
recordError(node->pragmaToken, QCoreApplication::translate(
"QQmlParser", "Empty pragma found"));
if (!shouldContinue())
return false;
isValid = false;
}
if (isValid) {
pragma->location.set(node->pragmaToken.startLine, node->pragmaToken.startColumn);
_pragmas.append(pragma);
}
return false;
}
static QStringList astNodeToStringList(QQmlJS::AST::Node *node)
{
if (node->kind == QQmlJS::AST::Node::Kind_IdentifierExpression) {
QString name =
static_cast<QQmlJS::AST::IdentifierExpression *>(node)->name.toString();
return QStringList() << name;
} else if (node->kind == QQmlJS::AST::Node::Kind_FieldMemberExpression) {
QQmlJS::AST::FieldMemberExpression *expr = static_cast<QQmlJS::AST::FieldMemberExpression *>(node);
QStringList rv = astNodeToStringList(expr->base);
if (rv.isEmpty())
return rv;
rv.append(expr->name.toString());
return rv;
}
return QStringList();
}
bool IRBuilder::visit(QQmlJS::AST::UiEnumDeclaration *node)
{
Enum *enumeration = New<Enum>();
bool isValid = true;
QString enumName = node->name.toString();
enumeration->nameIndex = registerString(enumName);
if (enumName.at(0).isLower()) {
COMPILE_EXCEPTION(node->enumToken, tr("Scoped enum names must begin with an upper case letter"));
isValid = false;
}
enumeration->location.set(node->enumToken.startLine, node->enumToken.startColumn);
enumeration->enumValues = New<PoolList<EnumValue>>();
QQmlJS::AST::UiEnumMemberList *e = node->members;
while (e) {
EnumValue *enumValue = New<EnumValue>();
QString member = e->member.toString();
enumValue->nameIndex = registerString(member);
bool isValueValid = true;
if (member.at(0).isLower()) {
COMPILE_EXCEPTION(e->memberToken, tr("Enum names must begin with an upper case letter"));
isValueValid = false;
}
double part;
if (std::modf(e->value, &part) != 0.0) {
COMPILE_EXCEPTION(e->valueToken, tr("Enum value must be an integer"));
isValueValid = false;
}
if (e->value > std::numeric_limits<qint32>::max()
|| e->value < std::numeric_limits<qint32>::min()) {
COMPILE_EXCEPTION(e->valueToken, tr("Enum value out of range"));
isValueValid = false;
}
if (isValueValid) {
enumValue->value = e->value;
enumeration->enumValues->append(enumValue);
} else {
isValid = false;
}
e = e->next;
}
QString error;
if (isValid)
error = _object->appendEnum(enumeration);
if (!error.isEmpty()) {
recordError(node->enumToken, error);
if (!shouldContinue())
return false;
}
return false;
}
bool IRBuilder::visit(QQmlJS::AST::UiPublicMember *node)
{
if (node->type == QQmlJS::AST::UiPublicMember::Signal) {
Signal *signal = New<Signal>();
bool isValid = true;
const QString signalName = node->name.toString();
signal->nameIndex = registerString(signalName);
QQmlJS::SourceLocation loc = node->typeToken;
signal->location.set(loc.startLine, loc.startColumn);
signal->parameters = New<PoolList<Parameter> >();
QQmlJS::AST::UiParameterList *p = node->parameters;
while (p) {
if (!p->type) {
recordError(node->typeToken, QCoreApplication::translate("QQmlParser","Expected parameter type"));
if (!shouldContinue())
return false;
isValid = false;
p = p->next;
continue;
}
Parameter *param = New<Parameter>();
param->nameIndex = registerString(p->name.toString());
if (!Parameter::initType(
¶m->type, [this](const QString &str) { return registerString(str); },
p->type)) {
QString errStr = QCoreApplication::translate("QQmlParser","Invalid signal parameter type: ");
errStr.append(p->type->toString());
recordError(node->typeToken, errStr);
if (!shouldContinue())
return false;
isValid = false;
p = p->next;
continue;
}
signal->parameters->append(param);
p = p->next;
}
for (const QChar &ch : signalName) {
if (ch.isLower())
break;
if (ch.isUpper()) {
COMPILE_EXCEPTION(node->identifierToken,
tr("Signal names cannot begin with an upper case letter"));
isValid = false;
break;
}
}
if (QV4::Compiler::Codegen::isNameGlobal(signalName)) {
COMPILE_EXCEPTION(node->identifierToken, tr("Illegal signal name"));
isValid = false;
}
QString error;
if (isValid)
error = _object->appendSignal(signal);
if (!error.isEmpty()) {
recordError(node->identifierToken, error);
if (!shouldContinue())
return false;
}
} else {
QString memberType = asString(node->memberType);
if (memberType == QLatin1String("alias")) {
return appendAlias(node);
} else {
QStringView name = node->name;
bool isValid = true;
Property *property = New<Property>();
property->setIsReadOnly(node->isReadonly());
property->setIsRequired(node->isRequired());
property->setIsVirtual(node->isVirtual());
property->setIsOverride(node->isOverride());
property->setIsFinal(node->isFinal());
const QV4::CompiledData::CommonType builtinPropertyType
= Parameter::stringToBuiltinType(memberType);
if (builtinPropertyType != QV4::CompiledData::CommonType::Invalid)
property->setCommonType(builtinPropertyType);
else
property->setTypeNameIndex(registerString(memberType));
QStringView typeModifier = node->typeModifier;
if (typeModifier == QLatin1String("list")) {
property->setIsList(true);
} else if (!typeModifier.isEmpty()) {
recordError(node->typeModifierToken, QCoreApplication::translate("QQmlParser","Invalid property type modifier"));
if (!shouldContinue())
return false;
isValid = false;
}
const QString propName = name.toString();
property->setNameIndex(registerString(propName));
QQmlJS::SourceLocation loc = node->firstSourceLocation();
property->location.set(loc.startLine, loc.startColumn);
QQmlJS::SourceLocation errorLocation;
QString error;
if (QV4::Compiler::Codegen::isNameGlobal(propName))
error = tr("Illegal property name");
else if (isValid)
error = _object->appendProperty(property, propName, node->isDefaultMember(), node->defaultToken(), &errorLocation);
if (!error.isEmpty()) {
if (errorLocation.startLine == 0)
errorLocation = node->identifierToken;
recordError(errorLocation, error);
if (!shouldContinue())
return false;
isValid = false;
}
if (isValid) {
qSwap(_propertyDeclaration, property);
if (node->binding) {
// process QML-like initializers (e.g. property Object o: Object {})
QQmlJS::AST::Node::accept(node->binding, this);
} else if (node->statement) {
if (!isRedundantNullInitializerForPropertyDeclaration(_propertyDeclaration,
node->statement)) {
appendBinding(node->identifierToken, node->identifierToken,
_propertyDeclaration->nameIndex(), node->statement, node);
}
}
qSwap(_propertyDeclaration, property);
}
}
}
return false;
}
void IRBuilder::registerFunctionExpr(QQmlJS::AST::FunctionExpression *fexp, IsQmlFunction isQmlFunction)
{
CompiledFunctionOrExpression *foe = New<CompiledFunctionOrExpression>();
foe->node = fexp;
foe->parentNode = fexp;
foe->nameIndex = registerString(fexp->name.toString());
const int index = _object->functionsAndExpressions->append(foe);
Function *f = New<Function>();
QQmlJS::SourceLocation loc = fexp->identifierToken;
f->location.set(loc.startLine, loc.startColumn);
f->index = index;
f->nameIndex = registerString(fexp->name.toString());
f->isQmlFunction = isQmlFunction == IsQmlFunction::Yes;
const auto idGenerator = [this](const QString &str) { return registerString(str); };
Parameter::initType(
&f->returnType, idGenerator,
fexp->typeAnnotation ? fexp->typeAnnotation->type : nullptr);
const QQmlJS::AST::BoundNames formals = fexp->formals ? fexp->formals->formals()
: QQmlJS::AST::BoundNames();
int formalsCount = formals.size();
f->formals.allocate(pool, formalsCount);
int i = 0;
for (const auto &arg : formals) {
Parameter *functionParameter = &f->formals[i];
functionParameter->nameIndex = registerString(arg.id);
Parameter::initType(
&functionParameter->type, idGenerator,
arg.typeAnnotation.isNull() ? nullptr : arg.typeAnnotation->type);
++i;
}
_object->appendFunction(f);
}
bool IRBuilder::visit(QQmlJS::AST::UiSourceElement *node)
{
if (QQmlJS::AST::FunctionExpression *funDecl = node->sourceElement->asFunctionDefinition()) {
if (_object->declarationsOverride) {
// See Object::appendFunction() for why.
recordError(node->firstSourceLocation(),
QCoreApplication::translate(
"QQmlParser", "Function declaration inside grouped property"));
if (!shouldContinue())
return false;
} else {
registerFunctionExpr(funDecl, IsQmlFunction::Yes);
}
} else {
recordError(node->firstSourceLocation(), QCoreApplication::translate("QQmlParser","JavaScript declaration outside Script element"));
}
return false;
}
bool IRBuilder::visit(AST::UiRequired *ast)
{
auto extraData = New<RequiredPropertyExtraData>();
extraData->nameIndex = registerString(ast->name.toString());
_object->appendRequiredPropertyExtraData(extraData);
return false;
}
QString IRBuilder::asString(QQmlJS::AST::UiQualifiedId *node)
{
QString s;
for (QQmlJS::AST::UiQualifiedId *it = node; it; it = it->next) {
s.append(it->name);
if (it->next)
s.append(QLatin1Char('.'));
}
return s;
}
QStringView IRBuilder::asStringRef(QQmlJS::AST::Node *node)
{
if (!node)
return QStringView();
return textRefAt(node->firstSourceLocation(), node->lastSourceLocation());
}
QTypeRevision IRBuilder::extractVersion(QStringView string)
{
if (string.isEmpty())
return QTypeRevision();
const int dot = string.indexOf(QLatin1Char('.'));
return (dot < 0)
? QTypeRevision::fromMajorVersion(string.toInt())
: QTypeRevision::fromVersion(string.left(dot).toInt(), string.mid(dot + 1).toInt());
}
QStringView IRBuilder::textRefAt(const QQmlJS::SourceLocation &first, const QQmlJS::SourceLocation &last) const
{
return QStringView(sourceCode).mid(first.offset, last.offset + last.length - first.offset);
}
void IRBuilder::setBindingValue(QV4::CompiledData::Binding *binding, QQmlJS::AST::Statement *statement, QQmlJS::AST::Node *parentNode)
{
QQmlJS::SourceLocation loc = statement->firstSourceLocation();
binding->valueLocation.set(loc.startLine, loc.startColumn);
binding->setType(QV4::CompiledData::Binding::Type_Invalid);
if (_propertyDeclaration && _propertyDeclaration->isReadOnly())
binding->setFlag(QV4::CompiledData::Binding::InitializerForReadOnlyDeclaration);
QQmlJS::AST::ExpressionStatement *exprStmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(statement);
if (exprStmt) {
QQmlJS::AST::ExpressionNode * const expr = exprStmt->expression;
if (QQmlJS::AST::StringLiteral *lit = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(expr)) {
binding->setType(QV4::CompiledData::Binding::Type_String);
binding->stringIndex = registerString(lit->value.toString());
} else if (QQmlJS::AST::TemplateLiteral *templateLit = QQmlJS::AST::cast<QQmlJS::AST::TemplateLiteral *>(expr);
templateLit && templateLit->hasNoSubstitution) {
// A template literal without substitution is just a string.
// With substitution, it could however be an arbitrarily complex expression
binding->setType(QV4::CompiledData::Binding::Type_String);
binding->stringIndex = registerString(templateLit->value.toString());
} else if (expr->kind == QQmlJS::AST::Node::Kind_TrueLiteral) {
binding->setType(QV4::CompiledData::Binding::Type_Boolean);
binding->value.b = true;
} else if (expr->kind == QQmlJS::AST::Node::Kind_FalseLiteral) {
binding->setType(QV4::CompiledData::Binding::Type_Boolean);
binding->value.b = false;
} else if (QQmlJS::AST::NumericLiteral *lit = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(expr)) {
binding->setType(QV4::CompiledData::Binding::Type_Number);
binding->value.constantValueIndex = jsGenerator->registerConstant(QV4::Encode(lit->value));
} else if (QQmlJS::AST::CallExpression *call = QQmlJS::AST::cast<QQmlJS::AST::CallExpression *>(expr)) {
if (QQmlJS::AST::IdentifierExpression *base = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression *>(call->base)) {
tryGeneratingTranslationBinding(base->name, call->arguments, binding);
// If it wasn't a translation binding, a normal script binding will be generated
// below.
}
} else if (QQmlJS::AST::cast<QQmlJS::AST::FunctionExpression *>(expr)) {
binding->setFlag(QV4::CompiledData::Binding::IsFunctionExpression);
} else if (QQmlJS::AST::UnaryMinusExpression *unaryMinus = QQmlJS::AST::cast<QQmlJS::AST::UnaryMinusExpression *>(expr)) {
if (QQmlJS::AST::NumericLiteral *lit = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(unaryMinus->expression)) {
binding->setType(QV4::CompiledData::Binding::Type_Number);
binding->value.constantValueIndex = jsGenerator->registerConstant(QV4::Encode(-lit->value));
}
} else if (QQmlJS::AST::cast<QQmlJS::AST::NullExpression *>(expr)) {
binding->setType(QV4::CompiledData::Binding::Type_Null);
binding->value.nullMarker = 0;
}
}
// Do binding instead
if (binding->type() == QV4::CompiledData::Binding::Type_Invalid) {
binding->setType(QV4::CompiledData::Binding::Type_Script);
CompiledFunctionOrExpression *expr = New<CompiledFunctionOrExpression>();
expr->node = statement;
expr->parentNode = parentNode;
expr->nameIndex = registerString(QLatin1String("expression for ")
+ stringAt(binding->propertyNameIndex));
const int index = bindingsTarget()->functionsAndExpressions->append(expr);
binding->value.compiledScriptIndex = index;
// We don't need to store the binding script as string, except for script strings
// and types with custom parsers. Those will be added later in the compilation phase.
// Except that we cannot recover the string when cachegen runs; we need to therefore retain
// "undefined" and "[]" (for QQmlListModel). Any other "special" strings (for the various
// literals) are already handled above
QQmlJS::AST::Node *nodeForString = statement;
if (exprStmt)
nodeForString = exprStmt->expression;
const QStringView source = asStringRef(nodeForString);
if (source == u"undefined") {
binding->stringIndex = registerString(u"undefined"_s);
} else if (qualifiedEnumDot(source) != -1) {
binding->stringIndex = registerString(source.toString());
} else if (const auto *arrayPattern =
QQmlJS::AST::cast<QQmlJS::AST::ArrayPattern *>(nodeForString);
arrayPattern && !arrayPattern->elements) {
binding->stringIndex = registerString(u"[]"_s);
} else {
binding->stringIndex = emptyStringIndex;
}
}
}
void IRBuilder::tryGeneratingTranslationBinding(QStringView base, AST::ArgumentList *args, QV4::CompiledData::Binding *binding)
{
const auto registerString = [&](QStringView string) {
return jsGenerator->registerString(string.toString()) ;
};
const auto finalizeTranslationData = [&](
QV4::CompiledData::Binding::Type type,
QV4::CompiledData::TranslationData translationData) {
binding->setType(type);
if (type == QV4::CompiledData::Binding::Type_Translation
|| type == QV4::CompiledData::Binding::Type_TranslationById) {
binding->value.translationDataIndex = jsGenerator->registerTranslation(translationData);
} else if (type == QV4::CompiledData::Binding::Type_String) {
binding->stringIndex = translationData.number;
}
};
tryGeneratingTranslationBindingBase(
base, args,
registerString, registerString, registerString, finalizeTranslationData);
}
void IRBuilder::appendBinding(QQmlJS::AST::UiQualifiedId *name, QQmlJS::AST::Statement *value, QQmlJS::AST::Node *parentNode)
{
const QQmlJS::SourceLocation qualifiedNameLocation = name->identifierToken;
Object *object = nullptr;
if (!resolveQualifiedId(&name, &object))
return;
if (_object == object && name->name == QLatin1String("id")) {
setId(name->identifierToken, value);
return;
}
qSwap(_object, object);
appendBinding(qualifiedNameLocation, name->identifierToken, registerString(name->name.toString()), value, parentNode);
qSwap(_object, object);
}
void IRBuilder::appendBinding(QQmlJS::AST::UiQualifiedId *name, int objectIndex, bool isOnAssignment)
{
const QQmlJS::SourceLocation qualifiedNameLocation = name->identifierToken;
Object *object = nullptr;
if (!resolveQualifiedId(&name, &object, isOnAssignment))
return;
qSwap(_object, object);
appendBinding(qualifiedNameLocation, name->identifierToken, registerString(name->name.toString()), objectIndex, /*isListItem*/false, isOnAssignment);
qSwap(_object, object);
}
void IRBuilder::appendBinding(const QQmlJS::SourceLocation &qualifiedNameLocation, const QQmlJS::SourceLocation &nameLocation, quint32 propertyNameIndex,
QQmlJS::AST::Statement *value, QQmlJS::AST::Node *parentNode)
{
Binding *binding = New<Binding>();
binding->propertyNameIndex = propertyNameIndex;
binding->offset = nameLocation.offset;
binding->location.set(nameLocation.startLine, nameLocation.startColumn);
binding->clearFlags();
setBindingValue(binding, value, parentNode);
QString error = bindingsTarget()->appendBinding(binding, /*isListBinding*/false);
if (!error.isEmpty()) {
recordError(qualifiedNameLocation, error);
}
}
void IRBuilder::appendBinding(const QQmlJS::SourceLocation &qualifiedNameLocation, const QQmlJS::SourceLocation &nameLocation, quint32 propertyNameIndex, int objectIndex, bool isListItem, bool isOnAssignment)
{
if (stringAt(propertyNameIndex) == QLatin1String("id")) {
recordError(nameLocation, tr("Invalid component id specification"));
if (!shouldContinue())
return;
} else {
Binding *binding = New<Binding>();
binding->propertyNameIndex = propertyNameIndex;
binding->offset = nameLocation.offset;
binding->location.set(nameLocation.startLine, nameLocation.startColumn);
const Object *obj = _objects.at(objectIndex);
binding->valueLocation = obj->location;
binding->clearFlags();
if (_propertyDeclaration && _propertyDeclaration->isReadOnly())
binding->setFlag(Binding::InitializerForReadOnlyDeclaration);
// No type name on the initializer means it must be a grouped property
if (_objects.at(objectIndex)->inheritedTypeNameIndex == emptyStringIndex)
binding->setType(Binding::Type_GroupedProperty);
else
binding->setType(Binding::Type_Object);
if (isOnAssignment)
binding->setFlag(Binding::IsOnAssignment);
if (isListItem)
binding->setFlag(Binding::IsListItem);
binding->value.objectIndex = objectIndex;
QString error = bindingsTarget()->appendBinding(binding, isListItem);
if (!error.isEmpty())
recordError(qualifiedNameLocation, error);
}
}
bool IRBuilder::appendAlias(QQmlJS::AST::UiPublicMember *node)
{
Alias *alias = New<Alias>();
bool isValid = true;
alias->setIsReadOnly(node->isReadonly());
const QString propName = node->name.toString();
alias->setNameIndex(registerString(propName));
QQmlJS::SourceLocation loc = node->firstSourceLocation();
alias->setLocation({loc.startLine, loc.startColumn});
alias->setPropertyNameIndex(emptyStringIndex);
if (!node->statement && !node->binding) {
COMPILE_EXCEPTION(loc, tr("No property alias location"));
isValid = false;
}
QQmlJS::SourceLocation rhsLoc;
if (node->binding)
rhsLoc = node->binding->firstSourceLocation();
else if (node->statement)
rhsLoc = node->statement->firstSourceLocation();
else
rhsLoc = node->semicolonToken;
alias->setReferenceLocation({rhsLoc.startLine, rhsLoc.startColumn});
QStringList aliasReference;
if (QQmlJS::AST::ExpressionStatement *stmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement*>(node->statement)) {
aliasReference = astNodeToStringList(stmt->expression);
if (aliasReference.isEmpty()) {
if (isStatementNodeScript(node->statement)) {
COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias reference. An alias reference must be specified as <id>, <id>.<property> or <id>.<value property>.<property>"));
} else {
COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias location"));
}
isValid = false;
}
} else {
COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias reference. An alias reference must be specified as <id>, <id>.<property> or <id>.<value property>.<property>"));
isValid = false;
}
if (isValid && (aliasReference.size() < 1 || aliasReference.size() > 3)) {
COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias reference. An alias reference must be specified as <id>, <id>.<property> or <id>.<value property>.<property>"));
isValid = false;
}
if (isValid)
alias->setIdIndex(registerString(aliasReference.first()));
QString propertyValue = aliasReference.value(1);
if (aliasReference.size() == 3)
propertyValue += QLatin1Char('.') + aliasReference.at(2);
alias->setPropertyNameIndex(registerString(propertyValue));
QQmlJS::SourceLocation errorLocation;
QString error;
if (QV4::Compiler::Codegen::isNameGlobal(propName)) {
error = tr("Illegal property name");
} else if (isValid) {
error = _object->appendAlias(alias, propName, node->isDefaultMember(), node->defaultToken(),
&errorLocation);
}
if (!error.isEmpty()) {
if (errorLocation.startLine == 0)
errorLocation = node->identifierToken;
recordError(errorLocation, error);
if (!shouldContinue())
return false;
}
return false;
}
Object *IRBuilder::bindingsTarget() const
{
if (_propertyDeclaration && _object->declarationsOverride)
return _object->declarationsOverride;
return _object;
}
bool IRBuilder::setId(const QQmlJS::SourceLocation &idLocation, QQmlJS::AST::Statement *value)
{
QQmlJS::SourceLocation loc = value->firstSourceLocation();
QStringView str;
QQmlJS::AST::Node *node = value;
if (QQmlJS::AST::ExpressionStatement *stmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(node)) {
if (QQmlJS::AST::StringLiteral *lit = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(stmt->expression)) {
str = lit->value;
node = nullptr;
} else
node = stmt->expression;
}
if (node && str.isEmpty())
str = asStringRef(node);
bool isValid = !str.isEmpty();
if (!isValid) {
COMPILE_EXCEPTION(loc, tr( "Invalid empty ID"));
} else {
QChar ch = str.at(0);
if (ch.isLetter() && !ch.isLower()) {
COMPILE_EXCEPTION(loc, tr( "IDs cannot start with an uppercase letter"));
isValid = false;
}
QChar u(QLatin1Char('_'));
if (!ch.isLetter() && ch != u) {
COMPILE_EXCEPTION(loc, tr( "IDs must start with a letter or underscore"));
isValid = false;
}
for (int ii = 1; ii < str.size(); ++ii) {
ch = str.at(ii);
if (!ch.isLetterOrNumber() && ch != u) {
COMPILE_EXCEPTION(loc, tr( "IDs must contain only letters, numbers, and underscores"));
isValid = false;
break;
}
}
QString idQString(str.toString());
if (QV4::Compiler::Codegen::isNameGlobal(idQString)) {
COMPILE_EXCEPTION(loc, tr( "ID illegally masks global JavaScript property"));
isValid = false;
}
if (_object->idNameIndex != emptyStringIndex) {
COMPILE_EXCEPTION(idLocation, tr("Property value set multiple times"));
isValid = false;
}
if (isValid) {
_object->idNameIndex = registerString(idQString);
_object->locationOfIdProperty.set(idLocation.startLine, idLocation.startColumn);
}
}
return isValid;
}
bool IRBuilder::resolveQualifiedId(QQmlJS::AST::UiQualifiedId **nameToResolve, Object **object, bool onAssignment)
{
QQmlJS::AST::UiQualifiedId *qualifiedIdElement = *nameToResolve;
if (qualifiedIdElement->name == QLatin1String("id") && qualifiedIdElement->next)
COMPILE_EXCEPTION(qualifiedIdElement->identifierToken, tr( "Invalid use of id property"));
// If it's a namespace, prepend the qualifier and we'll resolve it later to the correct type.
QString currentName = qualifiedIdElement->name.toString();
if (qualifiedIdElement->next) {
for (const QV4::CompiledData::Import* import : std::as_const(_imports))
if (import->qualifierIndex != emptyStringIndex
&& stringAt(import->qualifierIndex) == currentName) {
qualifiedIdElement = qualifiedIdElement->next;
currentName += QLatin1Char('.') + qualifiedIdElement->name;
if (!qualifiedIdElement->name.data()->isUpper())
COMPILE_EXCEPTION(qualifiedIdElement->firstSourceLocation(), tr("Expected type name"));
break;
}
}
*object = _object;
while (qualifiedIdElement->next) {
const quint32 propertyNameIndex = registerString(currentName);
const bool isAttachedProperty = qualifiedIdElement->name.data()->isUpper();
Binding *binding = (*object)->findBinding(propertyNameIndex);
if (binding) {
if (isAttachedProperty) {
if (!binding->isAttachedProperty())
binding = nullptr;
} else if (!binding->isGroupedProperty()) {
binding = nullptr;
}
}
if (!binding) {
binding = New<Binding>();
binding->propertyNameIndex = propertyNameIndex;
binding->offset = qualifiedIdElement->identifierToken.offset;
binding->location.set(qualifiedIdElement->identifierToken.startLine,
qualifiedIdElement->identifierToken.startColumn);
binding->valueLocation.set(qualifiedIdElement->next->identifierToken.startLine,
qualifiedIdElement->next->identifierToken.startColumn);
binding->clearFlags();
if (onAssignment)
binding->setFlag(QV4::CompiledData::Binding::IsOnAssignment);
if (isAttachedProperty)
binding->setType(QV4::CompiledData::Binding::Type_AttachedProperty);
else
binding->setType(QV4::CompiledData::Binding::Type_GroupedProperty);
int objIndex = 0;
if (!defineQMLObject(&objIndex, nullptr, binding->location, nullptr, nullptr))
return false;
binding->value.objectIndex = objIndex;
QString error = (*object)->appendBinding(binding, /*isListBinding*/false);
if (!error.isEmpty()) {
recordError(qualifiedIdElement->identifierToken, error);
if (!shouldContinue())
return false;
}
*object = _objects.at(objIndex);
} else {
Q_ASSERT(binding->isAttachedProperty() || binding->isGroupedProperty());
*object = _objects.at(binding->value.objectIndex);
}
qualifiedIdElement = qualifiedIdElement->next;
if (qualifiedIdElement)
currentName = qualifiedIdElement->name.toString();
}
*nameToResolve = qualifiedIdElement;
return true;
}
void IRBuilder::recordError(const QQmlJS::SourceLocation &location, const QString &description)
{
QQmlJS::DiagnosticMessage error;
error.loc = location;
error.message = description;
errors << error;
}
bool IRBuilder::isStatementNodeScript(QQmlJS::AST::Statement *statement)
{
if (QQmlJS::AST::ExpressionStatement *stmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(statement)) {
QQmlJS::AST::ExpressionNode *expr = stmt->expression;
if (QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(expr))
return false;
else if (expr->kind == QQmlJS::AST::Node::Kind_TrueLiteral)
return false;
else if (expr->kind == QQmlJS::AST::Node::Kind_FalseLiteral)
return false;
else if (QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(expr))
return false;
else {
if (QQmlJS::AST::UnaryMinusExpression *unaryMinus = QQmlJS::AST::cast<QQmlJS::AST::UnaryMinusExpression *>(expr)) {
if (QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(unaryMinus->expression)) {
return false;
}
}
}
}
return true;
}
bool IRBuilder::isRedundantNullInitializerForPropertyDeclaration(Property *property, QQmlJS::AST::Statement *statement)
{
if (property->isCommonType() || property->isList())
return false;
QQmlJS::AST::ExpressionStatement *exprStmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(statement);
if (!exprStmt)
return false;
QQmlJS::AST::ExpressionNode * const expr = exprStmt->expression;
return QQmlJS::AST::cast<QQmlJS::AST::NullExpression *>(expr);
}
void QmlUnitGenerator::generate(Document &output, const QV4::CompiledData::DependentTypesHasher &dependencyHasher)
{
using namespace QV4::CompiledData;
output.jsGenerator.stringTable.registerString(output.jsModule.fileName);
output.jsGenerator.stringTable.registerString(output.jsModule.finalUrl);
Unit *jsUnit = nullptr;
if (!output.javaScriptCompilationUnit)
output.javaScriptCompilationUnit.adopt(new QV4::CompiledData::CompilationUnit);
// We may already have unit data if we're loading an ahead-of-time generated cache file.
if (output.javaScriptCompilationUnit->unitData()) {
jsUnit = const_cast<Unit *>(output.javaScriptCompilationUnit->unitData());
output.javaScriptCompilationUnit->dynamicStrings
= output.jsGenerator.stringTable.allStrings();
} else {
Unit *createdUnit;
jsUnit = createdUnit = output.jsGenerator.generateUnit();
// enable flag if we encountered pragma Singleton
for (Pragma *p : std::as_const(output.pragmas)) {
switch (p->type) {
case Pragma::Singleton:
createdUnit->flags |= Unit::IsSingleton;
break;
case Pragma::Strict:
createdUnit->flags |= Unit::IsStrict;
break;
case Pragma::ComponentBehavior:
// ### Qt7: Change the default to Bound by reverting the meaning of the flag.
switch (p->componentBehavior) {
case Pragma::Bound:
createdUnit->flags |= Unit::ComponentsBound;
break;
case Pragma::Unbound:
// this is the default
break;
}
break;
case Pragma::ListPropertyAssignBehavior:
switch (p->listPropertyAssignBehavior) {
case Pragma::Replace:
createdUnit->flags |= Unit::ListPropertyAssignReplace;
break;
case Pragma::ReplaceIfNotDefault:
createdUnit->flags |= Unit::ListPropertyAssignReplaceIfNotDefault;
break;
case Pragma::Append:
// this is the default
break;
}
break;
case Pragma::FunctionSignatureBehavior:
switch (p->functionSignatureBehavior) {
case Pragma::Enforced:
break;
case Pragma::Ignored:
createdUnit->flags |= Unit::FunctionSignaturesIgnored;
break;
}
break;
case Pragma::NativeMethodBehavior:
switch (p->nativeMethodBehavior) {
case Pragma::AcceptThisObject:
createdUnit->flags |= Unit::NativeMethodsAcceptThisObject;
break;
case Pragma::RejectThisObject:
// this is the default;
break;
}
break;
case Pragma::ValueTypeBehavior:
if (Pragma::ValueTypeBehaviorValues(p->valueTypeBehavior)
.testFlag(Pragma::Copy)) {
createdUnit->flags |= Unit::ValueTypesCopied;
}
if (Pragma::ValueTypeBehaviorValues(p->valueTypeBehavior)
.testFlag(Pragma::Addressable)) {
createdUnit->flags |= Unit::ValueTypesAddressable;
}
if (Pragma::ValueTypeBehaviorValues(p->valueTypeBehavior)
.testFlag(Pragma::Assertable)) {
createdUnit->flags |= Unit::ValueTypesAssertable;
}
break;
case Pragma::Translator:
if (createdUnit->translationTableSize)
if (quint32_le *index = createdUnit->translationContextIndex())
*index = p->translationContextIndex;
break;
}
}
if (dependencyHasher) {
const QByteArray checksum = dependencyHasher();
if (checksum.size() == sizeof(createdUnit->dependencyMD5Checksum)) {
memcpy(createdUnit->dependencyMD5Checksum, checksum.constData(),
sizeof(createdUnit->dependencyMD5Checksum));
}
}
createdUnit->sourceFileIndex = output.jsGenerator.stringTable.getStringId(output.jsModule.fileName);
createdUnit->finalUrlIndex = output.jsGenerator.stringTable.getStringId(output.jsModule.finalUrl);
}
// No more new strings after this point, we're calculating offsets.
output.jsGenerator.stringTable.freeze();
const uint importSize = uint(sizeof(QV4::CompiledData::Import)) * output.imports.size();
const uint objectOffsetTableSize = output.objects.size() * uint(sizeof(quint32));
QHash<const Object*, quint32> objectOffsets;
const unsigned int objectOffset = sizeof(QV4::CompiledData::QmlUnit) + importSize;
uint nextOffset = objectOffset + objectOffsetTableSize;
for (Object *o : std::as_const(output.objects)) {
objectOffsets.insert(o, nextOffset);
nextOffset += QV4::CompiledData::Object::calculateSizeExcludingSignalsAndEnums(o->functionCount(), o->propertyCount(), o->aliasCount(), o->enumCount(), o->signalCount(), o->bindingCount(), o->namedObjectsInComponent.size(), o->inlineComponentCount(), o->requiredPropertyExtraDataCount());
int signalTableSize = 0;
for (const Signal *s = o->firstSignal(); s; s = s->next)
signalTableSize += QV4::CompiledData::Signal::calculateSize(s->parameters->count);
nextOffset += signalTableSize;
int enumTableSize = 0;
for (const Enum *e = o->firstEnum(); e; e = e->next)
enumTableSize += QV4::CompiledData::Enum::calculateSize(e->enumValues->count);
nextOffset += enumTableSize;
}
const uint totalSize = nextOffset;
char *data = (char*)malloc(totalSize);
memset(data, 0, totalSize);
QV4::CompiledData::QmlUnit *qmlUnit = reinterpret_cast<QV4::CompiledData::QmlUnit *>(data);
qmlUnit->offsetToImports = sizeof(*qmlUnit);
qmlUnit->nImports = output.imports.size();
qmlUnit->offsetToObjects = objectOffset;
qmlUnit->nObjects = output.objects.size();
// write imports
char *importPtr = data + qmlUnit->offsetToImports;
for (const QV4::CompiledData::Import *imp : std::as_const(output.imports)) {
QV4::CompiledData::Import *importToWrite = reinterpret_cast<QV4::CompiledData::Import*>(importPtr);
*importToWrite = *imp;
importPtr += sizeof(QV4::CompiledData::Import);
}
// write objects
quint32_le *objectTable = reinterpret_cast<quint32_le*>(data + qmlUnit->offsetToObjects);
for (int i = 0; i < output.objects.size(); ++i) {
const Object *o = output.objects.at(i);
char * const objectPtr = data + objectOffsets.value(o);
*objectTable++ = objectOffsets.value(o);
QV4::CompiledData::Object *objectToWrite = reinterpret_cast<QV4::CompiledData::Object*>(objectPtr);
objectToWrite->inheritedTypeNameIndex = o->inheritedTypeNameIndex;
objectToWrite->indexOfDefaultPropertyOrAlias = o->indexOfDefaultPropertyOrAlias;
objectToWrite->setHasAliasAsDefaultProperty(o->defaultPropertyIsAlias);
objectToWrite->setFlags(QV4::CompiledData::Object::Flags(o->flags));
objectToWrite->idNameIndex = o->idNameIndex;
objectToWrite->setObjectId(o->id);
objectToWrite->location = o->location;
objectToWrite->locationOfIdProperty = o->locationOfIdProperty;
quint32 nextOffset = sizeof(QV4::CompiledData::Object);
objectToWrite->nFunctions = o->functionCount();
objectToWrite->offsetToFunctions = nextOffset;
nextOffset += objectToWrite->nFunctions * sizeof(quint32);
objectToWrite->nProperties = o->propertyCount();
objectToWrite->offsetToProperties = nextOffset;
nextOffset += objectToWrite->nProperties * sizeof(QV4::CompiledData::Property);
objectToWrite->nAliases = o->aliasCount();
objectToWrite->offsetToAliases = nextOffset;
nextOffset += objectToWrite->nAliases * sizeof(QV4::CompiledData::Alias);
objectToWrite->nEnums = o->enumCount();
objectToWrite->offsetToEnums = nextOffset;
nextOffset += objectToWrite->nEnums * sizeof(quint32);
objectToWrite->nSignals = o->signalCount();
objectToWrite->offsetToSignals = nextOffset;
nextOffset += objectToWrite->nSignals * sizeof(quint32);
objectToWrite->nBindings = o->bindingCount();
objectToWrite->offsetToBindings = nextOffset;
nextOffset += objectToWrite->nBindings * sizeof(QV4::CompiledData::Binding);
objectToWrite->nNamedObjectsInComponent = o->namedObjectsInComponent.size();
objectToWrite->offsetToNamedObjectsInComponent = nextOffset;
nextOffset += objectToWrite->nNamedObjectsInComponent * sizeof(quint32);
objectToWrite->nInlineComponents = o->inlineComponentCount();
objectToWrite->offsetToInlineComponents = nextOffset;
nextOffset += objectToWrite->nInlineComponents * sizeof (QV4::CompiledData::InlineComponent);
objectToWrite->nRequiredPropertyExtraData = o->requiredPropertyExtraDataCount();
objectToWrite->offsetToRequiredPropertyExtraData = nextOffset;
nextOffset += objectToWrite->nRequiredPropertyExtraData * sizeof(QV4::CompiledData::RequiredPropertyExtraData);
quint32_le *functionsTable = reinterpret_cast<quint32_le *>(objectPtr + objectToWrite->offsetToFunctions);
for (const Function *f = o->firstFunction(); f; f = f->next)
*functionsTable++ = o->runtimeFunctionIndices.at(f->index);
char *propertiesPtr = objectPtr + objectToWrite->offsetToProperties;
for (const Property *p = o->firstProperty(); p; p = p->next) {
QV4::CompiledData::Property *propertyToWrite = reinterpret_cast<QV4::CompiledData::Property*>(propertiesPtr);
*propertyToWrite = *p;
propertiesPtr += sizeof(QV4::CompiledData::Property);
}
char *aliasesPtr = objectPtr + objectToWrite->offsetToAliases;
for (const Alias *a = o->firstAlias(); a; a = a->next) {
QV4::CompiledData::Alias *aliasToWrite = reinterpret_cast<QV4::CompiledData::Alias*>(aliasesPtr);
*aliasToWrite = *a;
aliasesPtr += sizeof(QV4::CompiledData::Alias);
}
char *bindingPtr = objectPtr + objectToWrite->offsetToBindings;
bindingPtr = writeBindings(bindingPtr, o);
Q_ASSERT((bindingPtr - objectToWrite->offsetToBindings - objectPtr) / sizeof(QV4::CompiledData::Binding) == unsigned(o->bindingCount()));
quint32_le *signalOffsetTable = reinterpret_cast<quint32_le *>(objectPtr + objectToWrite->offsetToSignals);
quint32 signalTableSize = 0;
char *signalPtr = objectPtr + nextOffset;
for (const Signal *s = o->firstSignal(); s; s = s->next) {
*signalOffsetTable++ = signalPtr - objectPtr;
QV4::CompiledData::Signal *signalToWrite = reinterpret_cast<QV4::CompiledData::Signal*>(signalPtr);
signalToWrite->nameIndex = s->nameIndex;
signalToWrite->location = s->location;
signalToWrite->nParameters = s->parameters->count;
QV4::CompiledData::Parameter *parameterToWrite = reinterpret_cast<QV4::CompiledData::Parameter*>(signalPtr + sizeof(*signalToWrite));
for (Parameter *param = s->parameters->first; param; param = param->next, ++parameterToWrite)
*parameterToWrite = *param;
int size = QV4::CompiledData::Signal::calculateSize(s->parameters->count);
signalTableSize += size;
signalPtr += size;
}
nextOffset += signalTableSize;
quint32_le *enumOffsetTable = reinterpret_cast<quint32_le*>(objectPtr + objectToWrite->offsetToEnums);
char *enumPtr = objectPtr + nextOffset;
for (const Enum *e = o->firstEnum(); e; e = e->next) {
*enumOffsetTable++ = enumPtr - objectPtr;
QV4::CompiledData::Enum *enumToWrite = reinterpret_cast<QV4::CompiledData::Enum*>(enumPtr);
enumToWrite->nameIndex = e->nameIndex;
enumToWrite->location = e->location;
enumToWrite->nEnumValues = e->enumValues->count;
QV4::CompiledData::EnumValue *enumValueToWrite = reinterpret_cast<QV4::CompiledData::EnumValue*>(enumPtr + sizeof(*enumToWrite));
for (EnumValue *enumValue = e->enumValues->first; enumValue; enumValue = enumValue->next, ++enumValueToWrite)
*enumValueToWrite = *enumValue;
int size = QV4::CompiledData::Enum::calculateSize(e->enumValues->count);
enumPtr += size;
}
quint32_le *namedObjectInComponentPtr = reinterpret_cast<quint32_le *>(objectPtr + objectToWrite->offsetToNamedObjectsInComponent);
for (int i = 0; i < o->namedObjectsInComponent.size(); ++i) {
*namedObjectInComponentPtr++ = o->namedObjectsInComponent.at(i);
}
char *inlineComponentPtr = objectPtr + objectToWrite->offsetToInlineComponents;
for (auto it = o->inlineComponentsBegin(); it != o->inlineComponentsEnd(); ++it) {
const InlineComponent *ic = it.ptr;
QV4::CompiledData::InlineComponent *icToWrite = reinterpret_cast<QV4::CompiledData::InlineComponent*>(inlineComponentPtr);
*icToWrite = *ic;
inlineComponentPtr += sizeof(QV4::CompiledData::InlineComponent);
}
char *requiredPropertyExtraDataPtr = objectPtr + objectToWrite->offsetToRequiredPropertyExtraData;
for (auto it = o->requiredPropertyExtraDataBegin(); it != o->requiredPropertyExtraDataEnd(); ++it) {
const RequiredPropertyExtraData *extraData = it.ptr;
QV4::CompiledData::RequiredPropertyExtraData *extraDataToWrite = reinterpret_cast<QV4::CompiledData::RequiredPropertyExtraData*>(requiredPropertyExtraDataPtr);
*extraDataToWrite = *extraData;
requiredPropertyExtraDataPtr += sizeof(QV4::CompiledData::RequiredPropertyExtraData);
}
}
if (!output.javaScriptCompilationUnit->unitData()) {
// Combine the qml data into the general unit data.
jsUnit = static_cast<QV4::CompiledData::Unit *>(realloc(jsUnit, jsUnit->unitSize + totalSize));
jsUnit->offsetToQmlUnit = jsUnit->unitSize;
jsUnit->unitSize += totalSize;
memcpy(jsUnit->qmlUnit(), qmlUnit, totalSize);
free(qmlUnit);
QV4::Compiler::JSUnitGenerator::generateUnitChecksum(jsUnit);
qmlUnit = jsUnit->qmlUnit();
}
static const bool showStats = qEnvironmentVariableIsSet("QML_SHOW_UNIT_STATS");
if (showStats) {
qDebug() << "Generated QML unit that is" << totalSize << "bytes big contains:";
qDebug() << " " << jsUnit->functionTableSize << "functions";
qDebug() << " " << jsUnit->unitSize << "for JS unit";
qDebug() << " " << importSize << "for imports";
qDebug() << " " << nextOffset - objectOffset - objectOffsetTableSize << "for" << qmlUnit->nObjects << "objects";
quint32 totalBindingCount = 0;
for (quint32 i = 0; i < qmlUnit->nObjects; ++i)
totalBindingCount += qmlUnit->objectAt(i)->nBindings;
qDebug() << " " << totalBindingCount << "bindings";
quint32 totalCodeSize = 0;
for (quint32 i = 0; i < jsUnit->functionTableSize; ++i)
totalCodeSize += jsUnit->functionAt(i)->codeSize;
qDebug() << " " << totalCodeSize << "bytes total byte code";
qDebug() << " " << jsUnit->stringTableSize << "strings";
quint32 totalStringSize = 0;
for (quint32 i = 0; i < jsUnit->stringTableSize; ++i)
totalStringSize += QV4::CompiledData::String::calculateSize(jsUnit->stringAtInternal(i));
qDebug() << " " << totalStringSize << "bytes total strings";
}
output.javaScriptCompilationUnit->setUnitData(
jsUnit, qmlUnit, output.jsModule.fileName, output.jsModule.finalUrl);
}
char *QmlUnitGenerator::writeBindings(char *bindingPtr, const Object *o) const
{
for (const Binding *b = o->firstBinding(); b; b = b->next) {
QV4::CompiledData::Binding *bindingToWrite = reinterpret_cast<QV4::CompiledData::Binding*>(bindingPtr);
*bindingToWrite = *b;
if (b->type() == QV4::CompiledData::Binding::Type_Script)
bindingToWrite->value.compiledScriptIndex = o->runtimeFunctionIndices.at(b->value.compiledScriptIndex);
bindingPtr += sizeof(QV4::CompiledData::Binding);
}
return bindingPtr;
}
JSCodeGen::JSCodeGen(
Document *document, QV4::Compiler::CodegenWarningInterface *iface,
bool storeSourceLocations)
: QV4::Compiler::Codegen(&document->jsGenerator, /*strict mode*/ false, iface,
storeSourceLocations),
document(document)
{
_module = &document->jsModule;
_fileNameIsUrl = true;
}
QList<int> JSCodeGen::generateJSCodeForFunctionsAndBindings(
const QList<CompiledFunctionOrExpression> &functions)
{
auto qmlName = [&](const CompiledFunctionOrExpression &c) {
if (c.nameIndex != 0)
return document->stringAt(c.nameIndex);
else
return QStringLiteral("%qml-expression-entry");
};
QList<int> runtimeFunctionIndices(functions.size());
QV4::Compiler::ScanFunctions scan(this, document->code, QV4::Compiler::ContextType::Global);
scan.enterGlobalEnvironment(QV4::Compiler::ContextType::Binding);
for (const CompiledFunctionOrExpression &f : functions) {
Q_ASSERT(f.node != document->program);
Q_ASSERT(f.parentNode && f.parentNode != document->program);
auto function = f.node->asFunctionDefinition();
if (function) {
scan.enterQmlFunction(function);
} else {
Q_ASSERT(f.node != f.parentNode);
scan.enterEnvironment(f.parentNode, QV4::Compiler::ContextType::Binding, qmlName(f));
}
/* We do not want to visit the whole function, as we already called enterQmlFunction
However, there might be a function defined as a default argument of the function.
That needs to be considered, too, so we call handleTopLevelFunctionFormals to
deal with them.
*/
scan.handleTopLevelFunctionFormals(function);
scan(function ? function->body : f.node);
scan.leaveEnvironment();
}
scan.leaveEnvironment();
if (hasError())
return QList<int>();
_context = nullptr;
for (int i = 0; i < functions.size(); ++i) {
const CompiledFunctionOrExpression &qmlFunction = functions.at(i);
QQmlJS::AST::Node *node = qmlFunction.node;
Q_ASSERT(node != document->program);
QQmlJS::AST::FunctionExpression *function = node->asFunctionDefinition();
QString name;
if (function)
name = function->name.toString();
else
name = qmlName(qmlFunction);
QQmlJS::AST::StatementList *body;
if (function) {
body = function->body;
} else {
// Synthesize source elements.
QQmlJS::MemoryPool *pool = document->jsParserEngine.pool();
QQmlJS::AST::Statement *stmt = node->statementCast();
if (!stmt) {
Q_ASSERT(node->expressionCast());
QQmlJS::AST::ExpressionNode *expr = node->expressionCast();
stmt = new (pool) QQmlJS::AST::ExpressionStatement(expr);
}
body = new (pool) QQmlJS::AST::StatementList(stmt);
body = body->finish();
}
int idx = defineFunction(name, function ? function : qmlFunction.parentNode,
function ? function->formals : nullptr, body);
runtimeFunctionIndices[i] = idx;
}
return runtimeFunctionIndices;
}
bool JSCodeGen::generateRuntimeFunctions(QmlIR::Object *object)
{
if (object->functionsAndExpressions->count == 0)
return true;
QList<QmlIR::CompiledFunctionOrExpression> functionsToCompile;
functionsToCompile.reserve(object->functionsAndExpressions->count);
for (QmlIR::CompiledFunctionOrExpression *foe = object->functionsAndExpressions->first; foe;
foe = foe->next) {
functionsToCompile << *foe;
}
const auto runtimeFunctionIndices = generateJSCodeForFunctionsAndBindings(functionsToCompile);
if (hasError())
return false;
object->runtimeFunctionIndices.allocate(document->jsParserEngine.pool(),
runtimeFunctionIndices);
return true;
}