Page MenuHomeWildfire Games

Remove JS_THIS_OBJECT and JS::CallReceiver in preparation for SpiderMonkey 49 and 61
ClosedPublic

Authored by elexis on Tue, Aug 13, 2:54 AM.

Details

Summary

The ugly macro JS_THIS_OBJECT and the JS::CallReceiver have been removed in SpiderMonkey 61 and 49 respectively:

Remove JS::CallReceiver
Firefox 49

https://bugzilla.mozilla.org/show_bug.cgi?id=1270977

Remove JS_THIS_OBJECT
Firefox 61

https://bugzilla.mozilla.org/show_bug.cgi?id=1255800

Gotchas / Pitfalls:

  1. Test-for-object:

One can control the this argument from JS functions. The macro alwas behaved correctly, even if this was a number, null, or empty object.
If one uses JS::CallArgs without testing for non-objects, one will get segfaults / null-deref.

  1. Return Callstack:

Instead of passing nullptr, one can pass the CallArgs to JS_GetInstancePrivate, and thus obtain a nicer JS callstack:

ERROR: JavaScript error: gui/pregame/mainmenu.js line 20
TypeError: GUIObject.prototype.toString called on incompatible Object
init@gui/pregame/mainmenu.js:20:7

These three lines were not printed in the previous verison, when calling Engine.GetGUIObjectByName("submenu").toString.call({}); in the main menu`

Patch prehistory:
The patch was created by wraitii early 2018 in
https://github.com/wraitii/0ad/commit/9b5e783e47a2b060463b1ca3a305bfaac5d416ed
https://github.com/0ad/0ad/commit/f231c1d0412d903b9accb7fde8ebfee7ab248fa1

I tried my luck in december 2018 in D1699, but got sidetracked by unrelated cleanup.
In march 2019, rP22134 added one CallReceiverFromVp in preference of avoiding JS_THIS_OBJECT, copied from the wrong place.
In august 2019, in D2142 I attempted again to remove the macros and got sidetracked again by deduplication.
In august 2019, rP22596 removed CallArgs, since they were redundant with the consistent CallReceiver use.
rP7259 added the TODO for JS_GetInstancePrivate and GetClass.

Test Plan

Read the specs.
Test passing crazy values as JS this object, numbers, null, empty object, using
Engine.GetGUIObjectByName("submenu").toString.call(crazyvalue);
Consider whether the two ScriptInterface functions may be merged.
Check that there is really no way to obtain the JS::CallArgs from the getProperty setProperty PropertyOps.
https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference/JSPropertyOp
Compie on VS2015 to be sure.
Make sure that every touched functions has the AutoRequest necessary, by looking up the specs for the function calls.
Remove every AutoRequest in touched functions that is unnecessary.
Check that the return value is always set, also in error cases.

Diff Detail

Repository
rP 0 A.D. Public Repository
Lint
Automatic diff as part of commit; lint not applicable.
Unit
Automatic diff as part of commit; unit tests not applicable.

Event Timeline

elexis created this revision.Tue, Aug 13, 2:54 AM

Build failure - The Moirai have given mortals hearts that can endure.

Link to build: https://jenkins.wildfiregames.com/job/docker-differential/370/display/redirect

elexis edited the test plan for this revision. (Show Details)Tue, Aug 13, 2:58 AM
elexis added a comment.EditedTue, Aug 13, 12:46 PM

Tested on VS2015.

On exception handling:
https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference/JSNative if the function does not succeed:

it should either report an error (using e.g. JS_ReportError or JS_ReportOutOfMemory) or raise an exception (using JS_SetPendingException), and the callback must return false. (Returning false without reporting an error or raising an exception terminates the script with an uncatchable error.

This requirement is met, because GetPrivate calls JS_ReportError if it is not an object and returns false;
or if it is an object but could not obtain a pointer from JS_GetInstancePrivate, obtained nullptr.
The stack rooting and static_casts dont fail, or the error will segfault in a later statement.
JS_GetInstancePrivate does report an error in case of returning null:
https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference/JS_GetInstancePrivate

JS_PUBLIC_API void* JS_GetInstancePrivate(JSContext* cx, HandleObject obj,
                                          const JSClass* clasp,
                                          CallArgs* args) {
  if (!JS_InstanceOf(cx, obj, clasp, args)) {
    return nullptr;
  }
  return obj->as<NativeObject>().getPrivate();
}


JS_PUBLIC_API bool JS_InstanceOf(JSContext* cx, HandleObject obj,
                                 const JSClass* clasp, CallArgs* args) {
  AssertHeapIsIdle();
  CHECK_THREAD(cx);
#ifdef DEBUG
  if (args) {
    cx->check(obj);
    cx->check(args->thisv(), args->calleev());
  }
#endif
  if (!obj || obj->getJSClass() != clasp) {
    if (args) {
      ReportIncompatibleMethod(cx, *args, Valueify(clasp));
    }
    return false;
  }
  return true;
}

This code hasnt changed between sm45 and sm68.

From https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference/JS_GetInstancePrivate

Note that if obj is an instance of clasp, but there is no private data currently associated with the object, or the object cannot have private data, JS_GetInstancePrivate also returns NULL.

So in this case, there should still be an error reported.
And it is reported in the JSNative that calls ScriptInterface::GetPrivate.

But the function should have a comment communicating this obligation to the caller. (Edit: The JSNative is the one making that requirement, i.e. none of GetPrivate's business)

source/scriptinterface/NativeWrapperDefns.h
154 ↗(On Diff #9316)

setUndefined unnecessary:

I set undefined because I remember the specs saying

the callback must set JS::CallArgs::rval() or call JS_SET_RVAL (at least once) and return true

https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference/JSNative

But that's only the case when returning true, not when returning false..

source/scriptinterface/ScriptInterface.h
350 ↗(On Diff #9316)

JS_ReportError requires Request mode too

(Also for reference, some brief discussions with wraitii on http://irclogs.wildfiregames.com/2019-08/2019-08-12-QuakeNet-%230ad-dev.log)

Further exception handling:

So the JSNatives that call this must still throw an exception in case the given JS Object is of the right class, but its data pointer being null.

Either the function supports nullpointer privates, then the caller has to JS_ReportError if it receives one.
But if it does that, then it overwrites the previous exceptions, making those useless.
And we dont want to add another output parameter I think.
...
Since all use cases assume that the private data is the C++ class instance that manages the JS object, and
since one can only distinguish the JS_GetInstancePrivate error case from the valid nullptr case by asking for JS_IsExceptionPending, it seems better to have the ScriptInterface::GetPrivate function in such a way that it always triggers an exception or returns the non-null class instance pointer.

To test this case where the object is of an appropriate class but has null prviate data, one can remove the JS_SetPrivate clal in IGUIObject.cpp.

Compiling takes forever.

source/gui/scripting/JSInterface_GUITypes.cpp
118 ↗(On Diff #9316)

This exception is still never thrown, but I must declare this subject to the GUI refactoring patches floating around, since this spidermonkey-upgrade shouldn't be hidden in refactoring, and since the obvious spidermonkey patches failed several times due to missing non-obvious GUI refactoring patches (D1699, D2142, ...).

source/gui/scripting/JSInterface_IGUIObject.cpp
227 ↗(On Diff #9316)

So this JSNative violates the specs by not triggering an exception on false since rP8629

source/scriptinterface/NativeWrapperDefns.h
154 ↗(On Diff #9316)

Still doesnt throw the exception that JSNative expects.
Would be broken since rP7259 if that SM version also had that requirement.

source/scriptinterface/ScriptInterface.h
342 ↗(On Diff #9316)

JS_GetInstancePrivate not requiring Request mode is a fake news!

  1. This page promises that the specs inform one of when its required

https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_Reference/JS_BeginRequest

In a JS_THREADSAFE build, many JSAPI functions must only be called from within a request. In this reference, the cx parameter of such functions is documented with the phrase “Requires request”

  1. This page doesn't mention this, while many others do:

https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference/JS_GetInstancePrivate

  1. If you look at the source of JS_GetInstancePrivate you find the first statement is JS_InstanceOf.
  1. https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference/JS_InstanceOf says

Requires request

So we have to have the request mode here too.

This revision was not accepted when it landed; it landed in state Needs Review.Tue, Aug 13, 4:11 PM
This revision was landed with ongoing or failed builds.
This revision was automatically updated to reflect the committed changes.