Revision control
Copy as Markdown
// Copyright (C) 2022 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 "qqmljsliteralbindingcheck_p.h"
#include "qqmlsa.h"
#include <private/qqmljsimportvisitor_p.h>
#include <private/qqmljstyperesolver_p.h>
#include <private/qqmljsmetatypes_p.h>
#include <private/qqmlsa_p.h>
#include <private/qqmlsasourcelocation_p.h>
#include <private/qqmlstringconverters_p.h>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
// This makes no sense, but we want to warn about things QQmlPropertyResolver complains about.
static bool canConvertForLiteralBinding(QQmlJSTypeResolver *resolver,
const QQmlSA::Element &fromElement,
const QQmlSA::Element &toElement)
{
Q_ASSERT(resolver);
auto from = QQmlJSScope::scope(fromElement);
auto to = QQmlJSScope::scope(toElement);
if (from == to)
return true;
if (!resolver->canConvertFromTo(from, to))
return false;
const bool fromIsString = from == resolver->stringType();
if (to == resolver->stringType()
|| to == resolver->stringListType()
|| to == resolver->byteArrayType()
|| to == resolver->urlType()) {
return fromIsString;
}
if (resolver->isNumeric(to))
return resolver->isNumeric(from);
if (to == resolver->boolType())
return from == resolver->boolType();
return true;
}
QQmlJSLiteralBindingCheck::QQmlJSLiteralBindingCheck(QQmlSA::PassManager *passManager)
: LiteralBindingCheckBase(passManager),
m_resolver(QQmlSA::PassManagerPrivate::resolver(*passManager))
{
}
QQmlJSStructuredTypeError QQmlJSLiteralBindingCheck::check(const QString &typeName,
const QString &value) const
{
return QQmlJSValueTypeFromStringCheck::hasError(typeName, value);
}
static QString literalPrettyTypeName(QQmlSA::BindingType type)
{
switch (type) {
case QQmlSA::BindingType::BoolLiteral:
return u"bool"_s;
case QQmlSA::BindingType::NumberLiteral:
return u"double"_s;
case QQmlSA::BindingType::StringLiteral:
return u"string"_s;
case QQmlSA::BindingType::RegExpLiteral:
return u"regexp"_s;
case QQmlSA::BindingType::Null:
return u"null"_s;
default:
return QString();
}
Q_UNREACHABLE_RETURN(QString());
}
QQmlSA::Property LiteralBindingCheckBase::getProperty(const QString &propertyName,
const QQmlSA::Binding &binding,
const QQmlSA::Element &bindingScope) const
{
if (!QQmlSA::Binding::isLiteralBinding(binding.bindingType()))
return {};
const QString unqualifiedPropertyName = [&propertyName]() -> QString {
if (auto idx = propertyName.lastIndexOf(u'.'); idx != -1 && idx != propertyName.size() - 1)
return propertyName.sliced(idx + 1);
return propertyName;
}();
return bindingScope.property(unqualifiedPropertyName);
}
void LiteralBindingCheckBase::warnOnCheckedBinding(
const QQmlSA::Binding &binding, const QQmlSA::Element &propertyType)
{
auto construction = check(propertyType.internalId(), binding.stringValue());
if (!construction.isValid())
return;
const QString warningMessage =
u"Construction from string is deprecated. Use structured value type "
u"construction instead for type \"%1\""_s.arg(propertyType.internalId());
if (construction.code.isEmpty()) {
emitWarning(warningMessage, qmlIncompatibleType, binding.sourceLocation());
return;
}
const QQmlSA::FixSuggestion suggestion(
u"Replace string by structured value construction"_s,
binding.sourceLocation(),
{ binding.bindingScope().filePath(), binding.sourceLocation(), construction.code });
emitWarning(warningMessage, qmlIncompatibleType, binding.sourceLocation(), suggestion);
}
void QQmlJSLiteralBindingCheck::onBinding(const QQmlSA::Element &element,
const QString &propertyName,
const QQmlSA::Binding &binding,
const QQmlSA::Element &bindingScope,
const QQmlSA::Element &value)
{
Q_UNUSED(value);
const auto property = getProperty(propertyName, binding, bindingScope);
if (!property.isValid())
return;
// If the property is defined in the same scope where it is set,
// we are in fact allowed to set it, even if it's not writable.
if (property.isReadonly() && !element.hasOwnProperty(propertyName)) {
emitWarning(u"Cannot assign to read-only property %1"_s.arg(propertyName),
qmlReadOnlyProperty, binding.sourceLocation());
return;
}
if (const auto propertyType = property.type())
warnOnCheckedBinding(binding, propertyType);
if (!canConvertForLiteralBinding(m_resolver, resolveLiteralType(binding), property.type())) {
emitWarning(u"Cannot assign literal of type %1 to %2"_s.arg(
literalPrettyTypeName(binding.bindingType()),
QQmlJSScope::prettyName(property.typeName())),
qmlIncompatibleType, binding.sourceLocation());
return;
}
}
QT_END_NAMESPACE