Revision control

Copy as Markdown

// Copyright (C) 2019 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 "qqmltyperegistrarutils_p.h"
#include "qqmltypesclassdescription_p.h"
#include "qanystringviewutils_p.h"
#include "qmetatypesjsonprocessor_p.h"
#include "qqmltyperegistrarconstants_p.h"
#include <QtCore/qcborarray.h>
#include <QtCore/qcbormap.h>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
using namespace Constants;
using namespace Constants::MetatypesDotJson;
using namespace Constants::MetatypesDotJson::Qml;
using namespace QAnyStringViewUtils;
template<typename Container>
static void collectExtraVersions(const Container &items, QList<QTypeRevision> &extraVersions)
{
for (const auto &obj : items) {
if (obj.revision.isValid() && !extraVersions.contains(obj.revision))
extraVersions.append(obj.revision);
}
}
struct Compare {
bool operator()(const QAnyStringView &typeName, const MetaType &type) const
{
return typeName < type.qualifiedClassName();
}
bool operator()(const MetaType &type, const QAnyStringView &typeName) const
{
return type.qualifiedClassName() < typeName;
}
};
FoundType::FoundType(const MetaType &single, FoundType::Origin origin)
{
if (single.inputFile().isEmpty()) {
javaScript = single;
javaScriptOrigin = origin;
} else {
native = single;
nativeOrigin = origin;
}
}
MetaType FoundType::select(const MetaType &category, QAnyStringView relation) const
{
if (category.inputFile().isEmpty()) {
if (javaScript.isEmpty()) {
warning(category)
<< relation << "type of" << category.qualifiedClassName()
<< "is not a JavaScript type";
}
return javaScript;
}
if (native.isEmpty()) {
warning(category)
<< relation << "of" << category.qualifiedClassName()
<< "is not a native type";
}
return native;
}
FoundType QmlTypesClassDescription::findType(
const QList<MetaType> &types, const QList<MetaType> &foreign,
const QAnyStringView &name, const QList<QAnyStringView> &namespaces)
{
const auto tryFindType = [&](QAnyStringView qualifiedName) -> FoundType {
FoundType result;
for (const QList<MetaType> &t : {types, foreign}) {
const auto [first, last] = std::equal_range(
t.begin(), t.end(), qualifiedName, Compare());
for (auto it = first; it != last; ++it) {
Q_ASSERT(it->qualifiedClassName() == qualifiedName);
if (it->inputFile().isEmpty()) {
if (result.javaScript.isEmpty()) {
result.javaScript = *it;
result.javaScriptOrigin = (&t == &types)
? FoundType::OwnTypes
: FoundType::ForeignTypes;
} else {
warning(result.javaScript)
<< "Multiple JavaScript types called" << qualifiedName << "found!";
}
} else if (result.native.isEmpty()) {
result.native = *it;
result.nativeOrigin = (&t == &types)
? FoundType::OwnTypes
: FoundType::ForeignTypes;
} else {
warning(result.native)
<< "Multiple C++ types called" << qualifiedName << "found!\n"
<< "(other occurrence in :"
<< it->inputFile() << ":" << it->lineNumber() << ")\n"
<< "This violates the One Definition Rule!";
}
}
}
return result;
};
if (startsWith(name, QLatin1String("::")))
return tryFindType(name.mid(2));
QString qualified;
for (int i = 0, end = namespaces.length(); i != end; ++i) {
for (int j = 0; j < end - i; ++j) {
namespaces[j].visit([&](auto data) { qualified.append(data); });
qualified.append(QLatin1String("::"));
}
name.visit([&](auto data) { qualified.append(data); });
if (const FoundType found = tryFindType(qualified))
return found;
qualified.truncate(0);
}
return tryFindType(name);
}
void QmlTypesClassDescription::collectSuperClasses(
const MetaType &classDef, const QList<MetaType> &types,
const QList<MetaType> &foreign, CollectMode mode, QTypeRevision defaultRevision)
{
const QList<QAnyStringView> namespaces = MetaTypesJsonProcessor::namespaces(classDef);
QAnyStringView superClassCandidate;
for (const BaseType &superObject : std::as_const(classDef.superClasses())) {
if (superObject.access == Access::Public) {
const QAnyStringView superName = superObject.name;
const CollectMode superMode = (mode == TopLevel) ? SuperClass : RelatedType;
if (const FoundType found = findType(types, foreign, superName, namespaces)) {
const MetaType other = found.select(classDef, "Base");
collect(other, types, foreign, superMode, defaultRevision);
if (mode == TopLevel && superClass.isEmpty())
superClass = other.qualifiedClassName();
} else {
// If we cannot resolve anything but find a correctly formed superObject,
// we can at least populate the name. Further tooling might locate the type
// in a different module.
superClassCandidate = superName;
}
}
}
if (mode == TopLevel && superClass.isEmpty())
superClass = superClassCandidate;
}
void QmlTypesClassDescription::collectInterfaces(const MetaType &classDef)
{
for (const Interface &iface : classDef.ifaces())
implementsInterfaces << interfaceName(iface);
}
void QmlTypesClassDescription::handleRegisterEnumClassesUnscoped(
const MetaType &classDef, QAnyStringView value)
{
if (value == S_FALSE)
enforcesScopedEnums = true;
else if (value == S_TRUE)
warning(classDef) << "Setting RegisterEnumClassesUnscoped to true has no effect.";
else
warning(classDef) << "Unrecognized value for RegisterEnumClassesUnscoped:" << value;
}
void QmlTypesClassDescription::collectLocalAnonymous(
const MetaType &classDef, const QList<MetaType> &types,
const QList<MetaType> &foreign, QTypeRevision defaultRevision)
{
file = classDef.inputFile();
lineNumber = classDef.lineNumber();
resolvedClass = classDef;
className = classDef.qualifiedClassName();
switch (classDef.kind()) {
case MetaType::Kind::Object:
accessSemantics = DotQmltypes::S_REFERENCE;
break;
case MetaType::Kind::Gadget:
accessSemantics = DotQmltypes::S_VALUE;
break;
case MetaType::Kind::Namespace:
case MetaType::Kind::Unknown:
accessSemantics = DotQmltypes::S_NONE;
break;
}
for (const ClassInfo &obj : classDef.classInfos()) {
if (obj.name == S_DEFAULT_PROPERTY)
defaultProp = obj.value;
else if (obj.name == S_PARENT_PROPERTY)
parentProp = obj.value;
else if (obj.name == S_REGISTER_ENUM_CLASSES_UNSCOPED)
handleRegisterEnumClassesUnscoped(classDef, obj.value);
}
collectInterfaces(classDef);
collectSuperClasses(classDef, types, foreign, TopLevel, defaultRevision);
}
void QmlTypesClassDescription::collect(
const MetaType &classDef, const QList<MetaType> &types,
const QList<MetaType> &foreign, CollectMode mode, QTypeRevision defaultRevision)
{
if (file.isEmpty()) {
file = classDef.inputFile();
lineNumber = classDef.lineNumber();
}
const QAnyStringView classDefName = classDef.className();
const QList<QAnyStringView> namespaces = MetaTypesJsonProcessor::namespaces(classDef);
QAnyStringView foreignTypeName;
bool foreignIsNamespace = false;
bool isConstructible = false;
for (const ClassInfo &obj : classDef.classInfos()) {
const QAnyStringView name = obj.name;
const QAnyStringView value = obj.value;
if (name == S_DEFAULT_PROPERTY) {
if (mode != RelatedType && defaultProp.isEmpty())
defaultProp = value;
continue;
}
if (name == S_PARENT_PROPERTY) {
if (mode != RelatedType && parentProp.isEmpty())
parentProp = value;
continue;
}
if (name == S_REGISTER_ENUM_CLASSES_UNSCOPED) {
if (mode != RelatedType)
handleRegisterEnumClassesUnscoped(classDef, value);
continue;
}
if (name == S_ADDED_IN_VERSION) {
const QTypeRevision revision = handleInMinorVersion(
QTypeRevision::fromEncodedVersion(toInt(value)),
defaultRevision.majorVersion());
revisions.append(revision);
if (mode == TopLevel)
addedInRevision = revision;
continue;
}
if (name == S_EXTRA_VERSION) {
const QTypeRevision revision = handleInMinorVersion(
QTypeRevision::fromEncodedVersion(toInt(value)),
defaultRevision.majorVersion());
revisions.append(revision);
continue;
}
if (mode != TopLevel)
continue;
if (name == S_REMOVED_IN_VERSION) {
removedInRevision = handleInMinorVersion(
QTypeRevision::fromEncodedVersion(toInt(value)),
defaultRevision.majorVersion());
continue;
}
// These only apply to the original class
if (name == S_ELEMENT) {
if (value == S_AUTO)
elementNames.append(classDefName);
else if (value != S_ANONYMOUS)
elementNames.append(value);
} else if (name == S_CREATABLE) {
isCreatable = (value != S_FALSE);
} else if (name == S_CREATION_METHOD) {
isStructured = (value == S_STRUCTURED);
isConstructible = isStructured || (value == S_CONSTRUCT);
} else if (name == S_ATTACHED) {
if (const FoundType attached = collectRelated(
value, types, foreign, defaultRevision, namespaces)) {
attachedType = attached.select(classDef, "Attached").qualifiedClassName();
}
} else if (name == S_EXTENDED) {
if (const FoundType extension = collectRelated(
value, types, foreign, defaultRevision, namespaces)) {
javaScriptExtensionType = extension.javaScript.qualifiedClassName();
nativeExtensionType = extension.native.qualifiedClassName();
}
if (value == classDefName)
isSelfExtension = true;
} else if (name == S_EXTENSION_IS_JAVA_SCRIPT) {
if (value == S_TRUE)
extensionIsJavaScript = true;
} else if (name == S_EXTENSION_IS_NAMESPACE) {
if (value == S_TRUE)
extensionIsNamespace = true;
} else if (name == S_SEQUENCE) {
if (const FoundType element = collectRelated(
value, types, foreign, defaultRevision, namespaces)) {
sequenceValueType = element.select(classDef, "Sequence value").qualifiedClassName();
} else {
// TODO: get rid of this once we have JSON data for the builtins.
sequenceValueType = value;
}
} else if (name == S_SINGLETON) {
if (value == S_TRUE)
isSingleton = true;
} else if (name == S_FOREIGN) {
foreignTypeName = value;
} else if (name == S_FOREIGN_IS_NAMESPACE) {
foreignIsNamespace = (value == S_TRUE);
} else if (name == S_PRIMITIVE_ALIAS) {
primitiveAliases.append(value);
} else if (name == S_ROOT) {
isRootClass = (value == S_TRUE);
} else if (name == S_HAS_CUSTOM_PARSER) {
if (value == S_TRUE)
hasCustomParser = true;
} else if (name == S_DEFERRED_PROPERTY_NAMES) {
deferredNames = split(value, QLatin1StringView(","));
} else if (name == S_IMMEDIATE_PROPERTY_NAMES) {
immediateNames = split(value, QLatin1StringView(","));
}
}
if (addedInRevision.isValid() && !elementNames.isEmpty())
revisions.append(addedInRevision);
// If the local type is a namespace the result can only be a namespace,
// no matter what the foreign type is.
const bool isNamespace = foreignIsNamespace || classDef.kind() == MetaType::Kind::Namespace;
MetaType resolved = classDef;
if (!foreignTypeName.isEmpty()) {
// We can re-use a type with own QML.* macros as target of QML.Foreign
if (const FoundType found = findType(foreign, types, foreignTypeName, namespaces)) {
resolved = found.select(classDef, "Foreign");
// Default properties and enum classes are always local.
defaultProp = {};
enforcesScopedEnums = false;
// Foreign type can have a default property or an attached type,
// or RegisterEnumClassesUnscoped classinfo.
for (const ClassInfo &obj : resolved.classInfos()) {
const QAnyStringView foreignName = obj.name;
const QAnyStringView foreignValue = obj.value;
if (defaultProp.isEmpty() && foreignName == S_DEFAULT_PROPERTY) {
defaultProp = foreignValue;
} else if (parentProp.isEmpty() && foreignName == S_PARENT_PROPERTY) {
parentProp = foreignValue;
} else if (foreignName == S_REGISTER_ENUM_CLASSES_UNSCOPED) {
handleRegisterEnumClassesUnscoped(resolved, foreignValue);
} else if (foreignName == S_ATTACHED) {
if (const FoundType attached = collectRelated(
foreignValue, types, foreign, defaultRevision, namespaces)) {
attachedType = attached.select(resolved, "Attached").qualifiedClassName();
}
} else if (foreignName == S_EXTENDED) {
if (const FoundType extension = collectRelated(
foreignValue, types, foreign, defaultRevision, namespaces)) {
nativeExtensionType = extension.native.qualifiedClassName();
javaScriptExtensionType = extension.javaScript.qualifiedClassName();
}
} else if (foreignName == S_EXTENSION_IS_JAVA_SCRIPT) {
if (foreignValue == S_TRUE)
extensionIsJavaScript = true;
} else if (foreignName == S_EXTENSION_IS_NAMESPACE) {
if (foreignValue == S_TRUE)
extensionIsNamespace = true;
} else if (foreignName == S_SEQUENCE) {
if (const FoundType element = collectRelated(
foreignValue, types, foreign, defaultRevision, namespaces)) {
sequenceValueType
= element.select(resolved, "Sequence value").qualifiedClassName();
}
}
}
if (mode == TopLevel) {
resolvedFile = resolved.inputFile();
lineNumberInResolvedFile = resolved.lineNumber();
}
} else {
className = foreignTypeName;
resolved = MetaType();
}
}
if (!resolved.isEmpty()) {
if (mode == RelatedType || !elementNames.isEmpty()) {
collectExtraVersions(resolved.properties(), revisions);
collectExtraVersions(resolved.methods(), revisions);
collectExtraVersions(resolved.sigs(), revisions);
}
collectSuperClasses(resolved, types, foreign, mode, defaultRevision);
}
if (mode != TopLevel)
return;
if (!resolved.isEmpty())
collectInterfaces(resolved);
if (!addedInRevision.isValid()) {
addedInRevision = defaultRevision;
}
if (addedInRevision <= defaultRevision
&& (!removedInRevision.isValid() || defaultRevision < removedInRevision)) {
revisions.append(defaultRevision);
}
std::sort(revisions.begin(), revisions.end());
const auto end = std::unique(revisions.begin(), revisions.end());
revisions.erase(QList<QTypeRevision>::const_iterator(end), revisions.constEnd());
resolvedClass = resolved;
if (className.isEmpty() && !resolved.isEmpty())
className = resolved.qualifiedClassName();
if (!sequenceValueType.isEmpty()) {
isCreatable = false;
accessSemantics = DotQmltypes::S_SEQUENCE;
} else if (isNamespace) {
isCreatable = false;
accessSemantics = DotQmltypes::S_NONE;
} else if (resolved.kind() == MetaType::Kind::Object) {
accessSemantics = DotQmltypes::S_REFERENCE;
} else {
isCreatable = isConstructible;
if (resolved.isEmpty()) {
if (elementNames.isEmpty()) {
// If no resolved, we generally assume it's a value type defined by the
// foreign/extended trick.
accessSemantics = DotQmltypes::S_VALUE;
}
for (auto elementName = elementNames.begin(); elementName != elementNames.end();) {
if (elementName->isEmpty() || elementName->front().isLower()) {
// If no resolved, we generally assume it's a value type defined by the
// foreign/extended trick.
accessSemantics = DotQmltypes::S_VALUE;
++elementName;
} else {
// Objects and namespaces always have metaobjects and therefore classDefs.
// However, we may not be able to resolve the metaobject at compile time. See
// the "Invisible" test case. In that case, we must not assume anything about
// access semantics.
warning(classDef)
<< "Refusing to generate non-lowercase name"
<< *elementName << "for unknown foreign type";
elementName = elementNames.erase(elementName);
if (elementNames.isEmpty()) {
// Make it completely inaccessible.
// We cannot get enums from anonymous types after all.
accessSemantics = DotQmltypes::S_NONE;
}
}
}
} else if (resolved.kind() == MetaType::Kind::Gadget) {
accessSemantics = DotQmltypes::S_VALUE;
} else {
accessSemantics = DotQmltypes::S_NONE;
}
}
}
FoundType QmlTypesClassDescription::collectRelated(
QAnyStringView related, const QList<MetaType> &types, const QList<MetaType> &foreign,
QTypeRevision defaultRevision, const QList<QAnyStringView> &namespaces)
{
if (FoundType other = findType(types, foreign, related, namespaces)) {
if (!other.native.isEmpty())
collect(other.native, types, foreign, RelatedType, defaultRevision);
if (!other.javaScript.isEmpty())
collect(other.javaScript, types, foreign, RelatedType, defaultRevision);
return other;
}
return FoundType();
}
struct UsingCompare {
bool operator()(const UsingDeclaration &a, QAnyStringView b) const
{
return a.alias < b;
}
bool operator()(QAnyStringView a, const UsingDeclaration &b) const
{
return a < b.alias;
}
};
ResolvedTypeAlias::ResolvedTypeAlias(
QAnyStringView alias, const QList<UsingDeclaration> &usingDeclarations)
: type(alias)
{
handleVoid();
if (type.isEmpty())
return;
handleList();
if (!isList) {
handlePointer();
handleConst();
}
while (true) {
const auto usingDeclaration = std::equal_range(
usingDeclarations.begin(), usingDeclarations.end(), type, UsingCompare());
if (usingDeclaration.first == usingDeclaration.second)
break;
type = usingDeclaration.first->original;
handleVoid();
if (type.isEmpty())
return;
if (isPointer) {
handleConst();
continue;
}
if (!isList) {
handleList();
if (!isList) {
handlePointer();
handleConst();
}
}
}
}
void ResolvedTypeAlias::handleVoid()
{
if (!isPointer && type == "void")
type = "";
}
void ResolvedTypeAlias::handleList()
{
for (QLatin1StringView list : {"QQmlListProperty<"_L1, "QList<"_L1}) {
if (!startsWith(type, list) || type.back() != '>'_L1)
continue;
const int listSize = list.size();
const QAnyStringView elementType = trimmed(type.mid(listSize, type.size() - listSize - 1));
// QQmlListProperty internally constructs the pointer. Passing an explicit '*' will
// produce double pointers. QList is only for value types. We can't handle QLists
// of pointers (unless specially registered, but then they're not isList).
if (elementType.back() == '*'_L1)
continue;
isList = true;
type = elementType;
return;
}
}
void ResolvedTypeAlias::handlePointer()
{
if (type.back() == '*'_L1) {
isPointer = true;
type = type.chopped(1);
while (type.back().isSpace())
type.chop(1);
}
}
void ResolvedTypeAlias::handleConst()
{
if (startsWith(type, "const "_L1)) {
isConstant = true;
type = type.sliced(strlen("const "));
}
}
QT_END_NAMESPACE