diff options
| author | Luca Di Sera <luca.disera@qt.io> | 2025-03-21 14:59:29 +0100 |
|---|---|---|
| committer | Luca Di Sera <luca.disera@qt.io> | 2025-04-09 13:36:21 +0200 |
| commit | f6f20242e4ac2fba54ad8d8dff0d9540290b441a (patch) | |
| tree | 2834cbc024d15a6049cead218b60e017ccc37543 /src/qml/compiler/qv4compiler.cpp | |
| parent | 9ac0e70cdbf99e06761b6edb28937fded8791bdb (diff) | |
Avoid incorrect access to arguments in signal bindings to arrow functions
Signal handler in QML can be bound to a Javascript expression.
For example:
```
onHello: console.log(10)
```
Where "onHello" is a Signal Handler.
When this is the case a certain amount of code will be generated
for the Signal Handler, performing some setup routines and executing the
provided expression, generally in a way that is somewhat equivalent to
the same expression being executed as the body of a function call.
It is possible to bind a Signal Handler to a anonymous function, say:
```
onHello: function () { ... }
```
Or:
```
onHello: () => { ... }
```
When this is the case, if the usual process was followed, executing the
Signal Handler would simply produce an anonymous function, but would
never actually call it.
Instead, when such a literal function expression is bound to a Signal
Handler, it is treated specially.
In particular, while the code generation will generally behave similarly,
on execution of the Signal Handler, the expression will be directly
called while the "wrapping" function that was generated around the
expression is ignored.
While this works correctly in many cases, it can misbehave on certain
occasions.
For example, an arrow function doesn't generally set up its own
context for a call, instead borrowing from the outer context at the time
of creation.
When making such a call to an arrow function, in a QML context, it is
then possible to execute code in an environment that wasn't properly set
up for it.
In particular, if the body of the arrow function introduces a
`LoadLocal` instruction, which assumes, when interpreted, an available
`CallContext`, it is possible to try and access memory that was never
correctly set up.
It is, for example, possible to do so by usage of `arguments`, a special
reference that allows access to a pack of arguments in all non-arrow
functions, which will produce a `LoadLocal` instruction when accessed,
for example:
```
onHello: () => { console.log(arguments) }
```
Internally, when generating code for a JavaScript program or expression,
an analysis is performed to understand whether the special "arguments"
reference is being used. When that is the case and the context in which
the reference is used is under a context where the reference can exist, a
local variable for "arguments" is injected and a "LoadLocal" instruction
is later generated to access it.
The analysis considers a binding scope as being able to provide the
"arguments" reference, something that is generally true due to the
"wrapping" that is performed when generating an expression for the
binding, and which needs to be guaranteed in certain cases.
While this breaks down in the face of directly calling the "inner"
function in a Signal Handler binding, such that the analysis might
itself not be thorough enough in those cases, at the time the analysis
is performed we cannot currently know whether the binding we are dealing
with is that of a Signal Handler.
Furthermore, we don't always bypass the "wrapping" function in a Signal
Handler, as there are other cases where this can create issues, for
example the usage of "this" in an arrow function.
When this is the case, instead of directly calling the "inner" function,
the normal "wrapping" function is called to perform setup routines and
obtain the function itself by executing the bound expression,
subsequently calling the function obtained in this way.
To avoid the issue with the usage of "arguments", we re-use the same
methodology, ensuring that when the special "arguments" object is
referenced we never bypass the setup provided by the binding expression.
This ensures that the arrow function will be created in a context where
`arguments` is present and where the necessary setup for the call is
performed.
The special object will be always be empty in that context, which aligns
the behavior to that of non-signal bindings.
To do so, an additional case was added to the code in `writeFunction`
that sets up a binding expression to later skip to its inner function.
A few test cases were added to inspect usages of the "arguments" special
reference under binding contexts.
Fixes: QTBUG-134215
Change-Id: Ib7fdfee91709358f2ee465b1926809ca4617d6f6
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'src/qml/compiler/qv4compiler.cpp')
| -rw-r--r-- | src/qml/compiler/qv4compiler.cpp | 1 |
1 files changed, 1 insertions, 0 deletions
diff --git a/src/qml/compiler/qv4compiler.cpp b/src/qml/compiler/qv4compiler.cpp index ad1390a650..f4b416d209 100644 --- a/src/qml/compiler/qv4compiler.cpp +++ b/src/qml/compiler/qv4compiler.cpp @@ -441,6 +441,7 @@ void QV4::Compiler::JSUnitGenerator::writeFunction(char *f, QV4::Compiler::Conte function->flags |= CompiledData::Function::IsClosureWrapper; if (!irFunction->returnsClosure + || (irFunction->usesArgumentsObject == Context::ArgumentsObjectUsed) || irFunction->innerFunctionAccessesThis || irFunction->innerFunctionAccessesNewTarget) { // If the inner function does things with this and new.target we need to do some work in |
