Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmljsfunctioninitializer.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant
4
6
7#include <private/qqmljsmemorypool_p.h>
8
9#include <QtCore/qloggingcategory.h>
10#include <QtCore/qfileinfo.h>
11
12#include <QtQml/private/qqmlsignalnames_p.h>
13
15
16using namespace Qt::StringLiterals;
17
18/*!
19 * \internal
20 * \class QQmlJSFunctionInitializer
21 *
22 * QQmlJSFunctionInitializer analyzes the IR to produce an initial
23 * QQmlJSCompilePass::Function for further analysis. It only looks for the
24 * signature and the QML scope and doesn't visit the byte code.
25 */
26
27static QString bindingTypeDescription(QmlIR::Binding::Type type)
28{
29 switch (type) {
30 case QmlIR::Binding::Type_Invalid:
31 return u"invalid"_s;
32 case QmlIR::Binding::Type_Boolean:
33 return u"a boolean"_s;
34 case QmlIR::Binding::Type_Number:
35 return u"a number"_s;
36 case QmlIR::Binding::Type_String:
37 return u"a string"_s;
38 case QmlIR::Binding::Type_Null:
39 return u"null"_s;
40 case QmlIR::Binding::Type_Translation:
41 return u"a translation"_s;
42 case QmlIR::Binding::Type_TranslationById:
43 return u"a translation by id"_s;
44 case QmlIR::Binding::Type_Script:
45 return u"a script"_s;
46 case QmlIR::Binding::Type_Object:
47 return u"an object"_s;
48 case QmlIR::Binding::Type_AttachedProperty:
49 return u"an attached property"_s;
50 case QmlIR::Binding::Type_GroupProperty:
51 return u"a grouped property"_s;
52 }
53
54 return u"nothing"_s;
55}
56
57void QQmlJSFunctionInitializer::populateSignature(
58 const QV4::Compiler::Context *context, QQmlJS::AST::FunctionExpression *ast,
59 QQmlJSCompilePass::Function *function)
60{
61 const auto signatureError = [&](const QString &message) {
62 m_logger->logCompileError(message, ast->firstSourceLocation());
63 function->isFullyTyped = false;
64 };
65
66 if (!m_typeResolver->canCallJSFunctions()) {
67 signatureError(u"Ignoring type annotations as requested "
68 "by pragma FunctionSignatureBehavior"_s);
69 return;
70 }
71
72 QQmlJS::AST::BoundNames arguments;
73 if (ast->formals)
74 arguments = ast->formals->formals();
75
76 // If the function has no arguments and no return type annotation we assume it's untyped.
77 // You can annotate it to return void to make it typed.
78 // Otherwise we first assume it's typed and reset the flag if we detect a problem.
79 function->isFullyTyped = !arguments.isEmpty() || ast->typeAnnotation;
80
81 if (function->argumentTypes.isEmpty()) {
82 bool alreadyWarnedAboutMissingAnnotations = false;
83 for (const QQmlJS::AST::BoundName &argument : std::as_const(arguments)) {
84 if (argument.typeAnnotation) {
85 if (const auto type = m_typeResolver->typeFromAST(argument.typeAnnotation->type)) {
86 function->argumentTypes.append(m_typeResolver->namedType(type));
87 } else {
88 function->argumentTypes.append(
89 m_typeResolver->namedType(m_typeResolver->varType()));
90 signatureError(u"Cannot resolve the argument type %1."_s
91 .arg(argument.typeAnnotation->type->toString()));
92 }
93 } else {
94 if (!alreadyWarnedAboutMissingAnnotations) {
95 alreadyWarnedAboutMissingAnnotations = true;
96 signatureError(u"Functions without type annotations won't be compiled"_s);
97 }
98 function->argumentTypes.append(
99 m_typeResolver->namedType(m_typeResolver->varType()));
100 }
101 }
102 } else {
103 for (qsizetype i = 0, end = arguments.size(); i != end; ++i) {
104 const QQmlJS::AST::BoundName &argument = arguments[i];
105 if (argument.typeAnnotation) {
106 if (const auto type = m_typeResolver->typeFromAST(argument.typeAnnotation->type)) {
107 if (!function->argumentTypes[i].contains(type)) {
108 signatureError(u"Type annotation %1 on signal handler "
109 "contradicts signal argument type %2"_s
110 .arg(argument.typeAnnotation->type->toString(),
111 function->argumentTypes[i].descriptiveName()));
112 }
113 }
114 }
115 }
116 }
117
118 if (!function->returnType.isValid()) {
119 if (ast->typeAnnotation) {
120 function->returnType = m_typeResolver->namedType(
121 m_typeResolver->typeFromAST(ast->typeAnnotation->type));
122 if (!function->returnType.isValid())
123 signatureError(u"Cannot resolve return type %1"_s.arg(
124 QmlIR::IRBuilder::asString(ast->typeAnnotation->type->typeId)));
125 }
126 }
127
128 for (int i = QQmlJSCompilePass::FirstArgument + function->argumentTypes.size();
129 i < context->registerCountInFunction; ++i) {
130 function->registerTypes.append(m_typeResolver->namedType(m_typeResolver->voidType()));
131 }
132
133 function->addressableScopes = m_typeResolver->objectsById();
134 function->code = context->code;
135 function->sourceLocations = context->sourceLocationTable.get();
136}
137
138static void diagnose(
139 const QString &message, const QQmlJS::SourceLocation &location, QQmlJSLogger *logger)
140{
141 logger->logCompileError(message, location);
142}
143
144QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run(
145 const QV4::Compiler::Context *context, const QString &propertyName,
146 QQmlJS::AST::Node *astNode, const QmlIR::Binding &irBinding)
147{
148 QQmlJS::SourceLocation bindingLocation;
149 bindingLocation.startLine = irBinding.location.line();
150 bindingLocation.startColumn = irBinding.location.column();
151
152 QQmlJSCompilePass::Function function;
153 function.qmlScope = m_typeResolver->registerContentPool()->createType(
154 m_scopeType, QQmlJSRegisterContent::InvalidLookupIndex,
155 QQmlJSRegisterContent::ScopeObject);
156
157 // If we find a problem before we can determine whether it's a signal handler,
158 // that's bad and warrants a warning, even if it then turns out to be a signal handler.
159 const auto setIsSignalHandler = [&]() {
160 function.isSignalHandler = true;
161
162 // If the function is a signal handler and just returns a closure, it's harmless.
163 // Otherwise it's a warning.
164 m_logger->setCompileErrorSeverity(context->returnsClosure
165 ? QQmlJS::WarningSeverity::Info
166 : QQmlJS::WarningSeverity::Warning);
167 };
168
169 if (irBinding.type() != QmlIR::Binding::Type_Script) {
170 diagnose(u"Binding is not a script binding, but %1."_s.arg(
171 bindingTypeDescription(QmlIR::Binding::Type(quint32(irBinding.type())))),
172 bindingLocation, m_logger);
173 }
174
175 function.isProperty = m_objectType->hasProperty(propertyName);
176 if (function.isProperty) {
177 const auto property = m_objectType->property(propertyName);
178 if (const QQmlJSScope::ConstPtr propertyType = property.type()) {
179 function.returnType = m_typeResolver->namedType(propertyType->isListProperty()
180 ? m_typeResolver->qObjectListType()
181 : QQmlJSScope::ConstPtr(property.type()));
182 } else {
183 diagnose(u"Cannot resolve property type %1 for binding on %2."_s
184 .arg(property.typeName(), propertyName),
185 bindingLocation, m_logger);
186 }
187
188 if (!property.bindable().isEmpty() && !property.isPrivate())
189 function.isQPropertyBinding = true;
190 } else if (QQmlSignalNames::isHandlerName(propertyName)) {
191 if (auto actualPropertyName =
192 QQmlSignalNames::changedHandlerNameToPropertyName(propertyName);
193 actualPropertyName && m_objectType->hasProperty(*actualPropertyName)) {
194 setIsSignalHandler();
195 } else {
196 auto signalName = QQmlSignalNames::handlerNameToSignalName(propertyName);
197 const auto methods = m_objectType->methods(*signalName);
198 for (const auto &method : methods) {
199 if (method.isCloned())
200 continue;
201 if (method.methodType() == QQmlJSMetaMethodType::Signal) {
202 setIsSignalHandler();
203 const auto arguments = method.parameters();
204 for (qsizetype i = 0, end = arguments.size(); i < end; ++i) {
205 const auto &type = arguments[i].type();
206 if (type.isNull()) {
207 diagnose(u"Cannot resolve the argument type %1."_s.arg(
208 arguments[i].typeName()),
209 bindingLocation, m_logger);
210 function.argumentTypes.append(
211 m_typeResolver->namedType(m_typeResolver->varType()));
212 } else {
213 function.argumentTypes.append(m_typeResolver->namedType(type));
214 }
215 }
216 break;
217 }
218 }
219 if (!function.isSignalHandler) {
220 diagnose(u"Could not find signal \"%1\"."_s.arg(*signalName),
221 bindingLocation, m_logger);
222 }
223 }
224 } else {
225 QString message = u"Could not find property \"%1\"."_s.arg(propertyName);
226 if (m_objectType->isNameDeferred(propertyName)) {
227 // If the property doesn't exist but the name is deferred, then
228 // it's deferred via the presence of immediate names. Those are
229 // most commonly used to enable generalized grouped properties.
230 message += u" You may want use ID-based grouped properties here.";
231 }
232
233 diagnose(message, bindingLocation, m_logger);
234 }
235
236 QQmlJS::MemoryPool pool;
237 auto ast = astNode->asFunctionDefinition();
238 if (!ast) {
239 QQmlJS::AST::Statement *stmt = astNode->statementCast();
240 if (!stmt) {
241 Q_ASSERT(astNode->expressionCast());
242 QQmlJS::AST::ExpressionNode *expr = astNode->expressionCast();
243 stmt = new (&pool) QQmlJS::AST::ExpressionStatement(expr);
244 }
245 auto body = new (&pool) QQmlJS::AST::StatementList(stmt);
246 body = body->finish();
247
248 QString name = u"binding for "_s; // ####
249 ast = new (&pool) QQmlJS::AST::FunctionDeclaration(
250 pool.newString(std::move(name)), /*formals*/ nullptr, body);
251 ast->lbraceToken = astNode->firstSourceLocation();
252 ast->functionToken = ast->lbraceToken;
253 ast->rbraceToken = astNode->lastSourceLocation();
254 }
255
256 populateSignature(context, ast, &function);
257 return function;
258}
259
260QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run(
261 const QV4::Compiler::Context *context, const QString &functionName,
262 QQmlJS::AST::Node *astNode)
263{
264 Q_UNUSED(functionName);
265
266 QQmlJSCompilePass::Function function;
267 function.qmlScope = m_typeResolver->registerContentPool()->createType(
268 m_scopeType, QQmlJSRegisterContent::InvalidLookupIndex,
269 QQmlJSRegisterContent::ScopeObject);
270
271 auto ast = astNode->asFunctionDefinition();
272 Q_ASSERT(ast);
273
274 populateSignature(context, ast, &function);
275 return function;
276}
277
278QT_END_NAMESPACE
Combined button and popup list for selecting options.
static QString bindingTypeDescription(QmlIR::Binding::Type type)
static void diagnose(const QString &message, const QQmlJS::SourceLocation &location, QQmlJSLogger *logger)