Tuesday, September 06, 2005

Some interesting output:

GetComponent: found a component object!
Button1Click got the component
Able to create a JS function for Hello!!!
*****Created the JS object for GUUID 2ABDF00B-025E-11DA-BFFE-000A27942344
Button1Click got the object
******Hello from HelloClass C++

JavaScript is now calling into my C++ component. Yay!

However, I ran into some JavaScript engine crashes when trying to call a function not exported from the component; this illuminated the need to make changes to how I was allocating certain JS engine data structures and so forth. Thanks to Brendan Eich and John Bandhauer for pointing out the issues. Here was the original post, and their responses:


I'm working on code that, like xpconnect, bridges JavaScript code and and C++ code found in a shared library component. In doing so, JavaScript code makes a call to obtain an object for a C++ class it is interested in using, and then calls functions on it. For example:

function Button1Click()
dump("Inside of Button1Click\n");
dump(170 + "\n");

var component = componentmgr.getComponent("94981D9E-FC99-11D9-8BDE-F66BAD1E3F3A");
if (component) {
dump("Button1Click got the component\n");
var anobject = component.getObject("2ABDF00B-025E-11DA-BFFE-000A27942344");

if (anobject) {
dump("Button1Click got the object\n");
anobject.Hello("Syd"); // a function that does exist
anobject.Foobar(); // a function that doesn't exist, crash


To handle the request by the above code for a class object, a JavaScript object is created and populated with pointers to extern "C" functions that wrap the object, using JS_DefineFunction(). This JavaScript object is then returned to the caller. Here's the relevant code:

JSClass objectClass = {
name.c_str(), JSCLASS_NEW_RESOLVE,
JS_PropertyStub, JS_PropertyStub,
JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub,
JS_ConvertStub, JS_FinalizeStub

m_jsObj = JS_DefineObject(ctx, obj, name.c_str(),
&objectClass, NULL, 0);


list args = func->GetArgumentList();
int size = args.size();

// query the component for the proxy lib entry point

JSNative fptr = (JSNative) lib->FindSymbol(name2);

if (fptr) {
JSFunction *func = JS_DefineFunction(ctx, m_jsObj,
fptr, // JSNative
size, 0);


Here is the problem: if the JavaScript function calls functions that were explicitly added to the object via JS_DefineFunction(), things work great. However, if I call a function that was not added this way, the JavaScript engine crashes hard:

#0 0xa30a353c in gNotifyRef ()
#1 0x00672854 in js_LookupPropertyWithFlags (cx=0x3e2950, obj=0x3e5a80, id=4678032, flags=1, objp=0xbfffdbc0, propp=0xbfffdbc4) at jsobj.c:2542
#2 0x0067235c in js_LookupProperty (cx=0x3e2950, obj=0x3e5a80, id=4678032, objp=0xbfffdbc0, propp=0xbfffdbc4) at jsobj.c:2447
#3 0x0067378c in js_GetProperty (cx=0x3e2950, obj=0x3e5a80, id=4678032, vp=0xbfffdd1c) at jsobj.c:2732
#4 0x00655874 in js_Interpret (cx=0x3e2950, pc=0x47921b "5", result=0xbfffe3b0) at jsinterp.c:3290
#5 0x00644fac in js_Invoke (cx=0x3e2950, argc=0, flags=2) at jsinterp.c:1193
#6 0x00645414 in js_InternalInvoke (cx=0x3e2950, obj=0x3e3fc0, fval=4086384, flags=0, argc=0, argv=0x0, rval=0xbfffe664) at jsinterp.c:1270
#7 0x005fd0fc in JS_CallFunction (cx=0x3e2950, obj=0x3e3fc0, fun=0x44a7a0, argc=0, argv=0x0, rval=0xbfffe664) at jsapi.c:3867
#8 0x000066f8 in Button::ButtonPressed() (this=0x4650d0) at button.cpp:129
#9 0x002cebf0 in ButtonPressSubject::NotifyButtonPress() (this=0x465108) at ../subject.cpp:13
#10 0x002cd4d0 in CocoaButtonImpl::HandleCommand() (this=0x465100) at cocoabuttonimpl.mm:55
#11 0x002ce28c in -[ButtonAction onClick:] (self=0x546050, _cmd=0x2d9f84, sender=0x5463b0) at cocoabuttonaction.mm:15
#12 0x930f9f5c in -[NSApplication sendAction:to:from:] ()
#13 0x9311a718 in -[NSControl sendAction:to:] ()
#14 0x9315eea4 in -[NSCell _sendActionFrom:] ()
#15 0x930f3968 in -[NSCell trackMouse:inRect:ofView:untilMouseUp:] ()
#16 0x9315eac0 in -[NSButtonCell trackMouse:inRect:ofView:untilMouseUp:] ()
#17 0x9312e6b4 in -[NSControl mouseDown:] ()
#18 0x930c1698 in -[NSWindow sendEvent:] ()
#19 0x930a94ec in -[NSApplication sendEvent:] ()
#20 0x930b2418 in -[NSApplication run] ()
#21 0x002ce1b4 in CocoaAppImpl::MainLoop() (this=0x45fb50) at cocoaappimpl.mm:28
#22 0x00008200 in App::MainLoop() (this=0x45fad0) at app.cpp:23
#23 0x00003bec in main (argc=0, argv=0xbffffb90) at layout.cpp:305

I'm wondering, is there something that needs to be done to protect calling applications from tubing the JavaScript engine in this way? I'm still getting my feet wet with JavaScript engine embedding, though I have made great progress, this is stumping me a bit.

Brendan's response:

Hey Syd,

Simplest theory first: if this is how the actual code looks, including objectClass being local to the calling function or method, then that won't work. The JSClass must live as long as any instance of it, and typically the way this is done is by making the JSClass struct static.


jband's response:

> JSClass objectClass = {
> name.c_str(), JSCLASS_NEW_RESOLVE,
> JS_PropertyStub, JS_PropertyStub,
> JS_PropertyStub, JS_PropertyStub,
> JS_EnumerateStub, JS_ResolveStub,
> JS_ConvertStub, JS_FinalizeStub
> };

Hi Syd,

I'm not even going to ask why you'd want to do this. I guess this makes for an interesting book project. But, I'm still a believer that XPConnect was the right way to go :-)

I'd say one mistake is in using JSCLASS_NEW_RESOLVE and passing JS_ResolveStub. I believe that JSCLASS_NEW_RESOLVE tells the engine that the resolve callback is of type JSNewResolveOp, but you are passing in a callback of type JSResolveOp. That would be bad.

I'd also have to wonder if the name string you are passing is static (name.c_str()) - the engine doesn't make a copy of it. As Brendan pointed out, same story for the entire JSClass structure.

You are either calling the wrong address if the JSClass* is not pointing to data that doesn't change, or you are screwing up the stack on return from JS_ResolveStub.

BTW, it is good to say explicitly which js version you are using when asking for help so that folks can correlate line numbers in stack dumps with actual source lines.