sdk icon indicating copy to clipboard operation
sdk copied to clipboard

[dart2js] Bug in inferred type of static closure `call` method tear-off

Open rakudrama opened this issue 6 months ago • 2 comments

Consider

import 'dart:typed_data';

String foo() => "Lily was here";

main() {
  var f = foo;
  var x = f.call;
  (print)("Lily was here" == x());
  //(print)(Uint8List(0));
}

Running: dart compile js foo.dart --out=o1.js && v8 o1.js:

true

Uncomment the last line in main, and run again:

false
[]

It appears that adding a live @Native class causes the inferred type of the tear-off to be Never.

rakudrama avatar Jun 11 '25 20:06 rakudrama

The difference is in the computed type of the static method, and a bug thereafter:

resultTypeOfSelector([subclass=Closure], get:call)  --> any
resultTypeOfSelector([subtype=Function], get:call)  --> empty

closedWorld.includesClosureCall and closedWorld.locateMembers produce the wrong result for [subtype=Function].

rakudrama avatar Jun 11 '25 22:06 rakudrama

  bool includesClosureCallInDomain(
    Selector selector,
    AbstractValue? receiver,
    AbstractValueDomain abstractValueDomain,
  ) {
    return selector.name == Identifiers.call &&
        (receiver == null ||
            // This is logically equivalent to the former implementation using
            // `abstractValueDomain.contains` (which wrapped `containsMask`).
            // The switch to `abstractValueDomain.containsType` is because
            // `contains` was generally unsound but happened to work correctly
            // here. See https://dart-review.googlesource.com/c/sdk/+/130565
            // for further discussion.
            //
            // This checks if the receiver mask contains the entire type cone
            // originating from [_functionLub] and may therefore be unsound if
            // the receiver mask contains only part of the type cone. (Is this
            // possible?)
            //
            // TODO(fishythefish): Use `isDisjoint` or equivalent instead of
            // `containsType` once we can ensure it's fast enough.
            abstractValueDomain
                .containsType(receiver, _functionLub)
                .isPotentiallyTrue);
  }

This returns false when receiver=[subtype=Function] and _functionLub=j:class(Function).

The comment '(Is this possible?)' will soon be 'yes':

I want to strengthen the receiver type with the static type from the Kernel IR for closure calls as this is one of the few remaining sources of primitives checks. To get to the point where closure calls can be trusted, we will need to use '[subclass=Closure]' in more places in order to exclude calls to the one other class that implements Function: JavaScriptFunction. (A test case for this is newtonsMethod in the Flutter engine and flute benchmark. It calls a closure in a loop, so we can't afford to get rid of primitive checks by replacing them with dynamic calls).

rakudrama avatar Jun 11 '25 22:06 rakudrama