NEW 206985
No clean way of calling JSObjectCallAsFunction with an undefined 'this' object
https://bugs.webkit.org/show_bug.cgi?id=206985
Summary No clean way of calling JSObjectCallAsFunction with an undefined 'this' object
Gabriel B. Nunes
Reported 2020-01-29 20:47:22 PST
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?
Attachments
Radar WebKit Bug Importer
Comment 1 2020-01-30 14:51:32 PST
Konnor Krupp
Comment 2 2020-06-16 15:01:16 PDT
iOS 13.4.0 (rolling up JSC) escalated this bug from requiring an inconvenient workaround to a full crash. We previously were doing something almost identical to Gabriel's last snippet: "auto undefinedThis = const_cast<JSObjectRef>(JSValueMakeUndefined(ctx)); JSObjectCallAsFunction(ctx, callback, undefinedThis, 1, resultArgs, &exception);" As of iOS 13.4.0 this *crashes* the iOS application (seemingly because something is checking thisValue as a JSObjectRef) and as far as I can tell, has no workarounds beyond disregarding strict mode and always using the Global Context as `this` if none is provided +1 for Gabriel's suggestion to have the ability in JavaScriptCore for this!
Note You need to log in before you can comment on or make changes to this bug.