Revision control
Copy as Markdown
// Copyright (C) 2020 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 "qqmldomtypesreader_p.h"
#include "qqmldomelements_p.h"
#include "qqmldomcompare_p.h"
#include "qqmldomfieldfilter_p.h"
#include <QtQml/private/qqmljsparser_p.h>
#include <QtQml/private/qqmljslexer_p.h>
#include <QtQml/private/qqmljsengine_p.h>
#include <private/qqmljstypedescriptionreader_p.h>
#include <QtCore/qdir.h>
QT_BEGIN_NAMESPACE
namespace QQmlJS {
namespace Dom {
static ErrorGroups readerParseErrors()
{
static ErrorGroups errs = { { NewErrorGroup("Dom"), NewErrorGroup("QmltypesFile"),
NewErrorGroup("Parsing") } };
return errs;
}
void QmltypesReader::insertProperty(
const QQmlJSScope::ConstPtr &jsScope, const QQmlJSMetaProperty &property,
QMap<int, QmlObject> &objs)
{
PropertyDefinition prop;
prop.name = property.propertyName();
prop.typeName = property.typeName();
prop.isPointer = property.isPointer();
prop.isReadonly = !property.isWritable();
prop.isRequired = jsScope->isPropertyLocallyRequired(prop.name);
prop.isList = property.isList();
int revision = property.revision();
prop.isFinal = property.isFinal();
prop.isVirtual = property.isVirtual();
prop.isOverride = property.isOverride();
prop.bindable = property.bindable();
prop.read = property.read();
prop.write = property.write();
prop.notify = property.notify();
if (prop.name.isEmpty() || prop.typeName.isEmpty()) {
addError(readerParseErrors()
.warning(tr("Property object is missing a name or type script binding."))
.handle());
return;
}
objs[revision].addPropertyDef(prop, AddOption::KeepExisting);
}
void QmltypesReader::insertSignalOrMethod(const QQmlJSMetaMethod &metaMethod,
QMap<int, QmlObject> &objs)
{
MethodInfo methodInfo;
// ### confusion between Method and Slot. Method should be removed.
switch (metaMethod.methodType()) {
case QQmlJSMetaMethodType::Method:
case QQmlJSMetaMethodType::Slot:
methodInfo.methodType = MethodInfo::MethodType::Method;
break;
case QQmlJSMetaMethodType::Signal:
methodInfo.methodType = MethodInfo::MethodType::Signal;
break;
default:
Q_UNREACHABLE();
}
auto parameters = metaMethod.parameters();
qsizetype nParam = parameters.size();
for (int i = 0; i < nParam; ++i) {
MethodParameter param;
param.name = parameters[i].name();
param.typeName = parameters[i].typeName();
methodInfo.parameters.append(param);
}
methodInfo.name = metaMethod.methodName();
methodInfo.typeName = metaMethod.returnTypeName();
int revision = metaMethod.revision();
methodInfo.isConstructor = metaMethod.isConstructor();
if (methodInfo.name.isEmpty()) {
addError(readerParseErrors().error(tr("Method or signal is missing a name.")).handle());
return;
}
objs[revision].addMethod(methodInfo, AddOption::KeepExisting);
}
EnumDecl QmltypesReader::enumFromMetaEnum(const QQmlJSMetaEnum &metaEnum)
{
EnumDecl res;
res.setName(metaEnum.name());
res.setAlias(metaEnum.alias());
res.setIsFlag(metaEnum.isFlag());
QList<EnumItem> values;
int lastValue = -1;
for (const auto &k : metaEnum.keys()) {
if (metaEnum.hasValues())
lastValue = metaEnum.value(k);
else
++lastValue;
values.append(EnumItem(k, lastValue));
}
res.setValues(values);
return res;
}
void QmltypesReader::insertComponent(const QQmlJSScope::ConstPtr &jsScope,
const QList<QQmlJSScope::Export> &exportsList)
{
QmltypesComponent comp;
comp.setSemanticScope(jsScope);
QMap<int, QmlObject> objects;
{
bool hasExports = false;
for (const QQmlJSScope::Export &jsE : exportsList) {
int metaRev = jsE.version().toEncodedVersion<int>();
hasExports = true;
QmlObject object;
object.setSemanticScope(jsScope);
objects.insert(metaRev, object);
}
if (!hasExports) {
QmlObject object;
object.setSemanticScope(jsScope);
objects.insert(0, object);
}
}
bool incrementedPath = false;
QString prototype;
QString defaultPropertyName;
{
QHash<QString, QQmlJSMetaProperty> els = jsScope->ownProperties();
auto it = els.cbegin();
auto end = els.cend();
while (it != end) {
insertProperty(jsScope, it.value(), objects);
++it;
}
}
{
QMultiHash<QString, QQmlJSMetaMethod> els = jsScope->ownMethods();
auto it = els.cbegin();
auto end = els.cend();
while (it != end) {
insertSignalOrMethod(it.value(), objects);
++it;
}
}
{
QHash<QString, QQmlJSMetaEnum> els = jsScope->ownEnumerations();
auto it = els.cbegin();
auto end = els.cend();
while (it != end) {
comp.addEnumeration(enumFromMetaEnum(it.value()));
++it;
}
}
comp.setFileName(jsScope->filePath());
comp.setName(jsScope->internalName());
m_currentPath = m_currentPath.withKey(comp.name())
.withIndex(qmltypesFilePtr()->components().values(comp.name()).size());
incrementedPath = true;
prototype = jsScope->baseTypeName();
defaultPropertyName = jsScope->ownDefaultPropertyName();
comp.setInterfaceNames(jsScope->interfaceNames());
QString typeName = jsScope->ownAttachedTypeName();
comp.setAttachedTypeName(typeName);
if (!typeName.isEmpty())
comp.setAttachedTypePath(Paths::lookupCppTypePath(typeName));
comp.setIsSingleton(jsScope->isSingleton());
comp.setIsCreatable(jsScope->isCreatable());
comp.setIsComposite(jsScope->isComposite());
comp.setHasCustomParser(jsScope->hasCustomParser());
comp.setElementTypeName(jsScope->elementTypeName());
comp.setAccessSemantics(jsScope->accessSemantics());
comp.setExtensionTypeName(jsScope->extensionTypeName());
comp.setExtensionIsJavaScript(jsScope->extensionIsJavaScript());
comp.setExtensionIsNamespace(jsScope->extensionIsNamespace());
Path exportSourcePath = qmltypesFilePtr()->canonicalPath();
QMap<int, Path> revToPath;
auto it = objects.end();
auto begin = objects.begin();
int objectIndex = 0;
QList<int> metaRevs;
Path compPath = qmltypesFilePtr()
->canonicalPath()
.withField(Fields::components)
.withKey(comp.name())
.withIndex(qmltypesFilePtr()->components().values(comp.name()).size());
// emit & map objs
while (it != begin) {
--it;
if (it.key() < 0) {
addError(readerParseErrors().error(
tr("negative meta revision %1 not supported").arg(it.key())));
}
revToPath.insert(it.key(), compPath.withField(Fields::objects).withIndex(objectIndex));
Path nextObjectPath = compPath.withField(Fields::objects).withIndex(++objectIndex);
if (it == begin) {
if (!prototype.isEmpty())
it->addPrototypePath(Paths::lookupCppTypePath(prototype));
it->setName(prototype);
} else {
it->addPrototypePath(nextObjectPath);
it->setName(comp.name() + QLatin1String("-") + QString::number(it.key()));
}
comp.addObject(*it);
metaRevs.append(it.key());
}
comp.setMetaRevisions(metaRevs);
// exports:
for (const QQmlJSScope::Export &jsE : exportsList) {
auto v = jsE.version();
int metaRev = v.toEncodedVersion<int>();
Export e;
e.uri = jsE.package();
e.typeName = jsE.type();
e.isSingleton = jsScope->isSingleton();
e.version = Version((v.hasMajorVersion() ? v.majorVersion() : Version::Latest),
(v.hasMinorVersion() ? v.minorVersion() : Version::Latest));
e.typePath = revToPath.value(metaRev);
if (!e.typePath) {
qCWarning(domLog) << "could not find version" << metaRev << "in" << revToPath.keys();
}
e.exportSourcePath = exportSourcePath;
comp.addExport(e);
}
if (comp.name().isEmpty()) {
addError(readerParseErrors()
.error(tr("Component definition is missing a name binding."))
.handle());
return;
}
qmltypesFilePtr()->addComponent(comp, AddOption::KeepExisting);
if (incrementedPath)
m_currentPath = m_currentPath.dropTail().dropTail();
}
bool QmltypesReader::parse()
{
QQmlJSTypeDescriptionReader reader(qmltypesFilePtr()->canonicalFilePath(),
qmltypesFilePtr()->code());
QStringList dependencies;
QList<QQmlJSExportedScope> objects;
const bool isValid = reader(&objects, &dependencies);
for (const auto &obj : std::as_const(objects))
insertComponent(obj.scope, obj.exports);
qmltypesFilePtr()->setIsValid(isValid);
return isValid;
}
void QmltypesReader::addError(ErrorMessage &&message)
{
if (message.file.isEmpty())
message.file = qmltypesFilePtr()->canonicalFilePath();
if (!message.path)
message.path = m_currentPath;
qmltypesFilePtr()->addErrorLocal(message.handle());
}
} // end namespace Dom
} // end namespace QQmlJS
QT_END_NAMESPACE