/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ /* ** */ /* * JS reflection of Java objects and vice versa * * javapackage is a java namespace * java is a java class or object * javaarray is a java array * javaslot represents an object+name that may resolve to * either a field or a method depending on later usage. * * netscape.javascript.JSObject is a java reflection of a JS object */ #ifdef JAVA #include #include /* java joy */ #include "oobj.h" #include "interpreter.h" #include "tree.h" #include "opcodes.h" #include "javaString.h" #include "exceptions.h" #include "jri.h" /* nspr stuph */ #ifndef NSPR20 #include "prhash.h" #else #include "plhash.h" #endif #include "prmon.h" /* for PR_XLock and PR_XUNlock */ #include "prlog.h" #include "prprf.h" #include "prgc.h" #include "jsapi.h" #include "jsjava.h" #include "jscntxt.h" /* for cx->savedErrors */ #include "jsscript.h" /* for script->javaData */ #include "jsobj.h" /* for OBJ_GET_SLOT and JSSLOT_PRIVATE */ #include "jsfun.h" /* for JSFUN_BOUND_METHOD */ #include "jslock.h" /* for JS_LOCK_RUNTIME/JS_UNLOCK_RUNTIME */ #ifdef MKLINUX #define VARARGS_ASSIGN(foo, bar) foo[0] = bar[0] #else #define VARARGS_ASSIGN(foo, bar) (foo) = (bar) #endif /*MKLINUX*/ #ifndef NSPR20 #define WAIT_FOREVER LL_MAXINT #else #define WAIT_FOREVER PR_INTERVAL_NO_TIMEOUT /* PR_LOG hacks */ #define debug PR_LOG_MAX #define error PR_LOG_ERROR #define warn PR_LOG_WARNING #endif /* * "exception" is defined by some Windows OS headers. * Redefine it for our own purposes. */ #ifdef exception #undef exception #endif /* * we don't have access to the classJavaLangObject from the java * runtime dll, so we need a different version of this macro */ static ClassClass *js_classJavaLangObject = 0; #undef obj_array_classblock #define obj_array_classblock(obj) \ ((obj_flags((obj)) == T_NORMAL_OBJECT) ? (obj)->methods->classdescriptor \ : ObjectClassBlock) /* JSObject generated header */ #define IMPLEMENT_netscape_javascript_JSObject #include "netscape_javascript_JSObject.h" #define IMPLEMENT_netscape_javascript_JSException #ifdef XP_MAC #include "n_javascript_JSException.h" #else #include "netscape_javascript_JSException.h" #endif #include "java_lang_Throwable.h" /* * types of reflected java objects */ /* a package is basically just a name, since the jdk doesn't * allow you to find out whether a particular name represents * a package or not */ typedef struct JSJavaPackage JSJavaPackage; /* either a java class or a java object. * class.field is a static field or method * class(...) constructs an object that is an instance of the class * object.field is a field or method */ typedef struct JSJava JSJava; /* type associated with a JSJava structure */ /* the JSClass java_class uses OBJECT and CLASS * javaarray_class uses ARRAY */ typedef enum JSJavaType { JAVA_UNDEF, JAVA_OBJECT, JAVA_CLASS, JAVA_ARRAY } JSJavaType; /* JSJClassData holds the info necessary for js to represent * itself to java. When js calls java, it leaves a call to * the method referred to by mb on the java stack. Each * JSScript that calls java contains its own copy of this, * though the classloader and class may be shared. */ typedef struct { int nrefs; jglobal loader; jglobal clazz; struct methodblock *mb; } JSJClassData; /* fields and methods are initially represented with a slot object: * this allows us to cope with fields and methods that have the same * name. if the slot is used in a function context it will call the * appropriate method (dynamically choosing between overloaded methods). * if it is used in any other context it will convert itself to the * value of the field at the time it was looked up. */ typedef struct JSJavaSlot JSJavaSlot; /* * globals for convenience, set up in the init routine */ static ClassClass * ObjectClassBlock = 0; static ClassClass * JSObjectClassBlock = 0; static ClassClass * JSExceptionClassBlock = 0; static ClassClass * StringClassBlock = 0; static ClassClass * BooleanClassBlock = 0; static ClassClass * DoubleClassBlock = 0; static ClassClass * ThrowableClassBlock = 0; static JRIFieldID JSObjectInternalField; /* note that these hash tables are only stable because we * use a non-moving garbage collector! */ /* * this is used to ensure that there is at most one reflection * of any java object. objects are keyed by handle, classes are * keyed by classblock pointer. the value for each is the JS * object reflection. there is a root finder registered with the * gc that marks all the java objects (keys). the JS objects * are not in the GC's root set, and are responsible for removing * themselves from the table upon finalization. */ static PRHashTable *javaReflections = NULL; static PRMonitor *javaReflectionsMonitor = 0; /* this jsContext is used when scanning the reflection table */ /* * similarly for java reflections of JS objects - in this case * the keys are JS objects. when the corresponding java instance * is finalized, the entry is removed from the table, and a GC * root for the JS object is removed. */ static PRHashTable *jsReflections = NULL; static PRMonitor *jsReflectionsMonitor = 0; /* * Because of the magic of windows DLLs we need to get this passed in */ static JSJCallbacks *jsj_callbacks = NULL; /* * we keep track of the "main" runtime in order to use it for * finalization (see js_RemoveReflection). this depends on there * being only one JSRuntime which can run java, which may not be * true eventually. */ JSRuntime *finalizeRuntime = 0; #ifndef NSPR20 #ifdef MOZILLA_CLIENT PR_LOG_DEFINE(MojaSrc); #else PRLogModuleInfo *MojaSrc; #endif #else PRLogModuleInfo *MojaSrc = NULL; #endif /* lazy initialization */ static void jsj_FinishInit(JSContext *cx, JRIEnv *env); /* check if a field is static/nonstatic */ /* this now accepts static fields for non-static references */ #define CHECK_STATIC(isStatic, fb) (((fb)->access & ACC_STATIC) \ ? (isStatic) : !(isStatic)) /* forward declarations */ typedef void (*JSJCallback)(void*); static JSBool js_CallJava(JSContext *cx, JSJCallback doit, void *d, JSBool pushSafeFrame); static JSBool js_FindSystemClass(JSContext *cx, char *name, ClassClass **clazz); static ClassClass * js_FindJavaClass(JSContext *cx, char *name, ClassClass *from); static JSBool js_ExecuteJavaMethod(JSContext *cx, void *raddr, size_t rsize, HObject *ho, char *name, char *sig, struct methodblock *mb, JSBool isStaticCall, ...); static HObject * js_ConstructJava(JSContext *cx, char *name, ClassClass *cb, char *sig, ...); static HObject * js_ConstructJavaPrivileged(JSContext *cx, char *name, ClassClass *cb, char *sig, ...); static JSObject * js_ReflectJava(JSContext *cx, JSJavaType type, HObject *handle, ClassClass *cb, char *sig, JSObject *useMe); static JSBool js_reflectJavaSlot(JSContext *cx, JSObject *obj, JSString *str, jsval *vp); static JSBool js_javaMethodWrapper(JSContext *cx, JSObject *obj, PRUintn argc, jsval *argv, jsval *rval); static JSBool js_javaConstructorWrapper(JSContext *cx, JSObject *obj, PRUintn argc, jsval *argv, jsval *rval); static JSBool js_JArrayElementType(HObject *handle, char **sig, ClassClass **classp); static JSBool js_convertJSValueToJSObject(JSContext *cx, JSObject *mo, ClassClass *paramcb, JSBool checkOnly, HObject **objp); static JSBool js_convertJSValueToJElement(JSContext *cx, jsval v, char *addr, char *sig, ClassClass *fromclass, char **sigRest); static JSBool js_convertJSValueToJValue(JSContext *cx, jsval v, OBJECT *addr, char *sig, ClassClass *fromclass, JSBool checkOnly, char **sigRestPtr, int *cost); static JSBool js_convertJSValueToJField(JSContext *cx, jsval v, HObject *ho, struct fieldblock *fb); static JSBool js_convertJElementToJSValue(JSContext *cx, char *addr, char *sig, jsval *v, JSType desired); static JSBool js_convertJValueToJSValue(JSContext *cx, OBJECT *addr, char *sig, jsval *vp, JSType desired); static JSBool js_convertJFieldToJSValue(JSContext *cx, HObject *ho, struct fieldblock *fb, jsval *vp, JSType desired); static JSBool js_convertJObjectToJSString(JSContext *cx, HObject *ho, JSBool isClass, jsval *vp); static JSBool js_convertJObjectToJSNumber(JSContext *cx, HObject *ho, JSBool isClass, jsval *vp); static JSBool js_convertJObjectToJSBoolean(JSContext *cx, HObject *ho, JSBool isClass, jsval *vp); static JSJClassData * jsj_MakeClassData(JSContext *cx); static void jsj_DestroyClassData(JSContext *cx, JSJClassData *data); static JSBool js_pushSafeFrame(JSContext *cx, ExecEnv *ee, JSJClassData **classData); static void js_popSafeFrame(JSContext *cx, ExecEnv *ee, JSJClassData *classData); /* handy macro from agent.c */ #define obj_getoffset(o, off) (*(OBJECT *)((char *)unhand(o)+(off))) /* Java threads call this to find the JSContext to use for * executing JavaScript */ PR_IMPLEMENT(int) JSJ_IsEnabled() { return jsj_callbacks->isEnabled(); } /* Java threads call this to find the JSContext to use for * executing JavaScript */ PR_IMPLEMENT(JSContext *) JSJ_CurrentContext(JRIEnv *env, char **errp) { return jsj_callbacks->currentContext(env, errp); } /* Java threads call this before executing JS code (to preserve * run-to-completion in the client */ PR_IMPLEMENT(void) JSJ_EnterJS(void) { jsj_callbacks->enterJS(); } /* Java threads call this when finished executing JS code */ PR_IMPLEMENT(void) JSJ_ExitJS(void) { jsj_callbacks->exitJS(); } /* Java threads call this when finished executing JS code */ PR_IMPLEMENT(JSObject *) JSJ_GetDefaultObject(JRIEnv *env, jobject hint) { return jsj_callbacks->getDefaultObject(env, hint); } static ExecEnv * jsj_GetCurrentEE(JSContext *cx) { JRIEnv *env = 0; static int js_java_initialized = 0; if (js_java_initialized != 2) { /* use a cached monitor to make sure that we only * initialize once */ PR_CEnterMonitor(&js_java_initialized); switch (js_java_initialized) { case 0: /* we're first */ js_java_initialized = 1; PR_CExitMonitor(&js_java_initialized); /* force both Java and JS to be initialized */ env = jsj_callbacks->currentEnv(cx); if (!env) goto out; /* initialize the hash tables and classes we need */ jsj_FinishInit(cx, env); PR_CEnterMonitor(&js_java_initialized); js_java_initialized = 2; PR_CNotifyAll(&js_java_initialized); break; case 1: /* in progress */ PR_CWait(&js_java_initialized, WAIT_FOREVER); break; case 2: /* done */ break; } PR_CExitMonitor(&js_java_initialized); } if (!env) env = jsj_callbacks->currentEnv(cx); out: if (!env) JS_ReportError(cx, "unable to get Java execution context for JavaScript"); return (ExecEnv *) env; } /**** **** **** **** **** **** **** **** **** * * java packages are just strings * **** **** **** **** **** **** **** **** ****/ struct JSJavaPackage { char *name; /* e.g. "java/lang" or 0 if it's the top level */ }; /* javapackage uses standard getProperty */ static JSBool javapackage_setProperty(JSContext *cx, JSObject *obj, jsval slot, jsval *vp) { JSJavaPackage *package = JS_GetPrivate(cx, obj); JS_ReportError(cx, "%s doesn't refer to any Java value", package->name); return JS_FALSE; } static JSBool javapackage_enumerate(JSContext *cx, JSObject *obj) { /* FIXME can't do this without reading directories... */ return JS_TRUE; } /* forward declaration */ static JSBool javapackage_resolve(JSContext *cx, JSObject *obj, jsval id); static JSBool javapackage_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp) { JSJavaPackage *package = JS_GetPrivate(cx, obj); JSString *str; char *name, *cp; switch (type) { case JSTYPE_STRING: /* convert '/' to '.' so it looks like the entry syntax */ if (!package->name) break; name = PR_smprintf("[JavaPackage %s]", package->name); if (!name) { JS_ReportOutOfMemory(cx); return JS_FALSE; } for (cp = name; *cp != '\0'; cp++) if (*cp == '/') *cp = '.'; str = JS_NewString(cx, name, strlen(name)); if (!str) { free(name); JS_ReportOutOfMemory(cx); return JS_FALSE; } *vp = STRING_TO_JSVAL(str); break; case JSTYPE_OBJECT: *vp = OBJECT_TO_JSVAL(obj); break; default: break; } return JS_TRUE; } static void javapackage_finalize(JSContext *cx, JSObject *obj) { JSJavaPackage *package = JS_GetPrivate(cx, obj); /* get rid of the private data */ if (package->name) JS_free(cx, package->name); JS_free(cx, package); } static JSClass javapackage_class = { "JavaPackage", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, javapackage_setProperty, javapackage_enumerate, javapackage_resolve, javapackage_convert, javapackage_finalize }; /* needs pointer to javapackage_class */ static JSBool javapackage_resolve(JSContext *cx, JSObject *obj, jsval id) { JSJavaPackage *package = JS_GetPrivate(cx, obj); char *fullname; char *name; int namelen; ClassClass *cb; JSObject *mo; JSJClassData *classData; JRIEnv *env; if (!JSVAL_IS_STRING(id)) return JS_TRUE; name = JS_GetStringBytes(JSVAL_TO_STRING(id)); namelen = JS_GetStringLength(JSVAL_TO_STRING(id)); if (package->name) { int packagelen = strlen(package->name); fullname = JS_malloc(cx, packagelen + namelen + 2); if (!fullname) return JS_FALSE; strcpy(fullname, package->name); fullname[packagelen] = '/'; strcpy(fullname + packagelen + 1, name); } else { fullname = JS_malloc(cx, namelen + 1); if (!fullname) return JS_FALSE; strcpy(fullname, name); } PR_LOG(MojaSrc, debug, ("looking for java class \"%s\"", fullname)); /* get the class whose classloader we use to find more classes */ if (!jsj_callbacks->jsClassLoader) { classData = NULL; } else if (!(classData = jsj_MakeClassData(cx))) { JS_free(cx, fullname); return JS_FALSE; } env = (JRIEnv *) jsj_GetCurrentEE(cx); if (!env) { JS_free(cx, fullname); return JS_FALSE; } /* see if the name is a class */ cb = js_FindJavaClass(cx, fullname, classData ? JRI_GetGlobalRef(env, classData->clazz) : NULL); if (jsj_callbacks->jsClassLoader) jsj_DestroyClassData(cx, classData); /* if not, it's a package */ if (!cb) { JSJavaPackage *newpackage = JS_malloc(cx, sizeof(JSJavaPackage)); if (!newpackage) { JS_free(cx, fullname); return JS_FALSE; } PR_LOG(MojaSrc, debug, ("creating package %s", fullname)); newpackage->name = fullname; mo = JS_NewObject(cx, &javapackage_class, 0, 0); /* FIXME proto and parent ok? */ if (!mo) { JS_free(cx, fullname); JS_free(cx, newpackage); return JS_FALSE; } JS_SetPrivate(cx, mo, newpackage); } else { /* reflect the Class object */ mo = js_ReflectJClassToJSObject(cx, cb); if (!mo) { /* FIXME error message? */ return JS_FALSE; } JS_free(cx, fullname); } return JS_DefineProperty(cx, obj, name, OBJECT_TO_JSVAL(mo), 0, 0, JSPROP_READONLY); } /**** **** **** **** **** **** **** **** ****/ struct JSJava { JSJavaType type; /* object / array / class */ HObject *handle; /* handle to the java Object */ ClassClass *cb; /* classblock, or element cb if array */ char *signature; /* array element signature */ }; static struct fieldblock * java_lookup_field(JSContext *cx, ClassClass *cb, JSBool isStatic, const char *name) { while (cb) { int i; for (i = 0; i < (int)cbFieldsCount(cb); i++) { struct fieldblock *fb = cbFields(cb) + i; if (CHECK_STATIC(isStatic, fb) && !strcmp(fieldname(fb), name)) { return fb; } } /* check the parent */ if (cbSuperclass(cb)) cb = cbSuperclass(cb); else cb = 0; } return 0; } static struct fieldblock * java_lookup_name(JSContext *cx, ClassClass *cb, JSBool isStatic, const char *name) { while (cb) { int i; for (i = 0; i < (int)cbFieldsCount(cb); i++) { struct fieldblock *fb = cbFields(cb) + i; if (CHECK_STATIC(isStatic, fb) && !strcmp(fieldname(fb), name)) { return fb; } } for (i = cbMethodsCount(cb); i--;) { struct methodblock *mb = cbMethods(cb) + i; if (CHECK_STATIC(isStatic, &mb->fb) && !strcmp(fieldname(&mb->fb), name)) { return &mb->fb; } } /* check the parent */ if (cbSuperclass(cb)) cb = cbSuperclass(cb); else cb = 0; } return 0; } static JSBool java_getProperty(JSContext *cx, JSObject *obj, jsval slot, jsval *vp) { JSString *str; JSJava *java = JS_GetPrivate(cx, obj); if (!java) { JS_ReportError(cx, "illegal operation on Java prototype object"); return JS_FALSE; } /* FIXME reflect a getter/setter pair as a property! */ if (!JSVAL_IS_STRING(slot)) { JS_ReportError(cx, "invalid Java property expression"); return JS_FALSE; } str = JSVAL_TO_STRING(slot); PR_LOG(MojaSrc, debug, ("looked up slot \"%s\"", JS_GetStringBytes(str))); /* utter hack so that the assign hack doesn't kill us */ if (slot == STRING_TO_JSVAL(ATOM_TO_STRING(cx->runtime->atomState.assignAtom))) { *vp = JSVAL_VOID; return JS_TRUE; } return js_reflectJavaSlot(cx, obj, str, vp); } static JSBool java_setProperty(JSContext *cx, JSObject *obj, jsval slot, jsval *vp) { JSJava *java = JS_GetPrivate(cx, obj); ClassClass *cb; struct fieldblock *fb = 0; JSString *str; const char *name; if (!java) { JS_ReportError(cx, "illegal operation on Java prototype object"); return JS_FALSE; } cb = java->cb; if (!JSVAL_IS_STRING(slot)) { JS_ReportError(cx, "invalid Java property assignment"); return JS_FALSE; } str = JSVAL_TO_STRING(slot); name = JS_GetStringBytes(str); PR_LOG(MojaSrc, debug, ("looked up slot \"%s\"", name)); fb = java_lookup_field(cx, cb, java->type == JAVA_CLASS, name); if (!fb) { JS_ReportError(cx, "no Java %sfield found with name %s", (java->type == JAVA_CLASS ? "static " : ""), name); return JS_FALSE; } if (!js_convertJSValueToJField(cx, *vp, java->handle, fb)) { JS_ReportError(cx, "can't set Java field %s", name); return JS_FALSE; } return JS_TRUE; } static JSBool java_enumerate_property(JSContext *cx, JSObject *obj, struct fieldblock *fb) { JSJava *java = JS_GetPrivate(cx, obj); jsval junk; PR_ASSERT(java->type == JAVA_OBJECT || java->type == JAVA_CLASS); if (!(fb->access & ACC_PUBLIC) || !CHECK_STATIC(java->type == JAVA_CLASS, fb)) { return JS_TRUE; } return JS_GetProperty(cx, obj, fieldname(fb), &junk); /* FIXME this is what i would prefer, but it seems the get is necessary return JS_DefineProperty(cx, obj, fieldname(fb), JSVAL_VOID, 0, 0, JSPROP_ENUMERATE); */ } static JSBool java_enumerate(JSContext *cx, JSObject *obj) { JSJava *java = JS_GetPrivate(cx, obj); ClassClass *cb; if (!java) return JS_TRUE; cb = java->cb; while (cb) { int i; for (i = 0; i < (int)cbFieldsCount(cb); i++) { struct fieldblock *fb, *outerfb; fb = cbFields(cb) + i; outerfb = java_lookup_name(cx, java->cb, java->type == JAVA_CLASS, fieldname(fb)); if (outerfb && outerfb != fb) continue; if (!java_enumerate_property(cx, obj, fb)) return JS_FALSE; } for (i = cbMethodsCount(cb); i--;) { struct methodblock *mb; struct fieldblock *outerfb; mb = cbMethods(cb) + i; outerfb = java_lookup_name(cx, java->cb, java->type == JAVA_CLASS, fieldname(&mb->fb)); if (outerfb && outerfb != &mb->fb) continue; if (!java_enumerate_property(cx, obj, &mb->fb)) return JS_FALSE; } /* check the parent */ if (cbSuperclass(cb)) cb = cbSuperclass(cb); else cb = 0; } return JS_TRUE; } PR_STATIC_CALLBACK(JSBool) java_resolve(JSContext *cx, JSObject *obj, jsval id) { JSJava *java = JS_GetPrivate(cx, obj); char *name; jsval v; JSErrorReporter oldReporter; JSBool hasProperty; if (!java || !JSVAL_IS_STRING(id)) return JS_TRUE; name = JS_GetStringBytes(JSVAL_TO_STRING(id)); PR_LOG(MojaSrc, debug, ("resolve(0x%x, %s) (handle 0x%x)", obj, name, java->handle)); oldReporter = JS_SetErrorReporter(cx, NULL); hasProperty = java_getProperty(cx, obj, id, &v); JS_SetErrorReporter(cx, oldReporter); if (!hasProperty) return JS_TRUE; return JS_DefineProperty(cx, obj, name, v, 0, 0, JSPROP_ENUMERATE); } PR_STATIC_CALLBACK(PRHashNumber) java_hashHandle(void *key) { PRHashNumber num = (PRHashNumber) key ; /* help lame MSVC1.5 on Win16 */ /* win16 compiler can't shift right by 2, but it will do it by 1 twice. */ num = num >> 1; return num >> 1; } static int java_pointerEq(void *v1, void *v2) { return v1 == v2; } PR_STATIC_CALLBACK(void) java_finalize(JSContext *cx, JSObject *obj) { JSJava *java = JS_GetPrivate(cx, obj); void *key; PRHashEntry *he, **hep; if (!java) return; /* remove it from the reflection table */ if (java->type == JAVA_CLASS) { PR_LOG(MojaSrc, debug, ("removing class 0x%x from table", java->cb)); key = cbName(java->cb); } else { PR_LOG(MojaSrc, debug, ("removing handle 0x%x from table", java->handle)); key = java->handle; } PR_EnterMonitor(javaReflectionsMonitor); if (javaReflections) { hep = PR_HashTableRawLookup(javaReflections, java_hashHandle(key), key); he = *hep; if (he) { PR_HashTableRawRemove(javaReflections, hep, he); } } PR_ExitMonitor(javaReflectionsMonitor); /* get rid of the private data */ JS_free(cx, java); } PR_STATIC_CALLBACK(JSBool) java_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp) { JSJava *java = JS_GetPrivate(cx, obj); if (!java) { if (type == JSTYPE_OBJECT) { *vp = OBJECT_TO_JSVAL(obj); return JS_TRUE; } JS_ReportError(cx, "illegal operation on Java prototype object"); return JS_FALSE; } PR_LOG(MojaSrc, debug, ("java_convert to %d", type)); switch (type) { case JSTYPE_OBJECT: *vp = OBJECT_TO_JSVAL(obj); break; case JSTYPE_FUNCTION: /* only classes convert to functions (constructors) */ if (java->type != JAVA_CLASS) { /* random java objects do not convert to functions */ JS_ReportError(cx, "can't convert Java object to function"); return JS_FALSE; } else { JSString *str; JSFunction *fun; JSObject *funobj; JSObject *globalobj; jsval javaPrototype; PR_LOG(MojaSrc, debug, ("making a constructor\n")); str = JS_NewStringCopyZ(cx, cbName(java->cb)); if (!str) return JS_FALSE; fun = JS_NewFunction(cx, js_javaConstructorWrapper, 0, 0, 0, JS_GetStringBytes(str)); /* FIXME a private property of the function object points to the * classblock: gc problem? */ funobj = JS_GetFunctionObject(fun); if (!JS_DefineProperty(cx, funobj, "#javaClass", PRIVATE_TO_JSVAL(java->cb), 0, 0, JSPROP_READONLY)) { return JS_FALSE; } /* set the prototype so that objects constructed with * "new" have the right JSClass */ if (!(globalobj = JS_GetGlobalObject(cx)) || !JS_GetProperty(cx, globalobj, "#javaPrototype", &javaPrototype) || !JS_DefineProperty(cx, funobj, "prototype", javaPrototype, 0, 0, JSPROP_READONLY)) { return JS_FALSE; } *vp = OBJECT_TO_JSVAL(funobj); } break; case JSTYPE_STRING: /* either pull out the string or call toString */ return js_convertJObjectToJSString(cx, java->handle, java->type==JAVA_CLASS, vp); case JSTYPE_NUMBER: /* call doubleValue() */ return js_convertJObjectToJSNumber(cx, java->handle, java->type==JAVA_CLASS, vp); case JSTYPE_BOOLEAN: /* call booleanValue() */ return js_convertJObjectToJSBoolean(cx, java->handle, java->type==JAVA_CLASS, vp); default: break; } return JS_TRUE; } static JSClass java_class = { "Java", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, java_getProperty, java_setProperty, java_enumerate, java_resolve, java_convert, java_finalize }; /**** **** **** **** **** **** **** **** ****/ static JSBool javaarray_getProperty(JSContext *cx, JSObject *obj, jsval slot, jsval *vp) { JSJava *array = JS_GetPrivate(cx, obj); HObject *ho = array->handle; PRInt32 index; PRInt32 size; char *addr; if (JSVAL_IS_STRING(slot)) { char *name = JS_GetStringBytes(JSVAL_TO_STRING(slot)); if (0 == strcmp(name, "length")) { *vp = INT_TO_JSVAL((jsint)obj_length(ho)); return JS_TRUE; } JS_ReportError(cx, "invalid Java array element %s", name); return JS_FALSE; } if (!JSVAL_IS_INT(slot)) { JS_ReportError(cx, "invalid Java array index expression"); return JS_FALSE; } index = JSVAL_TO_INT(slot); if (index < 0 || index >= (PRInt32) obj_length(ho)) { JS_ReportError(cx, "Java array index %d out of range", index); return JS_FALSE; } size = sizearray(obj_flags(ho), 1); addr = ((char*) unhand(ho)) + index * size; if (!js_convertJElementToJSValue(cx, addr, array->signature, vp, JSTYPE_VOID)) { JS_ReportError(cx, "can't convert Java array element %d to JavaScript value", index); return JS_FALSE; } return JS_TRUE; } static JSBool javaarray_setProperty(JSContext *cx, JSObject *obj, jsval slot, jsval *vp) { JSJava *array = JS_GetPrivate(cx, obj); HObject *ho = array->handle; PRInt32 index; PRInt32 size; char *addr; if (JSVAL_IS_STRING(slot)) { char *name = JS_GetStringBytes(JSVAL_TO_STRING(slot)); if (0 == strcmp(name, "length")) { JS_ReportError(cx, "can't set length of a Java array"); return JS_FALSE; } JS_ReportError(cx, "invalid assignment to Java array element %s", name); return JS_FALSE; } if (!JSVAL_IS_INT(slot)) { JS_ReportError(cx, "invalid Java array index expression"); return JS_FALSE; } index = JSVAL_TO_INT(slot); if (index < 0 || index >= (PRInt32) obj_length(ho)) { JS_ReportError(cx, "Java array index %d out of range", index); return JS_FALSE; } size = sizearray(obj_flags(ho), 1); addr = ((char*) unhand(ho)) + index * size; if (!js_convertJSValueToJElement(cx, *vp, addr, array->signature, array->cb, 0)) { JS_ReportError(cx, "invalid assignment to Java array element %d", index); return JS_FALSE; } return JS_TRUE; } static JSBool javaarray_enumerate(JSContext *cx, JSObject *obj) { JSJava *array = JS_GetPrivate(cx, obj); HObject *ho = array->handle; PRUint32 i; jsval v; for (i = 0; i < obj_length(ho); i++) { PRInt32 size = sizearray(obj_flags(ho), 1); char *addr = ((char*) unhand(ho)) + i * size; if (!js_convertJElementToJSValue(cx, addr, array->signature, &v, JSTYPE_VOID)) { return JS_FALSE; } if (!JS_SetElement(cx, obj, (jsint) i, &v)) { return JS_FALSE; } } return JS_TRUE; } static JSBool javaarray_resolve(JSContext *cx, JSObject *obj, jsval id) { JSJava *array = JS_GetPrivate(cx, obj); HObject *ho = array->handle; jsint index; if (!JSVAL_IS_INT(id)) return JS_TRUE; index = JSVAL_TO_INT(id); if (index < 0 || index >= (PRInt32) obj_length(ho)) return JS_TRUE; return JS_DefineElement(cx, obj, index, JSVAL_VOID, 0, 0, JSPROP_ENUMERATE); } static void javaarray_finalize(JSContext *cx, JSObject *obj) { JSJava *array = JS_GetPrivate(cx, obj); /* get rid of the private data */ /* array->signature is a static string */ ; array->signature = 0; /* remove it from the reflection table */ java_finalize(cx, obj); } static JSBool javaarray_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp) { switch (type) { case JSTYPE_OBJECT: *vp = OBJECT_TO_JSVAL(obj); break; case JSTYPE_STRING: /* FIXME how should arrays convert to strings? */ default: break; } return JS_TRUE; } static JSClass javaarray_class = { "JavaArray", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, javaarray_getProperty, javaarray_setProperty, javaarray_enumerate, javaarray_resolve, javaarray_convert, javaarray_finalize }; /**** **** **** **** **** **** **** **** ****/ /* this lameness is brought to you by the decision * to use strings for signatures in the JDK */ static char * getSignatureBase(char *sig) { int len; char *ret; char end; switch (*sig) { case SIGNATURE_CLASS: end = SIGNATURE_ENDCLASS; break; case SIGNATURE_FUNC: end = SIGNATURE_ENDFUNC; break; default: return 0; break; } len = strchr(sig+1, end) - (sig+1); ret = malloc(len+1); if (!ret) return 0; strncpy(ret, sig+1, len); ret[len] = '\0'; return ret; } static ClassClass * getSignatureClass(JSContext *cx, char *sig, ClassClass *from) { char *name; ClassClass *cb; if (sig[1] == '\0') { /* special hack: if the signature is "L" it means * just use the from class */ return from; } name = getSignatureBase(sig); if (!name) { JS_ReportOutOfMemory(cx); return 0; } cb = js_FindJavaClass(cx, name, from); free(name); return cb; } /* this doesn't need to run in a java env, but it may throw an * exception so we need a temporary one */ static JSBool js_isSubclassOf(JSContext *cx, ClassClass *cb1, ClassClass *cb2) { ExecEnv *ee = jsj_GetCurrentEE(cx); JSBool ret; /* FIXME what to do with the exception? */ if (!ee) { return JS_FALSE; } exceptionClear(ee); ret = (JSBool) is_subclass_of(cb1, cb2, ee); if (exceptionOccurred(ee)) { #ifdef DEBUG char *message; /* exceptionDescribe(ee); */ /* FIXME this could fail: we don't check if it's throwable, * but i assume that is_subclass_of is well-behaved */ HString *hdetail = unhand((Hjava_lang_Throwable *) ee->exception.exc)->detailMessage; ClassClass *cb = obj_array_classblock(ee->exception.exc); /* * FIXME JRI_GetStringPlatformChars and JRI_GetStringUTFChars both * return a pointer into an otherwise unreferenced, unrooted Java * array's body, without returning the Java array's handle. This * requires fairly conservative GC, which we have in NSPR1.0, but * if we lose it, this code may break. */ message = (char *) JRI_GetStringPlatformChars((JRIEnv *) ee, (struct java_lang_String *) hdetail, (const jbyte *) cx->charSetName, (jint) cx->charSetNameLength); PR_LOG(MojaSrc, error, ("exception in is_subclass_of %s (\"%s\")", cbName(cb), message)); #endif exceptionClear(ee); return JS_FALSE; } return ret ? JS_TRUE : JS_FALSE; } static JSBool js_convertJSValueToJSObject(JSContext *cx, JSObject *mo, ClassClass *paramcb, JSBool checkOnly, HObject **objp) { /* check if a JSObject would be an acceptable argument */ if (!js_isSubclassOf(cx, JSObjectClassBlock, paramcb)) return JS_FALSE; /* JSObject is ok, so convert mo to one. * a JS object which is not really a java object * converts to JSObject */ if (!checkOnly) { *objp = js_ReflectJSObjectToJObject(cx, mo); if (!*objp) return JS_FALSE; } return JS_TRUE; } static JSBool js_convertJSValueToJArray(HObject **objp, JSContext *cx, jsval v, char *sig, ClassClass *fromclass, JSBool checkOnly, int *cost) { /* FIXME bump the cost counter when necessary */ JSJava *java; HArrayOfObject *harr; ClassClass *acb; ClassClass *paramcb; JSObject *mo; char *elementsig; ClassClass *elementClazz; /* the only legal conversions are from null or from a java array */ if (!JSVAL_IS_OBJECT(v)) return JS_FALSE; /* can always pass null */ mo = JSVAL_TO_OBJECT(v); if (!mo) { if (!checkOnly) *objp = 0; return JS_TRUE; } /* otherwise, it must be a JS reflection of a java array */ if (! JS_InstanceOf(cx, mo, &javaarray_class, 0)) return JS_FALSE; java = JS_GetPrivate(cx, mo); sig++; /* skip to array element type */ switch (*sig) { case SIGNATURE_CLASS: /* object array */ paramcb = getSignatureClass(cx, sig, fromclass); PR_LOG(MojaSrc, debug, ("desired array element signature \"%s\"(0x%x)", sig, paramcb)); if (!paramcb) { PR_LOG(MojaSrc, warn, ("couldn't find class for signature \"%s\"\n", sig)); return JS_FALSE; } harr = (HArrayOfObject *) java->handle; acb = (ClassClass *)(unhand(harr)->body[class_offset(harr)]); if (!acb) { PR_LOG(MojaSrc, warn, ("couldn't find class of array element\n")); return JS_FALSE; } /* elements must convert */ if (js_isSubclassOf(cx, acb, paramcb)) { if (!checkOnly) *objp = (HObject *) harr; return JS_TRUE; } break; case SIGNATURE_ARRAY: /* nested array */ /* FIXME nested arrays can't be supported, because the * jdk runtime treats them all as flat * * This just in... Actually, you can get the dimensions of a * java array because they aren't flattened. */ /* FIXME throw an exception */ break; default: /* primitive array */ /* for any other array, the signature must match exactly */ if (js_JArrayElementType(java->handle, &elementsig, &elementClazz)) { if (elementsig[0] == sig[0]) { if (!checkOnly) *objp = java->handle; return JS_TRUE; } } break; } return JS_FALSE; } PR_IMPLEMENT(JSBool) js_convertJSValueToJObject(HObject **objp, JSContext *cx, jsval v, char *sig, ClassClass *fromclass, JSBool checkOnly, int *cost) { /* FIXME bump the cost counter when necessary */ ClassClass *paramcb; paramcb = sig ? getSignatureClass(cx, sig, fromclass) : ObjectClassBlock; PR_LOG(MojaSrc, debug, ("desired argument class signature \"%s\"(0x%x)", sig, paramcb)); if (!paramcb) { PR_LOG(MojaSrc, warn, ("couldn't find class for signature \"%s\"", sig)); return JS_FALSE; } /* JS wrappers around java objects do the same check * as the java compiler */ /* FIXME except classes, which become JSObject (circular problem?) */ if (JSVAL_IS_OBJECT(v)) { JSObject *mo = JSVAL_TO_OBJECT(v); /* null is always a valid object */ if (!mo) { if (!checkOnly) *objp = 0; return JS_TRUE; } if (JS_TypeOfValue(cx, v) == JSTYPE_FUNCTION) { if (js_convertJSValueToJSObject(cx, mo, paramcb, checkOnly, objp)) return JS_TRUE; } else if (JS_InstanceOf(cx, mo, &java_class, 0) || JS_InstanceOf(cx, mo, &javaarray_class, 0)) { JSJava *java = JS_GetPrivate(cx, mo); HObject *ho = java->handle; ClassClass *cb; /* class reflections convert to JSObject or String only */ if (java->type == JAVA_CLASS) { if (js_convertJSValueToJSObject(cx, mo, paramcb, checkOnly, objp)) { return JS_TRUE; } } else { /* get the classblock for the java argument type */ cb = obj_array_classblock(ho); PR_LOG(MojaSrc, debug, ("actual argument 0x%x, class 0x%x", ho, cb)); /* check against the expected class */ if (js_isSubclassOf(cx, cb, paramcb)) { if (!checkOnly) *objp = ho; return JS_TRUE; } } } else { /* otherwise see if it will take a JSObject */ if (js_convertJSValueToJSObject(cx, mo, paramcb, checkOnly, objp)) return JS_TRUE; } } else if (JSVAL_IS_NUMBER(v)) { /* java.lang.Double */ if (js_isSubclassOf(cx, DoubleClassBlock, paramcb)) { /* Float is ok */ if (!checkOnly) *objp = js_ConstructJava(cx, "java/lang/Double", 0, "(D)", JSVAL_IS_INT(v) ? (double) JSVAL_TO_INT(v) : *JSVAL_TO_DOUBLE(v)); return JS_TRUE; } } else if (JSVAL_IS_BOOLEAN(v)) { /* java.lang.Boolean */ /* FIXME this should return Boolean.JS_TRUE or Boolean.FALSE * instead of constructing a new one? */ if (js_isSubclassOf(cx, BooleanClassBlock, paramcb)) { if (!checkOnly) *objp = js_ConstructJava(cx, "java/lang/Boolean", 0, "(Z)", (long) JSVAL_TO_BOOLEAN(v)); return JS_TRUE; } } /* last ditch attempt: is a String acceptable? */ if (js_isSubclassOf(cx, StringClassBlock, paramcb)) { JSString *str; /* string is ok, convert to one */ str = JS_ValueToString(cx, v); if (str) { /* make a java String from str */ if (!checkOnly) { ExecEnv *ee = jsj_GetCurrentEE(cx); if (!ee) return JS_FALSE; *objp = (JHandle *) JRI_NewStringPlatform((JRIEnv *) ee, (const jbyte *) JS_GetStringBytes(str), (jint) JS_GetStringLength(str), (const jbyte *) cx->charSetName, (jint) cx->charSetNameLength); } return JS_TRUE; } } return JS_FALSE; } PR_IMPLEMENT(JSBool) js_convertJObjectToJSValue(JSContext *cx, jsval *vp, HObject *ho) { ExecEnv *ee; JRIEnv *env; JSObject *mo; ee = jsj_GetCurrentEE(cx); if (!ee) return JS_FALSE; env = (JRIEnv *) ee; if (!ho) { *vp = OBJECT_TO_JSVAL(0); return JS_TRUE; } /* * If it's a JSObject, pull out the original JS object when * it comes back into the JS world. */ if (obj_array_classblock(ho) == JSObjectClassBlock) { jref jso = (jref) ho /* FIXME */; *vp = OBJECT_TO_JSVAL( (JSObject *) JRI_GetFieldInt(env, jso, JSObjectInternalField)); return JS_TRUE; } /* * Instances of java.lang.String are wrapped so we can * call methods on them, but they convert to a JS string * if used in a string context. */ /* otherwise, wrap it */ mo = js_ReflectJObjectToJSObject(cx, ho); if (!mo) { JS_ReportError(cx, "can't convert Java object to JavaScript object"); return JS_FALSE; } *vp = OBJECT_TO_JSVAL(mo); return JS_TRUE; } #define REFLECTION_IN_PROGRESS ((jref)1) PR_IMPLEMENT(HObject *) js_ReflectJSObjectToJObject(JSContext *cx, JSObject *mo) { ExecEnv *ee; JRIEnv *env; jref jso; PRHashEntry *he; ee = jsj_GetCurrentEE(cx); if (!ee) return 0; env = (JRIEnv *) ee; /* see if it's already there */ PR_EnterMonitor(jsReflectionsMonitor); while ((jso = PR_HashTableLookup(jsReflections, mo)) != 0) { if (jso != REFLECTION_IN_PROGRESS) break; PR_Wait(jsReflectionsMonitor, WAIT_FOREVER); } if (jso) { PR_ExitMonitor(jsReflectionsMonitor); return (HObject *) jso /*FIXME*/; } /* * Release the monitor temporarily while we call the java constructor, * which could contain arbitrary scariness. Put a placeholder in the * hash table so anyone racing to reflect mo waits for us to finish. */ PR_HashTableAdd(jsReflections, mo, REFLECTION_IN_PROGRESS); PR_ExitMonitor(jsReflectionsMonitor); /* not found, reflect it */ jso = (jref) /*FIXME*/ js_ConstructJavaPrivileged(cx, 0, JSObjectClassBlock, "()"); /* FIXME check for exceptions */ if (!jso) { return 0; } /* need to check again since we released the monitor */ PR_EnterMonitor(jsReflectionsMonitor); #ifdef DEBUG { jref tmp = PR_HashTableLookup(jsReflections, mo); PR_ASSERT(!tmp || tmp == REFLECTION_IN_PROGRESS); if (tmp && tmp != REFLECTION_IN_PROGRESS) { PR_ExitMonitor(jsReflectionsMonitor); /* let the one we constructed die in gc and use the one we found */ return (HObject *) tmp; } } #endif JRI_SetFieldInt(env, jso, JSObjectInternalField, (jint) mo); /* add it to the table */ he = PR_HashTableAdd(jsReflections, mo, jso); if (he) (void) JS_AddRoot(cx, (void *)&he->key); /* FIXME be errors? */ /* awaken anyone racing and exit monitor */ PR_NotifyAll(jsReflectionsMonitor); PR_ExitMonitor(jsReflectionsMonitor); return (HObject *) jso;/*FIXME*/ } PR_IMPLEMENT(void) js_RemoveReflection(JSContext *cx, JSObject *mo) { PRHashEntry *he, **hep; /* remove it from the reflection table */ PR_LOG(MojaSrc, debug, ("removing JS object 0x%x from table", mo)); PR_EnterMonitor(jsReflectionsMonitor); hep = PR_HashTableRawLookup(jsReflections, java_hashHandle(mo), mo); he = *hep; if (he) { /* FIXME inline JS_RemoveRoot -- this will go away with GC unification */ JS_LOCK_RUNTIME(finalizeRuntime); (void) PR_HashTableRemove(finalizeRuntime->gcRootsHash, (void *)&he->key); JS_UNLOCK_RUNTIME(finalizeRuntime); } PR_HashTableRawRemove(jsReflections, hep, he); PR_ExitMonitor(jsReflectionsMonitor); } static JSBool js_convertJSValueToJValue(JSContext *cx, jsval v, OBJECT *addr, char *sig, ClassClass *fromclass, JSBool checkOnly, char **sigRestPtr, int *cost) { Java8 tdub; char *p = sig; switch (*p) { case SIGNATURE_BOOLEAN: if (!JSVAL_IS_BOOLEAN(v)) { /* FIXME we could convert other things to boolean too, * but until cost checking is done this will cause us * to choose the wrong method if there is method overloading */ #ifndef USE_COSTS return JS_FALSE; #else if (!JS_ConvertValue(cx, v, JSTYPE_BOOLEAN, &v)) return JS_FALSE; (*cost)++; #endif } if (!checkOnly) *(long*)addr = (JSVAL_TO_BOOLEAN(v) == JS_TRUE); break; case SIGNATURE_SHORT: case SIGNATURE_BYTE: case SIGNATURE_CHAR: case SIGNATURE_INT: /* FIXME should really do a range check... */ if (!JSVAL_IS_NUMBER(v)) { #ifndef USE_COSTS return JS_FALSE; #else if (!JS_ConvertValue(cx, v, JSTYPE_NUMBER, &v)) return JS_FALSE; (*cost)++; #endif } if (!checkOnly) { if (JSVAL_IS_INT(v)) *(long*)addr = (long) JSVAL_TO_INT(v); else *(long*)addr = (long) *JSVAL_TO_DOUBLE(v); } break; case SIGNATURE_LONG: if (!JSVAL_IS_NUMBER(v)) { #ifndef USE_COSTS return JS_FALSE; #else if (!JS_ConvertValue(cx, v, JSTYPE_NUMBER, &v)) return JS_FALSE; (*cost)++; #endif } if (!checkOnly) { PRInt64 ll; if (JSVAL_IS_INT(v)) { long l = (long) JSVAL_TO_INT(v); LL_I2L(ll, l); } else { double d = (double) *JSVAL_TO_DOUBLE(v); LL_D2L(ll, d); } SET_INT64(tdub, addr, ll); } break; case SIGNATURE_FLOAT: if (!JSVAL_IS_NUMBER(v)) { #ifndef USE_COSTS return JS_FALSE; #else if (!JS_ConvertValue(cx, v, JSTYPE_NUMBER, &v)) return JS_FALSE; (*cost)++; #endif } if (!checkOnly) { if (JSVAL_IS_INT(v)) *(float*)addr = (float) JSVAL_TO_INT(v); else *(float*)addr = (float) *JSVAL_TO_DOUBLE(v); } break; case SIGNATURE_DOUBLE: if (!JSVAL_IS_NUMBER(v)) { #ifndef USE_COSTS return JS_FALSE; #else if (!JS_ConvertValue(cx, v, JSTYPE_NUMBER, &v)) return JS_FALSE; (*cost)++; #endif } if (!checkOnly) { double d; if (JSVAL_IS_INT(v)) d = (double) JSVAL_TO_INT(v); else d = (double) *JSVAL_TO_DOUBLE(v); SET_DOUBLE(tdub, addr, d); } break; case SIGNATURE_CLASS: /* FIXME cost? */ if (!js_convertJSValueToJObject((HObject **)/*FIXME*/addr, cx, v, p, fromclass, checkOnly, cost)) { return JS_FALSE; } while (*p != SIGNATURE_ENDCLASS) p++; break; case SIGNATURE_ARRAY: /* FIXME cost? */ if (!js_convertJSValueToJArray((HObject **)/*FIXME*/addr, cx, v, p, fromclass, checkOnly, cost)) { return JS_FALSE; } while (*p == SIGNATURE_ARRAY) p++; /* skip array beginning */ /* skip the element type */ if (*p == SIGNATURE_CLASS) { while (*p != SIGNATURE_ENDCLASS) p++; } break; default: PR_LOG(MojaSrc, warn, ("unknown value signature '%s'", p)); return JS_FALSE; } if (sigRestPtr) *sigRestPtr = p+1; return JS_TRUE; } /* * this code mimics the method overloading resolution * done in java, for use in JS->java calls */ /* push a jsval array onto the java stack for use by * the given method. * sigRest gets a pointer to the remainder of the signature (the * rest of the arguments in the list). * if checkOnly is true, don't actually convert, just check that it's ok. */ static JSBool js_convertJSToJArgs(JSContext *cx, stack_item *optop, struct methodblock *mb, int argc, jsval *argv, JSBool checkOnly, char **sigRestPtr, int *cost) { struct fieldblock *fb = &mb->fb; char *sig = fieldsig(fb); jsval *vp = argv; int argsleft = argc; char *p; void *addr; *cost = 0; for (p = sig + 1; *p != SIGNATURE_ENDFUNC; vp++, argsleft--) { if (argsleft == 0) /* not enough arguments passed */ return JS_FALSE; if (checkOnly) addr = 0; else switch (*p) { case SIGNATURE_BOOLEAN: case SIGNATURE_SHORT: case SIGNATURE_BYTE: case SIGNATURE_CHAR: case SIGNATURE_INT: addr = &(optop++)->i; break; case SIGNATURE_LONG: addr = optop; optop += 2; break; case SIGNATURE_FLOAT: addr = &(optop++)->f; break; case SIGNATURE_DOUBLE: addr = optop; optop += 2; break; case SIGNATURE_CLASS: case SIGNATURE_ARRAY: addr = &(optop++)->h; break; default: PR_LOG(MojaSrc, warn, ("Invalid method signature '%s' for method '%s'\n", fieldsig(fb), fieldname(fb))); return JS_FALSE; break; } /* this bumps p to the next argument */ if (!js_convertJSValueToJValue(cx, *vp, addr, p, fieldclass(&mb->fb), checkOnly, &p, cost)) { return JS_FALSE; } } if (argsleft > 0) { /* too many arguments */ return JS_FALSE; } if (sigRestPtr) *sigRestPtr = p+1 /* go past the SIGNATURE_ENDFUNC */; return JS_TRUE; } /* java array elements are packed with smaller sizes */ static JSBool js_convertJSValueToJElement(JSContext *cx, jsval v, char *addr, char *sig, ClassClass *fromclass, char **sigRestPtr) { long tmp[2]; int cost = 0; if (!js_convertJSValueToJValue(cx, v, (OBJECT *)&tmp, sig, fromclass, JS_FALSE, sigRestPtr, &cost)) return JS_FALSE; switch (sig[0]) { case SIGNATURE_BOOLEAN: *(char*)addr = (char)(*(long*)&tmp); break; case SIGNATURE_BYTE: *(char*)addr = (char)(*(long*)&tmp); break; case SIGNATURE_CHAR: *(unicode*)addr = (unicode)(*(long*)&tmp); break; case SIGNATURE_SHORT: *(signed short*)addr = (signed short)(*(long*)&tmp); break; case SIGNATURE_INT: *(PRInt32*)addr = *(long*)&tmp; break; case SIGNATURE_LONG: *(PRInt64*)addr = *(PRInt64*)&tmp; break; case SIGNATURE_FLOAT: *(float*)addr = *(float*)&tmp; break; case SIGNATURE_DOUBLE: *(double*)addr = *(double*)&tmp; break; case SIGNATURE_CLASS: case SIGNATURE_ARRAY: *(HObject**)addr = *(HObject**)&tmp; break; default: PR_LOG(MojaSrc, warn, ("unknown value signature '%s'\n", sig[0])); return JS_FALSE; } return JS_TRUE; } static OBJECT * getJavaFieldAddress(HObject *ho, struct fieldblock *fb) { OBJECT *addr = 0; /* address of the java value */ if (fb->access & ACC_PUBLIC) { if (fb->access & ACC_STATIC) { char *sig = fieldsig(fb); if (sig[0] == SIGNATURE_LONG || sig[0] == SIGNATURE_DOUBLE) addr = (long *)twoword_static_address(fb); else addr = (long *)normal_static_address(fb); } else { addr = &obj_getoffset(ho, fb->u.offset); } } return addr; } static JSBool js_convertJSValueToJField(JSContext *cx, jsval v, HObject *ho, struct fieldblock *fb) { OBJECT *addr = getJavaFieldAddress(ho, fb); int cost = 0; if (!addr) { JS_ReportError(cx, "can't access field %s", fieldname(fb)); return JS_FALSE; } return js_convertJSValueToJValue(cx, v, addr, fieldsig(fb), fieldclass(fb), JS_FALSE, 0, &cost); } /* * Returns -1 if the given method is not applicable to the arguments, * or a cost if it is. * if argc == -1, match iff the name matches and don't check the * signature. this always returns cost 1. */ static int methodIsApplicable(JSContext *cx, struct methodblock *mb, JSBool isStatic, const char *name, int argc, jsval *argv) { struct fieldblock *fb = &mb->fb; int cost = 0; /* name and access must match */ if (!CHECK_STATIC(isStatic, fb) || strcmp(fieldname(fb), name)) return -1; if (argc == -1) return 1; if (!js_convertJSToJArgs(cx, 0, mb, argc, argv, JS_TRUE /* checkOnly */, 0 /* &sigRest */, &cost)) { return -1; } return cost; } /* FIXME this routine doesn't work yet - its purpose is to choose * the best method when java methods are overloaded, or to detect * ambiguous method calls */ static JSBool methodIsMoreSpecific(JSContext *cx, struct methodblock *mb1, struct methodblock *mb2) { char *sig1 = fieldsig(&mb1->fb) + 1; /* skip '(' */ char *sig2 = fieldsig(&mb2->fb) + 1; /* FIXME fill this in! */ return JS_TRUE; /* go through the args */ while (*sig1 != SIGNATURE_ENDFUNC && *sig2 != SIGNATURE_ENDFUNC) { if (*sig1 == SIGNATURE_CLASS) { if (*sig2 == SIGNATURE_CLASS) { ClassClass *cb1 = getSignatureClass(cx, sig1, fieldclass(&mb1->fb)); ClassClass *cb2 = getSignatureClass(cx, sig2, fieldclass(&mb2->fb)); if (! js_isSubclassOf(cx, cb1, cb2)) return JS_FALSE; /* next argument */ while (*sig1++ != SIGNATURE_ENDCLASS); while (*sig2++ != SIGNATURE_ENDCLASS); } } } if (*sig1 != *sig2) { return JS_FALSE; /* arg number mismatch */ } return JS_TRUE; } /* based on sun.tools.java.Environment */ /** * Return true if an implicit cast from this type to * the given type is allowed. */ static JSBool js_convertJValueToJSValue(JSContext *cx, OBJECT *addr, char *sig, jsval *vp, JSType desired) { Java8 tmp; switch (sig[0]) { case SIGNATURE_VOID: *vp = JSVAL_VOID; break; case SIGNATURE_BYTE: case SIGNATURE_CHAR: case SIGNATURE_SHORT: *vp = INT_TO_JSVAL((jsint) *addr); break; case SIGNATURE_INT: { PRInt32 val = (PRInt32) *addr; if (INT_FITS_IN_JSVAL(val)) { *vp = INT_TO_JSVAL((jsint) *addr); } else { if (!JS_NewDoubleValue(cx, val, vp)) return JS_FALSE; } } break; case SIGNATURE_BOOLEAN: *vp = BOOLEAN_TO_JSVAL((JSBool) *addr); break; case SIGNATURE_LONG: { PRInt64 ll; double d; ll = GET_INT64(tmp, addr); LL_L2D(d, ll); if (!JS_NewDoubleValue(cx, d, vp)) return JS_FALSE; } break; case SIGNATURE_FLOAT: if (!JS_NewDoubleValue(cx, *(float *)addr, vp)) return JS_FALSE; break; case SIGNATURE_DOUBLE: if (!JS_NewDoubleValue(cx, GET_DOUBLE(tmp, addr), vp)) return JS_FALSE; break; case SIGNATURE_CLASS: case SIGNATURE_ARRAY: return js_convertJObjectToJSValue(cx, vp, *(HObject **) addr); default: JS_ReportError(cx, "unknown Java signature character '%c'", sig[0]); return JS_FALSE; } if (desired != JSTYPE_VOID) return JS_ConvertValue(cx, *vp, desired, vp); return JS_TRUE; } /* java array elements are packed with smaller sizes */ static JSBool js_convertJElementToJSValue(JSContext *cx, char *addr, char *sig, jsval *vp, JSType desired) { switch (sig[0]) { case SIGNATURE_BYTE: *vp = INT_TO_JSVAL(*(char*)addr); break; case SIGNATURE_CHAR: *vp = INT_TO_JSVAL(*(unicode*)addr); break; case SIGNATURE_SHORT: *vp = INT_TO_JSVAL(*(signed short*)addr); break; case SIGNATURE_INT: { PRInt32 val = *(PRInt32*)addr; if (INT_FITS_IN_JSVAL(val)) { *vp = INT_TO_JSVAL((jsint)val); } else { if (!JS_NewDoubleValue(cx, val, vp)) return JS_FALSE; } } break; case SIGNATURE_BOOLEAN: *vp = BOOLEAN_TO_JSVAL((JSBool) *(PRInt32*)addr); break; default: return js_convertJValueToJSValue(cx, (OBJECT *) addr, sig, vp, desired); } if (desired != JSTYPE_VOID) return JS_ConvertValue(cx, *vp, desired, vp); return JS_TRUE; } static JSBool js_convertJFieldToJSValue(JSContext *cx, HObject *ho, struct fieldblock *fb, jsval *vp, JSType desired) { OBJECT *addr = getJavaFieldAddress(ho, fb); char *sig = fieldsig(fb); if (!addr) { JS_ReportError(cx, "can't access Java field %s", fieldname(fb)); return JS_FALSE; } return js_convertJValueToJSValue(cx, addr, sig, vp, desired); } static JSBool js_convertJObjectToJSString(JSContext *cx, HObject *ho, JSBool isClass, jsval *vp) { JSString *str; char *cstr; if (isClass) { cstr = PR_smprintf("[JavaClass %s]", cbName((ClassClass*)ho)); } else { HString *hstr; ExecEnv *ee; if (!ho) return JS_FALSE; if (obj_classblock(ho) == StringClassBlock) { /* it's a string already */ hstr = (HString*) ho; } else { /* call toString() to convert to a string */ if (!js_ExecuteJavaMethod(cx, &hstr, sizeof(hstr), ho, "toString", "()Ljava/lang/String;", 0, JS_FALSE)) { return JS_FALSE; } } /* FIXME js_ExecuteJavaMethod does this too */ ee = jsj_GetCurrentEE(cx); if (!ee) return JS_FALSE; /* convert the java string to a JS string */ cstr = (char *) JRI_GetStringPlatformChars((JRIEnv *) ee, (struct java_lang_String *) hstr, (const jbyte *) cx->charSetName, (jint) cx->charSetNameLength); if (cstr) cstr = strdup(cstr); } if (!cstr) { JS_ReportOutOfMemory(cx); return JS_FALSE; } str = JS_NewString(cx, cstr, strlen(cstr)); if (!str) { free(cstr); return JS_FALSE; } *vp = STRING_TO_JSVAL(str); return JS_TRUE; } static JSBool js_convertJObjectToJSNumber(JSContext *cx, HObject *ho, JSBool isClass, jsval *vp) { long foo[2], swap; /* FIXME JRI and JDK disagree */ JRI_JDK_Java8 tmp; double d; if (isClass || !ho) { JS_ReportError(cx, "can't convert Java object to number"); return JS_FALSE; } if (!js_ExecuteJavaMethod(cx, foo, sizeof(foo), ho, "doubleValue", "()D", 0, JS_FALSE)) return JS_FALSE; swap = foo[0]; foo[0] = foo[1]; foo[1] = swap; d = JRI_GET_DOUBLE(tmp, &foo[0]); return JS_NewDoubleValue(cx, d, vp); } static JSBool js_convertJObjectToJSBoolean(JSContext *cx, HObject *ho, JSBool isClass, jsval *vp) { long b; if (isClass) { b = JS_TRUE; goto ok; } if (!ho) { b = JS_FALSE; goto ok; } if (!js_ExecuteJavaMethod(cx, &b, sizeof(b), ho, "booleanValue", "()Z", 0, JS_FALSE)) { /* failed, probably missing the booleanValue() method */ /* by default we convert to true */ b = JS_TRUE; } ok: *vp = BOOLEAN_TO_JSVAL(b); return JS_TRUE; } static JSBool java_returnAsJSValue(JSContext *cx, jsval *vp, ExecEnv *ee, char *sig) { OBJECT *addr; if ((sig[0] == SIGNATURE_DOUBLE || sig[0] == SIGNATURE_LONG)) { addr = (OBJECT*) &ee->current_frame->optop[-2]; } else { addr = (OBJECT *) &ee->current_frame->optop[-1]; } return js_convertJValueToJSValue(cx, addr, sig, vp, JSTYPE_VOID); } static char* js_createArgTypeString(JSContext* cx, int argc, jsval* argv) { int i; int size = 0; char *p; char** strs; char *str; if (argc < 1) { str = strdup(""); return str; } strs = (char**) malloc(argc * sizeof(char*)); if (!strs) return 0; for (i = 0; i < argc; i++) { JSType t = JS_TypeOfValue(cx, argv[i]); switch (t) { case JSTYPE_VOID: strs[i] = "void"; break; case JSTYPE_OBJECT: if (JSVAL_IS_NULL(argv[i])) strs[i] = "null"; else strs[i] = JS_GetClass(JSVAL_TO_OBJECT(argv[i]))->name; break; case JSTYPE_FUNCTION: strs[i] = "function"; break; case JSTYPE_STRING: strs[i] = "string"; break; case JSTYPE_NUMBER: strs[i] = "number"; break; case JSTYPE_BOOLEAN: strs[i] = "boolean"; break; default: PR_ASSERT(!"bad arg type"); } size += strlen(strs[i]) + 2; /* +2 for ", " */ } str = (char*) malloc(size - 1); /* -2 for ", " & +1 for '\0' */ if (!str) goto out; strcpy(str, strs[0]); p = str + strlen(strs[0]); for (i = 1; i < argc; i++) { *p++ = ','; *p++ = ' '; strcpy(p, strs[i]); p += strlen(strs[i]); } *p = '\0'; out: free(strs); return str; } /* * find the best method to call for a name and a bunch of JS * arguments. try each possible method, then find the most * specific of the applicable methods. "most specific" is * determined without reference to the JS arguments: it is * the same as "most specific" in the java sense of the word. * FIXME this could cause trouble since more methods will be applicable * to a JS call than for a similar java call! * pass argc == -1 to match any method with the correct name */ static struct methodblock * matchMethod(JSContext *cx, ClassClass *clazz, JSBool isStatic, const char *name, int argc, jsval *argv, JSBool reportErrors) { int mindex; int *isApplicable = 0; /* array holding costs */ struct methodblock *bestmb = 0; JSBool isOverloaded = JS_FALSE; ClassClass *cb = clazz; while (cb) { /* * Find all applicable methods. keep track of which ones are * applicable using the isApplicable array, and the best one * found so far in bestmb. set isOverloaded if there is more * than one applicable method. */ isApplicable = JS_malloc(cx, cbMethodsCount(cb) * sizeof(int)); if (!isApplicable) return 0; for (mindex = cbMethodsCount(cb); mindex--;) { struct methodblock *mb = cbMethods(cb) + mindex; struct fieldblock *fb = &mb->fb; isApplicable[mindex] = methodIsApplicable(cx, mb, isStatic, name, argc, argv); if (isApplicable[mindex] == -1) continue; PR_LOG(MojaSrc, debug, ("found applicable method %s with sig %s", fieldname(fb), fieldsig(fb))); if (!bestmb) { /* first one found */ bestmb = mb; continue; } isOverloaded = JS_TRUE;; if (methodIsMoreSpecific(cx, mb, bestmb)) { bestmb = mb; } } /* if we've found something applicable in the current class, * no need to go any further */ /* this is the only exit from the loop in which isApplicable * is live */ if (bestmb) break; /* otherwise, check the parent */ if (cbSuperclass(cb)) cb = cbSuperclass(cb); else cb = 0; JS_free(cx, isApplicable); isApplicable = 0; } /* second pass: check that bestmb is more specific than all other * applicable methods */ /* FIXME if mb is equally specific, this is an ambiguous lookup. Hopefully we can disambiguate by the cost */ /* if (isOverloaded) for (mindex = cb->methods_count; mindex--;) { struct methodblock *mb = cbMethods(cb) + mindex; struct fieldblock *fb = &mb->fb; if (!isApplicable[mindex] || mb == bestmb) continue; } */ if (isApplicable) JS_free(cx, isApplicable); if (bestmb && (bestmb->fb.access & ACC_PUBLIC)) return bestmb; else if (reportErrors) { char buf[512]; char *argstr = js_createArgTypeString(cx, argc, argv); char *methodtype = "method"; if (0 == strcmp(name, "")) { methodtype = "constructor"; } if (!bestmb) { if (argstr) { PR_snprintf(buf, sizeof(buf), "no Java %s %s.%s matching JavaScript arguments (%s)", methodtype, cbName(clazz), name, argstr); free(argstr); } else { PR_snprintf(buf, sizeof(buf), "no Java %s %s.%s matching JavaScript arguments", methodtype, cbName(clazz), name); } } else { if (argstr) { PR_snprintf(buf, sizeof(buf), "Java %s %s.%s is not public (%s)", methodtype, cbName(clazz), name, argstr); free(argstr); } else { PR_snprintf(buf, sizeof(buf), "Java %s %s.%s is not public", methodtype, cbName(clazz), name); } } JS_ReportError(cx, buf); } return NULL; } /* this is the callback to execute java bytecodes in the * appropriate java env */ typedef struct { JRIEnv *env; char *pc; JSBool ok; } js_ExecuteJava_data; static void js_ExecuteJava_stub(void *d) { js_ExecuteJava_data *data = d; PR_EXTERN(bool_t) ExecuteJava(unsigned char *, ExecEnv *ee); data->ok = (JSBool) ExecuteJava((unsigned char*) data->pc, /*FIXME*/(ExecEnv*)data->env); } /* this is a copy of do_execute_java_method_vararg modified for * a jsval* argument list instead of a va_list */ static JSBool do_js_execute_java_method(JSContext *cx, void *obj, struct methodblock *mb, JSBool isStaticCall, int argc, jsval *argv, jsval *rval) { ExecEnv *ee = jsj_GetCurrentEE(cx); char *method_name; char *method_signature; JavaFrame *current_frame, *previous_frame; JavaStack *current_stack; char *sigRest; int cost = 0; stack_item *firstarg; JSBool success = JS_TRUE; JSJClassData *classData; unsigned char pc[6]; cp_item_type constant_pool[10]; unsigned char cpt[10]; JSBool ok; if (!ee) return JS_FALSE; /* push the safety frame before the call frame */ if (!js_pushSafeFrame(cx, ee, &classData)) return JS_FALSE; method_name = fieldname(&mb->fb); method_signature = fieldsig(&mb->fb); previous_frame = ee->current_frame; if (previous_frame == 0) { /* bottommost frame on this Exec Env. */ current_stack = ee->initial_stack; current_frame = (JavaFrame *)(current_stack->data); /* no vars */ } else { int args_size = mb->args_size; current_stack = previous_frame->javastack; /* assume same stack */ if (previous_frame->current_method) { int size = previous_frame->current_method->maxstack; current_frame = (JavaFrame *)(&previous_frame->ostack[size]); } else { /* The only frames that don't have a mb are pseudo frames like * this one and they don't really touch their stack. */ current_frame = (JavaFrame *)(previous_frame->optop + 3); } if (current_frame->ostack + args_size > current_stack->end_data) { /* Ooops. The current stack isn't big enough. */ if (current_stack->next != 0) { current_stack = current_stack->next; } else { current_stack = CreateNewJavaStack(ee, current_stack); if (current_stack == 0) { JS_ReportOutOfMemory(cx); success = JS_FALSE; goto done; } } /* no vars */ current_frame = (JavaFrame *)(current_stack->data); } } current_frame->prev = previous_frame; current_frame->javastack = current_stack; current_frame->optop = current_frame->ostack; current_frame->vars = 0; /* better not reference any! */ current_frame->constant_pool = 0; /* Until we set it up */ current_frame->monitor = 0; /* not monitoring anything */ current_frame->annotation = 0; current_frame->current_method = 0; /* * Allocate space for all the operands before they are actually * converted, because conversion may need to use this stack. */ firstarg = current_frame->optop; current_frame->optop += mb->args_size; ee->current_frame = current_frame; /* Push the target object, if not a static call. */ if (!isStaticCall) (firstarg++)->p = obj; /* Now convert the args into the space on the stack. */ if (!js_convertJSToJArgs(cx, firstarg, mb, argc, argv, JS_FALSE /* actually convert */, &sigRest, &cost)) { /* the method shouldn't have matched if this was going to happen! */ JS_ReportError(cx, "internal error: argument conversion failed"); success = JS_FALSE; goto done; } /* build the bytecodes and constant table for the call */ constant_pool[CONSTANT_POOL_TYPE_TABLE_INDEX].p = cpt; cpt[0] = CONSTANT_POOL_ENTRY_RESOLVED; pc[0] = isStaticCall ? opc_invokestatic_quick : opc_invokenonvirtual_quick; pc[1] = 0; pc[2] = 1; /* constant pool entry #1 */ pc[3] = opc_return; constant_pool[1].p = mb; cpt[1] = CONSTANT_POOL_ENTRY_RESOLVED | CONSTANT_Methodref; current_frame->constant_pool = constant_pool; /* Run the byte codes in java-land catch any exceptions. */ ee->exceptionKind = EXCKIND_NONE; { js_ExecuteJava_data data; data.pc = (char*) pc; js_CallJava(cx, js_ExecuteJava_stub, &data, JS_FALSE /* we pushed the safety frame already */); ok = data.ok; } if (ok) { JSBool ret; PR_LOG(MojaSrc, debug, ("method call succeeded\n")); ret = java_returnAsJSValue(cx, rval, ee, sigRest); if (!ret) { JS_ReportError(cx, "can't convert Java return value with signature %s", sigRest); success = JS_FALSE; } } else { success = JS_FALSE; } done: /* Our caller can look at ee->exceptionKind and ee->exception. */ ee->current_frame = previous_frame; /* pop the safety frame */ js_popSafeFrame(cx, ee, classData); /* FIXMEold cx->javaEnv = saved; */ return success; } static JSBool js_javaMethodWrapper(JSContext *cx, JSObject *obj, PRUintn argc, jsval *argv, jsval *rval) { JSFunction *fun; const char *name; JSJava *java; ClassClass *cb; struct methodblock *realmb; JSBool success; PR_ASSERT(JS_TypeOfValue(cx, argv[-2]) == JSTYPE_FUNCTION); if (!JS_InstanceOf(cx, obj, &java_class, argv)) return JS_FALSE; fun = JS_GetPrivate(cx, JSVAL_TO_OBJECT(argv[-2])); name = JS_GetFunctionName(fun); java = JS_GetPrivate(cx, obj); cb = java->cb; PR_LOG(MojaSrc, debug, ("entered methodwrap, fun=0x%x, name=\"%s\"(0x%x)", fun, name, name)); /* match argc,argv against the signatures of the java methods */ realmb = matchMethod(cx, cb, java->type==JAVA_CLASS, name, argc, argv, JS_TRUE /* reportErrors */); if (!realmb) { return JS_FALSE; } PR_LOG(MojaSrc, debug, ("calling %sjava method %s with signature %s", realmb->fb.access & ACC_STATIC ? "static " : "", realmb->fb.name, realmb->fb.signature)); success = do_js_execute_java_method(cx, realmb->fb.access & ACC_STATIC ? 0 : java->handle, realmb, realmb->fb.access & ACC_STATIC /* isStaticCall */, argc, argv, rval); return success; } static JSBool js_javaConstructorWrapper(JSContext *cx, JSObject *obj, PRUintn argc, jsval *argv, jsval *rval) { JSObject *funobj; ClassClass *cb; struct methodblock *realmb; HObject *ho; JSBool success; jsval tmp; ExecEnv *ee; PR_LOG(MojaSrc, debug, ("entered java constructor wrapper\n")); PR_ASSERT(JS_TypeOfValue(cx, argv[-2]) == JSTYPE_FUNCTION); funobj = JSVAL_TO_OBJECT(argv[-2]); if (!JS_GetProperty(cx, funobj, "#javaClass", &tmp)) return JS_FALSE; cb = JSVAL_TO_PRIVATE(tmp); if (!JS_InstanceOf(cx, obj, &java_class, 0) || JS_GetPrivate(cx, obj)) { /* not a fresh object! */ /* 3.0 allowed you to call a java constructor without using * "new" so we do the same. setting obj to null causes * ReflectJava to create a new js object rather than using * the "this" argument of the constructor */ obj = NULL; } /* FIXME these are copied from interpreter.c without much understanding */ if (cbAccess(cb) & (ACC_INTERFACE | ACC_ABSTRACT)) { JS_ReportError(cx, "can't instantiate Java class"); return JS_FALSE; } if (!VerifyClassAccess(0, cb, FALSE)) { JS_ReportError(cx, "can't access Java class"); return JS_FALSE; } /* match argc,argv against the signatures of the java constructors */ realmb = matchMethod(cx, cb, JS_FALSE, "", argc, argv, JS_TRUE /* reportErrors */); if (!realmb) { return JS_FALSE; } if (!VerifyFieldAccess(0, fieldclass(&realmb->fb), realmb->fb.access, FALSE)) { JS_ReportError(cx, "illegal access to Java constructor"); return JS_FALSE; } PR_LOG(MojaSrc, debug, ("calling java constructor with signature %s", realmb->fb.signature)); /* * Because newobject can fail, and call SignalError, which calls * FindClassFromClass, etc. */ /* Allocate the object */ ee = jsj_GetCurrentEE(cx); if (!ee) return JS_FALSE; if ((ho = newobject(cb, 0, ee)) == 0) { JS_ReportOutOfMemory(cx); return JS_FALSE; } success = do_js_execute_java_method(cx, ho, realmb, JS_FALSE /* isStaticCall */, argc, argv, &tmp /* gets set to void */); /* FIXME the interpreter.c code calls cbDecRef(cb) at the end, * why? should we? */ if (success) { JSObject *jso = js_ReflectJava(cx, JAVA_OBJECT, ho, cb, 0, obj); if (jso) { *rval = OBJECT_TO_JSVAL(jso); return JS_TRUE; } } return JS_FALSE; } /* * * * * * * * * * * * * * * * * * * * * JS/java reflection tables * * * * * * * * * * * * * * * * * * * */ typedef struct ScanArgs { GCInfo *gcInfo; } ScanArgs; PR_STATIC_CALLBACK (int) scanJSJavaReflectionEntry(PRHashEntry *he, int i, void *arg) { ScanArgs *args = arg; JSObject *jso = he->value; /* scan the handle */ if (1 /* jso != JSO_REFLECTION_IN_PROGRESS */) { JSJava *java = JSVAL_TO_PRIVATE(OBJ_GET_SLOT(jso, JSSLOT_PRIVATE)); args->gcInfo->livePointer((void *)java->handle); } return HT_ENUMERATE_NEXT; } PR_STATIC_CALLBACK (void) scanJSJavaReflections(void *runtime) { ScanArgs args; /* PR_PROCESS_ROOT_LOG(("Scanning JS reflections of java objects")); */ args.gcInfo = PR_GetGCInfo(); /* FIXME this is kind of scary long-term access inside the * monitor - is there any alternative? */ #ifndef NSPR20 PR_EnterMonitor(javaReflectionsMonitor); #endif if (javaReflections) { PR_HashTableEnumerateEntries(javaReflections, scanJSJavaReflectionEntry, &args); } #ifndef NSPR20 PR_ExitMonitor(javaReflectionsMonitor); #endif } #ifdef NSPR20 void PR_CALLBACK PrepareJSLocksForGC(GCLockHookArg arg1) { PR_ASSERT(arg1 == PR_GCBEGIN || arg1 == PR_GCEND); if (arg1 == PR_GCBEGIN) PR_EnterMonitor(javaReflectionsMonitor); else PR_ExitMonitor(javaReflectionsMonitor); } #endif static JSObject * js_ReflectJava(JSContext *cx, JSJavaType type, HObject *handle, ClassClass *cb, char *sig, JSObject *useMe) { JSObject *mo; JSJava *java; void *key; PRHashEntry *he; PR_EnterMonitor(javaReflectionsMonitor); if (!javaReflections) goto fail; /* see if it's already been reflected */ switch (type) { case JAVA_CLASS: key = (void *) cbName(cb); break; case JAVA_OBJECT: key = (void *) handle; break; case JAVA_ARRAY: key = (void *) handle; break; default: break; } mo = PR_HashTableLookup(javaReflections, key); if (mo) goto succeed; /* if we got this far, it isn't in the hash table. * if useMe is non-null, we were called via "new" and should * use the object that was already constructed if possible */ if (useMe) { /* make sure we have the right js class and no private * data yet. if not, ignore the provided object */ if (JS_GetPrivate(cx, useMe)) useMe = NULL; else { JSClass *clasp = JS_GetClass(useMe); switch (type) { case JAVA_CLASS: case JAVA_OBJECT: if (clasp != &java_class) useMe = NULL; break; case JAVA_ARRAY: if (clasp != &javaarray_class) useMe = NULL; break; } } } mo = useMe; /* build the private data for the js reflection */ java = JS_malloc(cx, sizeof(JSJava)); if (!java) goto fail; java->type = type; java->handle = handle; java->cb = cb; java->signature = sig; switch (type) { case JAVA_CLASS: case JAVA_OBJECT: if (!mo) mo = JS_NewObject(cx, &java_class, 0, 0); if (!mo || ! JS_SetPrivate(cx, mo, java)) goto fail_free_java; break; case JAVA_ARRAY: if (!mo) mo = JS_NewObject(cx, &javaarray_class, 0, 0); if (!mo || ! JS_SetPrivate(cx, mo, java)) goto fail_free_java; if (! JS_DefineProperty(cx, mo, "length", INT_TO_JSVAL((jsint)obj_length(handle)), 0, 0, JSPROP_READONLY)) goto fail; break; default: break; } /* add it to the table */ he = PR_HashTableAdd(javaReflections, key, mo); if (!he) { JS_ReportOutOfMemory(cx); mo = 0; } succeed: PR_ExitMonitor(javaReflectionsMonitor); return mo; fail_free_java: JS_free(cx, java); fail: PR_ExitMonitor(javaReflectionsMonitor); return 0; } /* * Get the element type for a java array. *classp will be set to null * if it's not of object type. */ static JSBool js_JArrayElementType(HObject *handle, char **sig, ClassClass **classp) { /* figure out the signature from the type */ unsigned long elementtype = obj_flags(handle); char *elementsig; *sig = 0; *classp = 0; switch (elementtype) { case T_CLASS: *classp = (ClassClass*) unhand((HArrayOfObject *)handle)->body[class_offset(handle)]; elementsig = SIGNATURE_CLASS_STRING; break; case T_BOOLEAN: elementsig = SIGNATURE_BOOLEAN_STRING; break; case T_CHAR: elementsig = SIGNATURE_CHAR_STRING; break; case T_FLOAT: elementsig = SIGNATURE_FLOAT_STRING; break; case T_DOUBLE: elementsig = SIGNATURE_DOUBLE_STRING; break; case T_BYTE: elementsig = SIGNATURE_BYTE_STRING; break; case T_SHORT: elementsig = SIGNATURE_SHORT_STRING; break; case T_INT: elementsig = SIGNATURE_INT_STRING; break; case T_LONG: elementsig = SIGNATURE_LONG_STRING; break; default: PR_ASSERT(0); return JS_FALSE; } *sig = elementsig; return JS_TRUE; } PR_IMPLEMENT(JSObject *) js_ReflectJObjectToJSObject(JSContext *cx, HObject *handle) { JSObject *mo = 0; ExecEnv *ee; /* force initialization */ ee = jsj_GetCurrentEE(cx); if (!ee) return 0; if (handle) { if (obj_flags(handle)) { char *elementSig; ClassClass *elementClazz; if (!js_JArrayElementType(handle, &elementSig, &elementClazz)) { /* FIXMEbe report an error! */ return 0; } mo = js_ReflectJava(cx, JAVA_ARRAY, handle, elementClazz, elementSig, 0); PR_LOG(MojaSrc, debug, ("reflected array[%s] 0x%x as JSObject* 0x%x", elementSig, handle, mo)); } else { mo = js_ReflectJava(cx, JAVA_OBJECT, handle, obj_classblock(handle), 0, 0); PR_LOG(MojaSrc, debug, ("reflected HObject* 0x%x as JSObject* 0x%x", handle, mo)); } } return mo; } JSObject * js_ReflectJClassToJSObject(JSContext *cx, ClassClass *cb) { ExecEnv *ee; JSObject *mo; /* force initialization */ ee = jsj_GetCurrentEE(cx); if (!ee) return 0; mo = js_ReflectJava(cx, JAVA_CLASS, (HObject *) cbHandle(cb), cb, 0, 0); PR_LOG(MojaSrc, debug, ("reflected ClassClass* 0x%x as JSObject* 0x%x", cb, mo)); return mo; } /* * * * * * * * * */ /* * JSJavaSlot is a java slot which will be resolved as a method * or a field depending on context. */ struct JSJavaSlot { JSObject *obj; /* the object or class reflection */ jsval value; /* the field value when created */ JSString *name; /* name of the field or method */ struct fieldblock *fb; /* fieldblock if there is a field */ }; /* none of these should ever be called, since javaslot_convert will * first turn the slot into the underlying object if there is one */ PR_STATIC_CALLBACK(JSBool) javaslot_getProperty(JSContext *cx, JSObject *obj, jsval slot, jsval *vp) { /* evil so that the assign hack doesn't kill us */ if (slot == STRING_TO_JSVAL(ATOM_TO_STRING(cx->runtime->atomState.assignAtom))) { *vp = JSVAL_VOID; return JS_TRUE; } JS_ReportError(cx, "Java slots have no properties"); return JS_FALSE; } PR_STATIC_CALLBACK(JSBool) javaslot_setProperty(JSContext *cx, JSObject *obj, jsval slot, jsval *vp) { JS_ReportError(cx, "Java slots have no properties"); return JS_FALSE; } PR_STATIC_CALLBACK(JSBool) javaslot_resolve(JSContext *cx, JSObject *obj, jsval id) { return JS_TRUE; } PR_STATIC_CALLBACK(void) javaslot_finalize(JSContext *cx, JSObject *obj) { JSJavaSlot *slot = JS_GetPrivate(cx, obj); /* the value may be holding a reference to something... */ JS_RemoveRoot(cx, &slot->value); /* drop the object of which this is a slot */ JS_RemoveRoot(cx, &slot->obj); /* drop the name */ JS_UnlockGCThing(cx, slot->name); JS_free(cx, slot); PR_LOG(MojaSrc, debug, ("finalizing JSJavaSlot 0x%x", obj)); } PR_STATIC_CALLBACK(JSBool) javaslot_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp) { JSJavaSlot *slot = JS_GetPrivate(cx, obj); const char *name; JSFunction *fun; JSString *str; name = JS_GetStringBytes(slot->name); switch (type) { case JSTYPE_FUNCTION: fun = JS_NewFunction(cx, js_javaMethodWrapper, 0, JSFUN_BOUND_METHOD, slot->obj, name); if (!fun) return JS_FALSE; PR_LOG(MojaSrc, debug, ("converted slot to function 0x%x with name %s\n", fun, name)); *vp = OBJECT_TO_JSVAL(JS_GetFunctionObject(fun)); return JS_TRUE; case JSTYPE_OBJECT: PR_LOG(MojaSrc, debug, ("converting java slot 0x%x to object", obj)); /* FALL THROUGH */ case JSTYPE_NUMBER: case JSTYPE_BOOLEAN: case JSTYPE_STRING: if (slot->fb) { return JS_ConvertValue(cx, slot->value, type, vp); } if (type == JSTYPE_STRING) { JSJava *java = JS_GetPrivate(cx, slot->obj); char *cstr, *cp; PR_ASSERT(java->type == JAVA_OBJECT || java->type == JAVA_CLASS); cstr = PR_smprintf("[JavaMethod %s.%s]", cbName(java->cb), name); if (!cstr) { JS_ReportOutOfMemory(cx); return JS_FALSE; } for (cp = cstr; *cp != '\0'; cp++) if (*cp == '/') *cp = '.'; str = JS_NewString(cx, cstr, strlen(cstr)); if (!str) { free(cstr); return JS_FALSE; } *vp = STRING_TO_JSVAL(str); return JS_TRUE; } if (type != JSTYPE_OBJECT) { JS_ReportError(cx, "no field with name \"%s\"", name); return JS_FALSE; } *vp = OBJECT_TO_JSVAL(obj); break; default: break; } return JS_TRUE; } static JSClass javaslot_class = { "JavaSlot", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, javaslot_getProperty, javaslot_setProperty, JS_EnumerateStub, javaslot_resolve, javaslot_convert, javaslot_finalize }; static JSBool js_reflectJavaSlot(JSContext *cx, JSObject *obj, JSString *str, jsval *vp) { JSJava *java = JS_GetPrivate(cx, obj); ClassClass *cb = java->cb; struct fieldblock *fb; struct methodblock *mb; JSJavaSlot *slot; JSObject *mo; const char *name = JS_GetStringBytes(str); *vp = JSVAL_VOID; /* PR_ASSERT(obj->clazz == &java_class); */ /* if there's a field get its value at reflection time */ fb = java_lookup_field(cx, cb, java->type == JAVA_CLASS, name); if (fb) { if (!js_convertJFieldToJSValue(cx, java->handle, fb, vp, JSTYPE_VOID)) { /* * If this happens, the field had a value that couldn't * be represented in JS. */ PR_LOG(MojaSrc, error, ("looking up initial field value failed!")); /* FIXME should really set a flag that will cause an error * only if the slot is accessed as a field. for now we * make it look like there wasn't any field by that name, * which is less informative */ fb = 0; } } #ifndef REFLECT_ALL_SLOTS_LAZILY /* match the name against the signatures of the java methods */ mb = matchMethod(cx, cb, java->type==JAVA_CLASS, name, -1, 0, JS_FALSE /* reportErrors */); if (!fb) { JSFunction *fun; if (!mb) { /* nothing by that name, report an error */ JS_ReportError(cx, "Java object has no field or method named %s", name); return JS_FALSE; } /* if we get here, there's a method but no field by this name */ fun = JS_NewFunction(cx, js_javaMethodWrapper, 0, JSFUN_BOUND_METHOD, obj, name); if (!fun) return JS_FALSE; PR_LOG(MojaSrc, debug, ("eagerly converted slot to function 0x%x with name %s(0x%x)\n", fun, name, name)); *vp = OBJECT_TO_JSVAL(JS_GetFunctionObject(fun)); return JS_TRUE; } else if (!mb) { /* there's a field but no method by this name */ /* looking up the field already set *vp */ return JS_TRUE; } /* if we get here there are both fields and methods by this name, * so we create a slot object to delay the binding */ #endif slot = (JSJavaSlot *) JS_malloc(cx, sizeof(JSJavaSlot)); if (!slot) { return JS_FALSE; } /* corresponding removes and unlocks are in javaslot_finalize */ slot->obj = obj; if (!JS_AddRoot(cx, &slot->obj)) { JS_free(cx, slot); return JS_FALSE; } slot->value = *vp; if (!JS_AddRoot(cx, &slot->value)) { JS_RemoveRoot(cx, &slot->obj); JS_free(cx, slot); return JS_FALSE; } /* FIXME check return value? */ JS_LockGCThing(cx, str); slot->name = str; slot->fb = fb; mo = JS_NewObject(cx, &javaslot_class, 0, 0); JS_SetPrivate(cx, mo, slot); PR_LOG(MojaSrc, debug, ("reflected slot %s of 0x%x as 0x%x", name, java->handle, mo)); if (!mo) { JS_RemoveRoot(cx, &slot->obj); JS_RemoveRoot(cx, &slot->value); JS_UnlockGCThing(cx, slot->name); JS_free(cx, slot); return JS_FALSE; } *vp = OBJECT_TO_JSVAL(mo); return JS_TRUE; } /***********************************************************************/ /* a saved JS error state */ typedef struct SavedJSError SavedJSError; struct SavedJSError { char *message; JSErrorReport report; SavedJSError *next; }; /* * capture a JS error that occurred in JS code called by java. * makes a copy of the JS error data and hangs it off the JS * environment. when the JS code returns, this is checked and * used to generate a JSException. if the JSException is uncaught * and makes it up to another layer of JS, the error will be * reinstated with JS_ReportError */ PR_IMPLEMENT(void) js_JavaErrorReporter(JSContext *cx, const char *message, JSErrorReport *report) { /* save the error state */ SavedJSError *newerr; newerr = malloc(sizeof(SavedJSError)); if (!newerr) { /* FIXME not much we can do here, abort? */ return; } newerr->message = 0; if (message) { newerr->message = strdup(message); if (!newerr->message) { /* FIXME not much we can do here, abort? */ free(newerr); return; } } newerr->report.filename = 0; newerr->report.lineno = 0; newerr->report.linebuf = 0; newerr->report.tokenptr = 0; if (report) { if (report->filename) { newerr->report.filename = strdup(report->filename); if (!newerr->report.filename) { /* FIXME not much we can do here, abort? */ free(newerr->message); free(newerr); return; } } newerr->report.lineno = report->lineno; if (report->linebuf) { newerr->report.linebuf = strdup(report->linebuf); if (!newerr->report.linebuf) { /* FIXME not much we can do here, abort? */ free((void*)newerr->report.filename); free(newerr->message); free(newerr); return; } newerr->report.tokenptr = newerr->report.linebuf + (report->tokenptr - report->linebuf); } } /* push this error */ newerr->next = cx->savedErrors; cx->savedErrors = newerr; } static SavedJSError * js_js_FreeError(SavedJSError *err) { SavedJSError *next = err->next; free(err->message); free((char*)err->report.filename);/*FIXME*/ free((char*)err->report.linebuf); free(err); return next; } PR_IMPLEMENT(void) jsj_ClearSavedErrors(JSContext *cx) { while (cx->savedErrors) cx->savedErrors = js_js_FreeError(cx->savedErrors); } /* this is called upon returning from JS to java. one possibility * is that the JS error was actually triggered by java at some point - * if so we throw the original java exception. otherwise, each JS * error will have pushed something on JSContext->savedErrors, so * we convert them all to a string and throw a JSException with that * info. */ PR_IMPLEMENT(void) js_JSErrorToJException(JSContext *cx, ExecEnv *ee) { SavedJSError *err = 0; if (!cx->savedErrors) { exceptionClear(ee); PR_LOG(MojaSrc, debug, ("j-m succeeded with no exception cx=0x%x ee=0x%x", cx, ee)); return; } /* * If there's a pending exception in the java env, assume it * needs to be propagated (since JS couldn't have caught it * and done something with it). */ if (exceptionOccurred(ee)) { PR_LOG(MojaSrc, debug, ("j-m propagated exception through JS cx=0x%x ee=0x%x", cx, ee)); return; /* propagating is easy! */ } /* otherwise, throw a JSException */ /* get the message from the deepest saved JS error */ err = cx->savedErrors; if (err) { while (err->next) err = err->next; } /* propagate any pending JS errors upward with a java exception */ { JRIEnv *env = (JRIEnv*) ee; struct java_lang_String* message = JRI_NewStringUTF(env, err->message, strlen(err->message)); struct java_lang_String* filename = err->report.filename ? JRI_NewStringUTF(env, err->report.filename, strlen(err->report.filename)) : NULL; int lineno = err->report.lineno; struct java_lang_String* source = err->report.linebuf ? JRI_NewStringUTF(env, err->report.linebuf, strlen(err->report.linebuf)) : NULL; int index = err->report.linebuf ? err->report.tokenptr - err->report.linebuf : 0; jref exc = (jref) execute_java_constructor((ExecEnv *)env, NULL, JSExceptionClassBlock, "(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;I)", message, filename, (int32_t)lineno, source, (int32_t)index); exceptionThrow(ee, (HObject *)exc); PR_LOG(MojaSrc, debug, ("j-m raised JSException \"%s\" cx=0x%x ee=0x%x", err->message, cx, ee)); } } static JSBool js_isJSException(ExecEnv *ee, HObject *exc) { return strcmp(cbName(obj_array_classblock(exc)), "netscape/javascript/JSException") ? JS_TRUE : JS_FALSE; } /* * This is called after returning from java to JS. if the exception * is actually a JSException, we pull the original JS error state out * of the JSContext and use that. Otherwise we turn the JSException * into a string and pass it up as a JS error */ static JSBool js_JExceptionToJSError(JSContext *cx, ExecEnv *ee) { SavedJSError *err = 0; char *message; JSBool success; JHandle *exc; /* * Get rid of any JS errors so far, but save the deepest one * in case this was a JSException and we re-report it. */ /* FIXME the deepest one is the most interesting? */ err = cx->savedErrors; if (err) { while (err->next) err = js_js_FreeError(err); } /* if no exception reached us, continue on our merry way */ if (!exceptionOccurred(ee)) { PR_LOG(MojaSrc, debug, ("m-j succeeded, no exceptions cx=0x%x ee=0x%x", cx, ee)); success = JS_TRUE; goto done; } /* if we got this far there was an error for sure */ success = JS_FALSE; switch (ee->exceptionKind) { case EXCKIND_THROW: /* save the exception while we poke around in java */ exc = ee->exception.exc; exceptionClear(ee); if (err && js_isJSException(ee, exc)) { js_ReportErrorAgain(cx, err->message, &err->report); PR_LOG(MojaSrc, debug, ("m-j re-reported error \"%s\" cx=0x%x ee=0x%x", err->message, cx, ee)); } /* otherwise, describe the exception to a string */ else if (js_isSubclassOf(cx, obj_array_classblock(exc), ThrowableClassBlock)) { HString *hdetail = unhand((Hjava_lang_Throwable *)exc)->detailMessage; ClassClass *cb = obj_array_classblock(exc); message = (char *) JRI_GetStringPlatformChars((JRIEnv *) ee, (struct java_lang_String *) hdetail, (const jbyte *) cx->charSetName, (jint) cx->charSetNameLength); PR_LOG(MojaSrc, debug, ("m-j converted exception %s, \"%s\" to error cx=0x%x ee=0x%x", cbName(cb), message, cx, ee)); /* pass the string to JS_ReportError */ JS_ReportError(cx, "uncaught Java exception %s (\"%s\")", cbName(cb), message); } /* it's not a Throwable, somebody in java-land is being lame */ else { ClassClass *cb = obj_array_classblock(exc); JS_ReportError(cx, "uncaught Java exception of class %s", cbName(cb)); PR_LOG(MojaSrc, debug, ("m-j converted exception %s to error cx=0x%x ee=0x%x", cbName(cb), cx, ee)); } break; case EXCKIND_STKOVRFLW: JS_ReportError(cx, "Java stack overflow, pc=0x%x", ee->exception.addr); break; default: JS_ReportError(cx, "internal error: Java exception of unknown type %d", ee->exceptionKind); break; } done: if (err) { js_js_FreeError(err); cx->savedErrors = 0; } PR_LOG(MojaSrc, debug, ("m-j cleared JS errors cx=0x%x", cx)); return success; } /***********************************************************************/ static JSBool js_FindSystemClass(JSContext *cx, char *name, ClassClass **clazz) { ExecEnv *ee; ee = jsj_GetCurrentEE(cx); if (!ee) return JS_FALSE; *clazz = FindClassFromClass(ee, name, TRUE, NULL); return js_JExceptionToJSError(cx, ee); } /* * All js_CallJava calls must use a data pointer that starts like * this one: */ typedef struct { JRIEnv *env; } js_CallJava_data; static JSBool js_CallJava(JSContext *cx, JSJCallback doit, void *d, JSBool pushSafeFrame) { js_CallJava_data *data = d; JSBool success = JS_TRUE; ExecEnv *ee; JSJClassData *classData; ee = jsj_GetCurrentEE(cx); if (!ee) return JS_FALSE; data->env = (JRIEnv *) ee; /* security: push the safety frame onto the java stack */ if (pushSafeFrame) if (!js_pushSafeFrame(cx, ee, &classData)) { return JS_FALSE; } PR_LOG_BEGIN(MojaSrc, debug, ("entering java ee=0x%x cx=0x%x", ee, cx)); /* FIXME this should be restructured: there could be a prolog and * epilog to set up and tear down the JS->java call stuff */ doit(data); PR_LOG_END(MojaSrc, debug, ("left java ee=0x%x cx=0x%x", ee, cx)); /* it's only safe to call this on the mozilla thread */ success = js_JExceptionToJSError(cx, ee); /* pop the safety frame */ if (pushSafeFrame) js_popSafeFrame(cx, ee, classData); return success; } /***********************************************************************/ typedef struct { JRIEnv *env; HObject *self; char *name; char *sig; struct methodblock *mb; JSBool isStaticCall; va_list args; long *raddr; size_t rsize; } js_ExecuteJavaMethod_data; static void js_ExecuteJavaMethod_stub(void *d) { js_ExecuteJavaMethod_data *data = (js_ExecuteJavaMethod_data *) d; data->raddr[0] = do_execute_java_method_vararg(/*FIXME*/(ExecEnv*)data->env, data->self, data->name, data->sig, data->mb, (bool_t) data->isStaticCall, data->args, (data->rsize > sizeof(long)) ? &data->raddr[1] : NULL, FALSE); } static JSBool js_ExecuteJavaMethod(JSContext *cx, void *raddr, size_t rsize, HObject *ho, char *name, char *sig, struct methodblock *mb, JSBool isStaticCall, ...) { js_ExecuteJavaMethod_data data; JSBool success; va_list args; #if defined(XP_MAC) && !defined(NSPR20) /* Metrowerks va_start() doesn't handle one-byte parameters properly. FIX this when va_start() works again. */ args = &isStaticCall+1; #else va_start(args, isStaticCall); #endif data.self = ho; data.name = name; data.sig = sig; data.mb = mb; data.isStaticCall = isStaticCall; VARARGS_ASSIGN(data.args, args); data.raddr = raddr; data.rsize = rsize; success = js_CallJava(cx, js_ExecuteJavaMethod_stub, &data, JS_TRUE); va_end(args); return success; } /***********************************************************************/ typedef struct { JRIEnv *env; JSContext *cx; char *name; ClassClass *fromclass; ClassClass *ret; char *errstr; } js_FindJavaClass_data; static void js_FindJavaClass_stub(void *d) { js_FindJavaClass_data *data = d; ExecEnv *ee = /*FIXME*/(ExecEnv*)data->env; exceptionClear(ee); /* FIXME need to push a stack frame with the classloader * of data->fromclass or the security check on opening * the url for the class will fail */ data->ret = FindClassFromClass(ee, data->name, TRUE, data->fromclass); /* we clear the exception state, because when JS * fails to find a class it assumes it's a package instead * of an error. */ /* FIXME can we report an error if the problem is accessing * bogus-codebase? */ if (exceptionOccurred(ee)) { ClassClass *cb = obj_array_classblock(ee->exception.exc); #ifdef DEBUG char *message; /* FIXME this could fail: we don't check if it's Throwable, * but i assume that FindClass is well-behaved */ HString *hdetail = unhand((Hjava_lang_Throwable *) ee->exception.exc)->detailMessage; message = (char *) JRI_GetStringPlatformChars((JRIEnv *) ee, (struct java_lang_String *) hdetail, (const jbyte *) data->cx->charSetName, (jint) data->cx->charSetNameLength); PR_LOG(MojaSrc, debug, ("exception in is_subclass_of %s (\"%s\")", cbName(cb), message)); #endif #ifdef DEBUG_JSJ /* take a look at the exception to see if we can narrow * down the kinds of failures that cause a package to * be created? */ exceptionDescribe(ee); #endif /* FIXME other exceptions don't matter? narrow this down... */ exceptionClear(ee); } } /* * This can call arbitrary java code in class initialization. * pass 0 for the "from" argument for system classes, but if * you want to check the applet first pass its class. */ static ClassClass * js_FindJavaClass(JSContext *cx, char *name, ClassClass *from) { js_FindJavaClass_data data; data.cx = cx; data.name = name; data.fromclass = from; data.errstr = 0; if (!js_CallJava(cx, js_FindJavaClass_stub, &data, JS_TRUE)) return 0; if (data.errstr) { JS_ReportError(cx, "%s", data. errstr); free(data.errstr); /* FIXME need to propagate error condition differently */ return 0; } return data.ret; } /***********************************************************************/ typedef struct { JRIEnv *env; JSBool privileged; char *name; ClassClass *cb; char *sig; va_list args; HObject *ret; } js_ConstructJava_data; static void js_ConstructJava_stub(void *d) { js_ConstructJava_data *data = d; ExecEnv *ee = /*FIXME*/(ExecEnv*)data->env; if (data->privileged) { /* FIXME extremely lame - there should be a security * flag to execute_java_constructor_vararg instead. * the effect of this is that the JSObject constructor may * get called on the wrong thread, but this probably won't * do any damage. JRI will fix this, right? */ ee = PRIVILEGED_EE; } data->ret = execute_java_constructor_vararg(ee, data->name, data->cb, data->sig, data->args); } /* this can call arbitrary java code in class initialization */ static HObject * js_ConstructJava(JSContext *cx, char *name, ClassClass *cb, char *sig, ...) { js_ConstructJava_data data; va_list args; JSBool success; va_start(args, sig); data.name = name; data.privileged = JS_FALSE; data.cb = cb; data.sig = sig; VARARGS_ASSIGN(data.args, args); data.env = JRI_GetCurrentEnv(); success = js_CallJava(cx, js_ConstructJava_stub, &data, JS_TRUE); va_end(args); if (success) return data.ret; else return 0; } /* for private constructors, i.e. JSObject */ static HObject * js_ConstructJavaPrivileged(JSContext *cx, char *name, ClassClass *cb, char *sig, ...) { js_ConstructJava_data data; va_list args; JSBool success; va_start(args, sig); data.privileged = JS_TRUE; data.name = name; data.cb = cb; data.sig = sig; VARARGS_ASSIGN(data.args, args); data.env = JRI_GetCurrentEnv(); success = js_CallJava(cx, js_ConstructJava_stub, &data, JS_TRUE); va_end(args); if (success) return data.ret; else return 0; } /***********************************************************************/ static JSBool jsj_enabled = JS_TRUE; /* * most of the initialization is done lazily by this function, * which is only called through jsj_GetCurrentEE(). it is * only called once - this is enforced in jsj_GetCurrentEE(). */ static void jsj_FinishInit(JSContext *cx, JRIEnv *env) { char *name; ExecEnv *ee; PR_LOG(MojaSrc, debug, ("jsj_FinishInit()\n")); /* initialize the reflection tables */ javaReflections = PR_NewHashTable(256, (PRHashFunction) java_hashHandle, (PRHashComparator) java_pointerEq, (PRHashComparator) java_pointerEq, 0, 0); #ifdef NSPR20 PR_RegisterGCLockHook((GCLockHookFunc*) PrepareJSLocksForGC, 0); #endif PR_RegisterRootFinder(scanJSJavaReflections, "scan JS reflections of java objects", JS_GetRuntime(cx)); if (javaReflectionsMonitor == NULL) javaReflectionsMonitor = PR_NewNamedMonitor("javaReflections"); jsReflections = PR_NewHashTable(256, (PRHashFunction) java_hashHandle, (PRHashComparator) java_pointerEq, (PRHashComparator) java_pointerEq, 0, 0); if (jsReflectionsMonitor == NULL) jsReflectionsMonitor = PR_NewNamedMonitor("jsReflections"); ee = (ExecEnv *) env; exceptionClear(ee); name = "java/lang/Object"; if (! (ObjectClassBlock = FindClassFromClass(ee, name, TRUE, 0))) goto badclass; MakeClassSticky(ObjectClassBlock); name = "netscape/javascript/JSObject"; if (! (JSObjectClassBlock = FindClassFromClass(ee, name, TRUE, 0))) goto badclass; MakeClassSticky(JSObjectClassBlock); JSObjectInternalField = JRI_GetFieldID(env, (struct java_lang_Class*)cbHandle(JSObjectClassBlock), "internal", "I"); name = "netscape/javascript/JSException"; if (! (JSExceptionClassBlock = FindClassFromClass(ee, name, TRUE, 0))) goto badclass; MakeClassSticky(JSExceptionClassBlock); name = "java/lang/String"; if (! (StringClassBlock = FindClassFromClass(ee, name, TRUE, 0))) goto badclass; MakeClassSticky(StringClassBlock); name = "java/lang/Boolean"; if (! (BooleanClassBlock = FindClassFromClass(ee, name, TRUE, 0))) goto badclass; MakeClassSticky(BooleanClassBlock); name = "java/lang/Double"; if (! (DoubleClassBlock = FindClassFromClass(ee, name, TRUE, 0))) goto badclass; MakeClassSticky(DoubleClassBlock); name = "java/lang/Throwable"; if (! (ThrowableClassBlock = FindClassFromClass(ee, name, TRUE, 0))) goto badclass; MakeClassSticky(ThrowableClassBlock); return; badclass: PR_LOG(MojaSrc, error, ("couldn't find class \"%s\"\n", name)); JS_ReportError(cx, "Unable to initialize LiveConnect: missing \"%s\"", name); jsj_enabled = JS_FALSE; return; } PR_STATIC_CALLBACK(int) jsj_TrashJSReflectionEntry(PRHashEntry *he, int i, void *arg) { JRIEnv *env = arg; jref jso = (jref) he->value; JRI_SetFieldInt(env, jso, JSObjectInternalField, (jint) 0); /* FIXME inline JS_RemoveRoot -- this will go away with GC unification */ JS_LOCK_RUNTIME(finalizeRuntime); (void) PR_HashTableRemove(finalizeRuntime->gcRootsHash, (void *)&he->key); JS_UNLOCK_RUNTIME(finalizeRuntime); return HT_ENUMERATE_REMOVE; } PR_EXTERN(void) JSJ_Finish(void) { JRIEnv *env = 0; /* PR_ASSERT(we_have_libmocha_lock) */ jsj_enabled = JS_FALSE; if (!javaReflectionsMonitor) return; PR_EnterMonitor(javaReflectionsMonitor); if (javaReflections) { PR_HashTableDestroy(javaReflections); javaReflections = NULL; } PR_ExitMonitor(javaReflectionsMonitor); env = JRI_GetCurrentEnv(); if (!env) { #ifdef DEBUG_JSJ PR_ASSERT(env); #else return; #endif } /* FIXME assert that lm_lock is held */ PR_EnterMonitor(jsReflectionsMonitor); if (jsReflections) { PR_HashTableEnumerateEntries(jsReflections, jsj_TrashJSReflectionEntry, (void*) env); PR_HashTableDestroy(jsReflections); jsReflections = NULL; } PR_ExitMonitor(jsReflectionsMonitor); } /* * Get the java class associated with an instance, useful for access * to static fields and methods of applets. */ static JSBool js_getJavaClass(JSContext *cx, JSObject *obj, PRUintn argc, jsval *argv, jsval *rval) { JSObject *mo; JSObject *moclass; JSJava *java; /* FIXME this could accept strings as well i suppose */ if (argc != 1 || !JSVAL_IS_OBJECT(argv[0]) || !(mo = JSVAL_TO_OBJECT(argv[0])) || !JS_InstanceOf(cx, mo, &java_class, 0) || (java = (JSJava *) JS_GetPrivate(cx, mo))->type != JAVA_OBJECT) { JS_ReportError(cx, "getClass expects a Java object argument"); return JS_FALSE; } if (!(moclass = js_ReflectJClassToJSObject(cx, java->cb))) { JS_ReportError(cx, "getClass can't find Java class reflection"); return JS_FALSE; } *rval = OBJECT_TO_JSVAL(moclass); return JS_TRUE; } static JSObject * js_DefineJavaPackage(JSContext *cx, JSObject *obj, char *jsname, char *package) { JSJavaPackage *pack; JSObject *pobj; pack = JS_malloc(cx, sizeof(JSJavaPackage)); if (!pack) return 0; if (package) { pack->name = JS_strdup(cx, package); if (!pack->name) { JS_free(cx, pack); return 0; } } else { pack->name = 0; } /* FIXME can we make the package read-only? */ pobj = JS_DefineObject(cx, obj, jsname, &javapackage_class, 0, 0); if (pobj && !JS_SetPrivate(cx, pobj, pack)) (void) JS_DeleteProperty(cx, obj, jsname); return pobj; } /* hook from js_DestroyContext */ static void jsj_DestroyJSContextHook(JSContext *cx) { /* FIXME do anything with the env? */ /* FIXMEold cx->javaEnv = 0; */ } /* FIXME make sure this is called from jsscript.c:js_DestroyScript !*/ static void jsj_DestroyScriptHook(JSContext *cx, JSScript *script) { JSJClassData *data = script->javaData; if (!data) return; jsj_DestroyClassData(cx, data); script->javaData = 0; } static void jsj_DestroyFrameHook(JSContext *cx, JSStackFrame *frame) { if (frame->annotation) { JRIEnv *env = (JRIEnv *) jsj_GetCurrentEE(cx); if (!env) return; JRI_DisposeGlobalRef(env, frame->annotation); } } /* we need to hook into the js interpreter in a few special places */ JSInterpreterHooks js_Hooks = { jsj_DestroyJSContextHook, jsj_DestroyScriptHook, jsj_DestroyFrameHook }; extern void _java_javascript_init(void); /* * Initialize JS<->Java glue */ PR_IMPLEMENT(JSBool) JSJ_Init(JSJCallbacks *callbacks) { static JSBool initialized = JS_FALSE; /* JSJ_Init may be called twice: it is called with default * hooks when the netscape.javascript.JSObject class is * initialized, but it also may be called by the client * or server. in this case, the first to call it gets * to set the hooks */ if (initialized) return JS_FALSE; initialized = JS_TRUE; _java_javascript_init(); /* stupid linker tricks */ js_SetInterpreterHooks(&js_Hooks); if (!callbacks) return JS_TRUE; #ifdef NSPR20 if (MojaSrc == NULL) MojaSrc = PR_NewLogModule("MojaSrc"); #endif jsj_callbacks = callbacks; return JS_TRUE; } /* * Initialize a javascript context and toplevel object for use with JSJ */ PR_IMPLEMENT(JSBool) JSJ_InitContext(JSContext *cx, JSObject *obj) { JSObject *mo; /* grab the main runtime from the context if we haven't yet */ if (!finalizeRuntime) finalizeRuntime = JS_GetRuntime(cx); /* define the top of the java package namespace as "Packages" */ mo = js_DefineJavaPackage(cx, obj, "Packages", 0); /* some convenience packages */ /* FIXME these should be properties of the top-level package * too. as it is there will be two different objects for * "java" and "Packages.java" which is unfortunate but mostly * invisible */ /* FIXMEbe Use new JS_AliasProperty API call. * have to lookup Packages.java first... */ js_DefineJavaPackage(cx, obj, "java", "java"); js_DefineJavaPackage(cx, obj, "sun", "sun"); js_DefineJavaPackage(cx, obj, "netscape", "netscape"); JS_DefineFunction(cx, obj, "getClass", js_getJavaClass, 0, JSPROP_READONLY); /* create the prototype object for java objects and classes */ mo = JS_DefineObject(cx, obj, "#javaPrototype", &java_class, 0, JSPROP_READONLY | JSPROP_PERMANENT); /* FIXME set up the private data? */ /* any initialization that depends on java running is done in * js_FinishInitJava */ return JS_TRUE; } #ifdef NEVER /* This is really a big comment describing the JavaScript class file, never intended to be compiled. */ /* FIXME should this be in a package? */ /** * Internal class for secure JavaScript->Java calls. * We load this class with a crippled ClassLoader, and make sure * that one of the methods from this class is on the Java stack * when JavaScript calls into Java. * The call to System.err.println is necessary because otherwise * sj doesn't account for the arguments in mb->maxstack, and * the jsContext argument gets wiped out when the next stack * frame is constructed. */ package netscape.javascript; class JavaScript { /* can't be constructed */ private JavaScript() {}; /** * this is the method that will be on the Java stack when * JavaScript calls Java. it is never actually called, * but we put a reference to it on the stack as if it had * been. the mochaContext argument is a native pointer to * the current JavaScript context so that we can figure * that out from Java. */ static void callJava(int jsContext) { /* this call is here to make sure the compiler * allocates enough stack for us - previously we * were getting mb->maxstack == 0, which would * cause the jsContext argument to be overwritten */ System.err.println(jsContext); }; } #endif /* NEVER */ static unsigned char JavaScript_class_bytes[] = { "\312\376\272\276\000\003\000-\000\035\007\000\026\007\000\030\007\000" "\020\007\000\033\012\000\003\000\012\012\000\002\000\010\011\000\004\000" "\011\014\000\031\000\034\014\000\015\000\032\014\000\013\000\014\001\000" "\007println\001\000\004(I)V\001\000\003err\001\000\015ConstantValue\001" "\000\010callJava\001\000\023java/io/PrintStream\001\000\012Exceptions" "\001\000\017LineNumberTable\001\000\012SourceFile\001\000\016LocalVar" "iables\001\000\004Code\001\000\036netscape/javascript/JavaScript\001\000" "\017JavaScript.java\001\000\020java/lang/Object\001\000\006\001" "\000\025Ljava/io/PrintStream;\001\000\020java/lang/System\001\000\003" "()V\000\000\000\001\000\002\000\000\000\000\000\002\000\002\000\031\000" "\034\000\001\000\025\000\000\000\035\000\001\000\001\000\000\000\005*" "\267\000\006\261\000\000\000\001\000\022\000\000\000\006\000\001\000\000" "\000\020\000\010\000\017\000\014\000\001\000\025\000\000\000$\000\002" "\000\001\000\000\000\010\262\000\007\032\266\000\005\261\000\000\000\001" "\000\022\000\000\000\012\000\002\000\000\000\037\000\007\000\032\000\001" "\000\023\000\000\000\002\000\027" }; /* now something to make a classloader and get a JavaScript object * from it. */ /* FIXME garbage collection of these classes? how should they be * protected? */ #include "java_lang_ClassLoader.h" #define USELESS_CODEBASE_URL "http://javascript-of-unknown-origin.netscape.com/" static void jsj_DestroyClassData(JSContext *cx, JSJClassData *data) { ExecEnv *ee; JRIEnv *env; HObject *loader; /* FIXME locking! */ if (--data->nrefs > 0) return; ee = jsj_GetCurrentEE(cx); if (!ee) { /* FIXME memory leak - if we can't find a java execution * env we can't free the classloader and class */ PR_ASSERT(!"can't find java env to free JSScript.javaData"); return; } env = (JRIEnv *) ee; if (data->clazz) { JRI_DisposeGlobalRef(env, data->clazz); data->clazz = NULL; } if (data->loader) { loader = JRI_GetGlobalRef(env, data->loader); /* comment out call to releaseClassLoader because it is obsolete. execute_java_dynamic_method(ee, loader, "releaseClassLoader", "()V"); if (exceptionOccurred(ee)) { */ /* FIXME memory leak - if we can't find a java execution * env we can't free the classloader and class */ /* PR_ASSERT(!"failed releasing javascript classloader"); goto done; } */ JRI_DisposeGlobalRef(env, data->loader); data->loader = NULL; } data->mb = NULL; done: free(data); } /* security: this method returns a methodblock which can be * used for secure JS->java calls. the methodblock is * guaranteed to have an associated AppletClassLoader which will * allow the SecurityManager to determine what permissions to * give to the Java code being called. */ static JSJClassData * jsj_MakeClassData(JSContext *cx) { int i; ExecEnv *ee; JRIEnv *env; JSScript *script; JSStackFrame *fp; PRInt32 length; HArrayOfByte *bytes; const char *origin_url; struct Hjava_lang_ClassLoader *loader; ClassClass *clazz; struct methodblock *mb; JSJClassData *data; ee = jsj_GetCurrentEE(cx); if (!ee) return 0; /* see if this script already has a classloader */ script = NULL; for (fp = cx->fp; fp; fp = fp->down) { script = fp->script; if (script) break; } if (script && (data = (JSJClassData *) script->javaData)) { /* FIXME locking! */ data->nrefs++; return data; } data = (JSJClassData *) malloc(sizeof(JSJClassData)); if (!data) { JS_ReportOutOfMemory(cx); return 0; } data->nrefs = 1; data->loader = NULL; data->clazz = NULL; data->mb = NULL; if (script && script->filename && strcmp("[unknown origin]", script->filename)) { origin_url = script->filename; } else { origin_url = USELESS_CODEBASE_URL; } loader = jsj_callbacks->jsClassLoader(cx, origin_url); if (exceptionOccurred(ee) || !loader) { JS_ReportError(cx, "couldn't create ClassLoader for JavaScript"); free(data); return 0; } env = (JRIEnv *) ee; data->loader = JRI_NewGlobalRef(env, loader); /* FIXME should save and re-use this array in a jglobal */ /* make the array of bytes for class JavaScript */ length = sizeof(JavaScript_class_bytes); bytes = (HArrayOfByte *) ArrayAlloc(T_BYTE, length); if (!bytes) { jsj_DestroyClassData(cx, data); JS_ReportError(cx, "couldn't allocate bytes for JavaScript.class"); return 0; } memmove(unhand(bytes)->body, JavaScript_class_bytes, (size_t)length); /* FIXME lock to avoid race condition between this and defineClass */ clazz = FindLoadedClass("netscape/javascript/JavaScript", loader); if (!clazz) { /* make class JavaScript */ clazz = (ClassClass*) do_execute_java_method(ee, loader, "defineClass", "(Ljava/lang/String;[BII)Ljava/lang/Class;", 0, FALSE, NULL /* no required name */, bytes, 0L, length); if (exceptionOccurred(ee) || !clazz) { jsj_DestroyClassData(cx, data); JS_ReportError(cx, "couldn't load class JavaScript"); return 0; } (void) do_execute_java_method(ee, loader, "setPrincipalAry", "(Ljava/lang/Class;Ljava/lang/String;)Z", 0, FALSE, clazz, NULL /* don't lookup */); if (exceptionOccurred(ee) || !clazz) { jsj_DestroyClassData(cx, data); JS_ReportError(cx, "couldn't set principal for class JavaScript"); return 0; } } data->clazz = JRI_NewGlobalRef(env, cbHandle(clazz)); /* FIXME set up the signatures on the class here */ /* find the static method callJava() for the class */ for (i = 0; i < (int)cbMethodsCount(clazz); i++) { mb = cbMethods(clazz) + i; if (!strcmp(fieldname(&mb->fb), "callJava") && !strcmp(fieldsig(&mb->fb), "(I)V")) { /* found it... */ data->mb = mb; if (script) { /* FIXME locking! */ data->nrefs++; script->javaData = data; } return data; } } jsj_DestroyClassData(cx, data); JS_ReportError(cx, "can't find method JavaScript.callJava"); return 0; } /* * tell whether a partiular methodblock is part of a safety frame * * the security here depends on the fact that users are forbidden * to define packages in netscape.javascript. the consequence * of breaking this security would be that the user could get * a java int dereferenced as a pointer, most likely crashing * the program. * * embedding some sort of secret into the JavaScript class * (pointer to itself?) would also secure this. */ PR_IMPLEMENT(JSBool) JSJ_IsSafeMethod(struct methodblock *mb) { ClassClass *cb; if (!mb) return JS_FALSE; cb = fieldclass(&mb->fb); if (!cb || strcmp(cbName(cb), "netscape/javascript/JavaScript")) return JS_FALSE; return JS_TRUE; } /* * push a frame onto the java stack which does nothing except * provide a classloader for the security manager */ static JSBool js_pushSafeFrame(JSContext *cx, ExecEnv *ee, JSJClassData **classData) { JavaFrame *current_frame, *previous_frame; JavaStack *current_stack; struct methodblock *mb; if (!jsj_callbacks->jsClassLoader) { *classData = NULL; return JS_TRUE; } if (!(*classData = jsj_MakeClassData(cx))) return JS_FALSE; mb = (*classData)->mb; previous_frame = ee->current_frame; if (previous_frame == 0) { /* bottommost frame on this Exec Env. */ current_stack = ee->initial_stack; current_frame = (JavaFrame *)(current_stack->data); /* no vars */ } else { int args_size = mb->args_size; current_stack = previous_frame->javastack; /* assume same stack */ if (previous_frame->current_method) { int size = previous_frame->current_method->maxstack; current_frame = (JavaFrame *)(&previous_frame->ostack[size]); } else { /* The only frames that don't have a mb are pseudo frames like * this one and they don't really touch their stack. */ current_frame = (JavaFrame *)(previous_frame->optop + 3); } if (current_frame->ostack + args_size > current_stack->end_data) { /* Ooops. The current stack isn't big enough. */ if (current_stack->next != 0) { current_stack = current_stack->next; } else { current_stack = CreateNewJavaStack(ee, current_stack); if (current_stack == 0) { JS_ReportOutOfMemory(cx); return JS_FALSE; } } /* no vars */ current_frame = (JavaFrame *)(current_stack->data); } } current_frame->prev = previous_frame; current_frame->javastack = current_stack; current_frame->optop = current_frame->ostack; current_frame->vars = 0; /* better not reference any! */ current_frame->monitor = 0; /* not monitoring anything */ current_frame->annotation = 0; /* make this be a method with the JS classloader */ current_frame->current_method = mb; /* FIXME push the current mochaContext as an integer */ current_frame->optop[0].i = (int32_t) cx; current_frame->optop += current_frame->current_method->args_size; current_frame->constant_pool = 0; ee->current_frame = current_frame; return JS_TRUE; } /* * pop the safety frame from the java stack */ static void js_popSafeFrame(JSContext *cx, ExecEnv *ee, JSJClassData *classData) { if (!classData) return; ee->current_frame = ee->current_frame->prev; jsj_DestroyClassData(cx, classData); } /* * look up the stack for the most recent safety frame and extract * the JSContext from it. return NULL if no such frame was found. */ PR_IMPLEMENT(void) JSJ_FindCurrentJSContext(JRIEnv *env, JSContext **cxp, void **loaderp) { ExecEnv *ee = (ExecEnv *) env; JavaFrame *frame, frame_buf; ClassClass *cb; #define NEXT_FRAME(frame) \ (((frame)->current_method && (frame)->current_method->fb.access & ACC_MACHINE_COMPILED) ? \ CompiledFramePrev(frame, &frame_buf) \ : frame->prev) *cxp = 0; *loaderp = 0; /* search the stack upward */ for (frame = ee->current_frame; frame; frame = NEXT_FRAME(frame)) { struct methodblock *mb = frame->current_method; if (mb) { cb = fieldclass(&frame->current_method->fb); *loaderp = cbLoader(cb); if (*loaderp) { if (JSJ_IsSafeMethod(mb)) { /* extract the mochacontext here */ *cxp = (JSContext *)frame->ostack[0].i; } else *cxp = 0; return; } } } return; } PR_IMPLEMENT(JSBool) JSJ_IsCalledFromJava(JSContext *cx) { ExecEnv *ee; ee = (ExecEnv *) JRI_GetCurrentEnv(); if (ee == NULL) return JS_FALSE; return ee->current_frame != NULL; } /* extract the javascript object from a netscape.javascript.JSObject */ PR_IMPLEMENT(JSObject *) JSJ_ExtractInternalJSObject(JRIEnv *env, HObject* jso) { PR_ASSERT(obj_array_classblock((HObject*)jso) == JSObjectClassBlock); return (JSObject *) JRI_GetFieldInt(env, (netscape_javascript_JSObject*)jso, JSObjectInternalField); } PR_IMPLEMENT(JSPrincipals *) js_GetJSPrincipalsFromJavaCaller(JSContext *cx, int callerDepth) { return jsj_callbacks->getJSPrincipalsFromJavaCaller(cx, callerDepth); } #endif /* defined(JAVA) */