Skip to content

Dart2JS conflates variables with same name in different blocks #59843

@lrhn

Description

@lrhn

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=2

That 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.

Metadata

Metadata

Assignees

Labels

area-web-jsIssues related to JavaScript support for Dart Web, including DDC, dart2js, and JS interop.dart2js-soundnesstype-bugIncorrect behavior (everything from a crash to more subtle misbehavior)web-dart2js

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions