[Webkit-unassigned] [Bug 206985] New: No clean way of calling JSObjectCallAsFunction with an undefined 'this' object

bugzilla-daemon at webkit.org bugzilla-daemon at webkit.org
Wed Jan 29 20:47:22 PST 2020


https://bugs.webkit.org/show_bug.cgi?id=206985

            Bug ID: 206985
           Summary: No clean way of calling JSObjectCallAsFunction with an
                    undefined 'this' object
           Product: WebKit
           Version: WebKit Nightly Build
          Hardware: All
                OS: All
            Status: NEW
          Severity: Normal
          Priority: P2
         Component: JavaScriptCore
          Assignee: webkit-unassigned at lists.webkit.org
          Reporter: gabriel at kronopath.net

This isn't a bug, but instead is, as far as I can tell, a bit of an oversight in the API.

If you have a JSObjectRef that points to a function object, you can call that function with JSObjectCallAsFunction. This is useful for handling callbacks from native code in C++ (heavily simplified code):

  // C++ side:
  void nativeFunction(JSObjectRef callback) {
    // ...
    JSObjectCallAsFunction(ctx, callback, thisObject, 1, resultArgs, &exception);
    // ...
  }

  // JS side:
  nativeFunction(function (result) {
    doSomeStuffWith(result);
  });

This is roughly analogous to callback-based programming in JS:

  function longRunningFunction(callback) {
    // ...
    callback();
    // ...
  }

  longRunningFunction(function (result) {
    doSomeStuffWith(result);
  });

There's a particular quirk that comes up with the 'this' object, though. In non-strict mode, accessing 'this' inside the callback will give you the global object:

  longRunningFunction(function (result) {
    this === globalThis; // true in non-strict mode
  });

But when you are in strict mode, 'this' ends up being undefined:

  "use strict";
  longRunningFunction(function (result) {
    this === undefined; // true
  });

Currently, in callbacks from a native function, you can emulate the non-strict-style behaviour by passing nullptr as the thisObject in JSObjectCallAsFunction. As the docs say, this sets 'this' to be the global object.

The implementation of this behaviour is here: https://github.com/WebKit/webkit/blob/db267339ce27f469f18aa15b64f0915c1ea33869/Source/JavaScriptCore/API/JSObjectRef.cpp#L696-L697

However, this is true regardless of whether your function was declared to be strict or non-strict. JSC always sets 'this' to be the global object if you pass nullptr. The only other alternative is to pass an actual alternative object for 'thisObject'.

There doesn't seem to be a clean way of emulating the strict-mode functionality where 'this' is undefined.

  auto undefinedThis = JSValueToObject(ctx, JSValueMakeUndefined(ctx), &exception);
  // Check exception here
  JSObjectCallAsFunction(ctx, callback, undefinedThis, 1, resultArgs, &exception);

This doesn't work because JSValueToObject raises a JS exception saying "undefined is not an object".

  auto undefinedThis = const_cast<JSObjectRef>(JSValueMakeUndefined(ctx));
  JSObjectCallAsFunction(ctx, callback, undefinedThis, 1, resultArgs, &exception);

This seems to work, but it really doesn't seem kosher. I'm not an expert in the JSC codebase yet, it seems like this works because it just so happens that no one in the call chain of JSObjectCallAsFunction ever actually treats thisObject as an object, in the sense of trying to access its properties and the like. It just manipulates the wrapper values for it and dodges all the footguns that could happen along the way, and if the implementation ever changes it's possible that this hack would run into one of them.

Is this solution safe to use? It seems to work, but this doesn't look like the way the API was intended to work. But, as far as I can tell, this is the only way to have an 'undefined' 'this' from a function call triggered by native code.

If you compare this to Facebook's Hermes VM, they have two methods for calling function objects, one callWithThis where you can provide your own custom 'this' object, and one that's just 'call', where it leaves it either undefined or sets it to the global object depending on strictness. That latter API, specifically with strict semantics, is what JSC currently lacks.
https://github.com/facebook/hermes/blob/6e7eecd93bfd8c3f8c2f790a30b1fe98eefe5e28/API/jsi/jsi/jsi-inl.h#L222-L265

Would it make sense to add this to the JSC API?

-- 
You are receiving this mail because:
You are the assignee for the bug.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.webkit.org/pipermail/webkit-unassigned/attachments/20200130/7d43aae5/attachment.htm>


More information about the webkit-unassigned mailing list