-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Example:
void main() {
List<void Function()> functions = [];
{
var sub;
functions.add(() {
print("1: $sub"); // 1: 2
sub = 1;
});
}
{
var sub;
functions.add(() {
print("2: $sub"); // 2: 1
});
sub = 2;
}
for (var function in functions) function();
}The variable sub inside the closure in the first block actually refers to the sub variable of the second block.
This can be seen from the program printing 1: 2 and 2: 1 where it should print 1: null and 2: 2.
Marking as soundness-issue too, since the variables don't have to have the same type, so the sub inside the first closure will have the static type of the first variable, and the runtime value of the second, which can be completely unrelated.
To trigger this the variables need to have the same name (rename one to sub1 and the problem goes away),
they have to be in the same function body (convert one block to () { ... }(); and the problem goes away),
both variables have to be captured in a closure, and there has to be at least one assignment to both variables (I guess otherwise that variable is eliminated and the value inlined).
Problem goes away with optimization level -O2 or above.
The generated code has a main starting with:
main() {
var t1, _i, _box_0 = {},
functions = A._setArrayType([], type$.JSArray_of_void_Function);
_box_0.sub = null;
B.JSArray_methods.add$1(functions, new A.main_closure(_box_0));
_box_0.sub = null;
B.JSArray_methods.add$1(functions, new A.main_closure0(_box_0));
_box_0.sub = 2;which shows that it does use the same box object for both closures, and the same variable name.
(That also suggests that it would use the same box for differently named variables too, for different closures, keeping each variable alive longer than it needs.)
At -O2 or above, the individiual variables are renamed, so one is a and the other b. The main code (if I guess correctly) starts with:
c6(){var t,s,r={},q=A.ao([],u.u)
r.a=null
B.a.j(q,new A.af(r))
r.b=null
B.a.j(q,new A.ag(r))
r.b=2That solves the problem (and still uses the same box object, r, for both closures, even if they share no captured variables).
I hit this issue when converting async_minitest.dart-using tests to simple blocks for each test. Some test runners use -O<2, and the tests failed only for dart2js, so the problem gets hit.
I'll add () {...}(); around the blocks for now, with a TODO to remove them when this issue is done.