netscape-revival
sun-java/netscape/javascript/jsStubs.c
/* XXX we leak private/secret data via js_toString_stub and others */
/* XXX need to check for taint and throw exception in js_GetMember_stub etc. */
/* XXX remove when pure JRI if possible */
#include "oobj.h"
#include "interpreter.h"
#include "tree.h"
#include "mocha.h"
#include "mochaapi.h"
#include "mo_java.h"
#include "mo_cntxt.h" /* for MochaContext.javaEnv */
#include "java.h"
#include "jri.h"
#include "lj.h"
#include "prmon.h" /* for PR_XLock and PR_XUnlock */
#include "prprf.h" /* for PR_snprintf */
#include "prthread.h"
#include "prlog.h"
#include "structs.h" /* for MWContext.type */
#define IMPLEMENT_netscape_javascript_JSObject
#include "netscape_javascript_JSObject.h"
#ifndef XP_MAC
#include "netscape_javascript_JSException.h"
#else
#include "n_javascript_JSException.h"
#endif
#define IMPLEMENT_netscape_applet_AppletClassLoader
#define IMPLEMENT_netscape_applet_MozillaAppletContext
#ifndef XP_MAC
/* for acl.context: */
#include "netscape_applet_AppletClassLoader.h"
/* for acx.frameMWContext: */
#include "netscape_applet_MozillaAppletContext.h"
#else
#include "n_applet_AppletClassLoader.h"
#include "n_applet_MozillaAppletContext.h"
#endif
/*
* utility functions, some overlap with jrijdk.c
*/
#define NaEnv2EE(env) \
((ExecEnv*)((char*)(env) - offsetof(ExecEnv, nativeInterface)))
#define java_MochaContext(env) ((MochaContext *) (NaEnv2EE(env)->mochaContext))
#define java_SetMochaContext(env, mc) \
(* (MochaContext **) &(NaEnv2EE(env)->mochaContext) = (mc))
#if 0
static JRIGlobalRef js_JSExceptionClass = 0;
#endif
#define JAVA_MOCHA_STACK_SIZE 8192
typedef struct {
JRIEnv *env;
MochaErrorReporter reporter;
} SavedMochaContext;
typedef struct JSErrorState JSErrorState;
struct JSErrorState {
char *message;
MochaErrorReport error;
JSErrorState *next;
};
static void
js_throwJSException(JRIEnv *env, char *detail)
{
jref message = JRI_NewStringUTF(env, detail, strlen(detail));
jref exc =
netscape_javascript_JSException_new_1(env,
class_netscape_javascript_JSException(env),
/*Java_getGlobalRef(env, js_JSExceptionClass),*/
message);
JRI_Throw(env, exc);
}
static void *
js_currentClassLoader(JRIEnv *env)
{
struct javaframe *frame, frame_buf;
ClassClass *cb;
ExecEnv *ee = (ExecEnv *) env;
void *loader = 0;
/* XXX this should all be put in a convenience function
* in security.c! */
/* otherwise, look up the stack for a classloader */
for (frame = ee->current_frame ; frame != 0 ;) {
if (frame->current_method != 0) {
cb = fieldclass(&frame->current_method->fb);
if (cb && cbLoader(cb)) {
loader = cbLoader(cb);
break;
}
frame = frame->current_method->fb.access & ACC_MACHINE_COMPILED ?
CompiledFramePrev(frame, &frame_buf) : frame->prev;
} else {
frame = frame->prev;
}
}
return loader;
}
/* check to see if it is ok to use JS from this Java context.
* if not, throw an exception and return false */
static Bool
js_checkMayScript(JRIEnv *env)
{
jref loader;
loader = js_currentClassLoader(env);
if (!loader || netscape_applet_AppletClassLoader_mayScript(env, loader))
return TRUE;
js_throwJSException(env, "MAYSCRIPT is not enabled for this applet");
return FALSE;
}
/* find the current classloader and associated MochaContext
* if possible */
/* this must be called only if both the mozilla and the java thread
* are under control to avoid race conditions */
static MochaContext *
js_findMochaContext(JRIEnv *env, char **errp)
{
ExecEnv *ee = (ExecEnv *) env;
void *loader;
*errp = 0;
/* XXX daemon threads can't call mocha */
loader = js_currentClassLoader(env);
if (!loader) {
extern MochaContext *lm_crippled_context; /* XXX kill me */
/* no classloader, use the crippled MochaContext */
PR_LOG(Moja, debug,
("called JavaScript with no classloader"));
return lm_crippled_context;
}
if (obj_classblock((HObject*)loader)
== FindClass(ee, "netscape/applet/AppletClassLoader", FALSE)) {
netscape_applet_AppletClassLoader *aloader = loader;
netscape_applet_MozillaAppletContext *acx;
MWContext *mwcontext;
MochaContext *mc;
/* ok, we were called through an applet. find the associated
* applet context */
acx = get_netscape_applet_AppletClassLoader_context(env, aloader);
/* get the MWContext from the applet context */
mwcontext = (MWContext *)
get_netscape_applet_MozillaAppletContext_frameMWContext(env, acx);
if (!mwcontext) {
PR_LOG(Moja, warn,
("no MWContext for MozillaAppletContext 0x%x",
acx));
*errp = "JavaScript unavailable: no window found";
return 0;
}
/* we only allow mocha calls in browser windows */
/* XXX this could probably be relaxed a bit */
if (mwcontext->type != MWContextBrowser) {
PR_LOG(Moja, warn,
("tried to call JavaScript on a context of type %d",
mwcontext->type));
*errp = "JavaScript unavailable: bad window type";
return 0;
}
/* ... and the MochaContext from there... */
if (!(mc = mwcontext->mocha_context)) {
PR_LOG(Moja, warn,
("no MochaContext for MWContext 0x%x", mwcontext));
*errp = "JavaScript unavailable in this window";
return 0;
}
if (!mc->globalObject) {
PR_LOG(Moja, warn,
("no global object for MochaContext 0x%x", mc));
*errp = "JavaScript unavailable in this window";
return 0;
}
return mc;
}
PR_LOG(Moja, warn, ("unknown ClassLoader subclass %s found",
classname(obj_classblock((HObject*)loader))));
*errp = "JavaScript unavailable: illegal ClassLoader";
return 0;
}
/***********************************************************************/
/*
* this is the gateway from java to mocha.
*/
/* all callbacks used with js_CallMocha must start like this */
typedef struct {
JRIEnv *env;
MochaObject *mo;
} js_CallMocha_data_prefix;
typedef struct {
JRIEnv *env;
MochaObject *mo;
LJCallback doit;
js_CallMocha_data_prefix *data;
char *err;
} js_CallMocha_data;
/* this extra level of indirection (double-callback) is
* unfortunate but necessary to make sure that we can mess
* with the MochaContext without interfering with mozilla.
* another approach would be to make lj_thred.c mocha-specific.
*/
static void
js_CallMocha_stub(void *d)
{
js_CallMocha_data *data = (js_CallMocha_data *) d;
SavedMochaContext save;
JRIEnv *env = data->env;
MochaContext *mc;
data->err = 0;
mc = js_findMochaContext(env, &data->err);
/* if we couldn't get the mocha context return and throw an
* exception with detail message data->err */
if (!mc) return;
/* save some info and change the error reporter */
save.env = (JRIEnv *) mc->javaEnv;
save.reporter = MOCHA_SetErrorReporter(mc, mocha_js_ErrorReporter);
mc->javaEnv = (void*) env;
/* XXX should save env.mochaContext as well? */
java_SetMochaContext(env, mc);
data->data->env = data->env;
data->data->mo = data->mo;
data->doit(data->data);
/* restore saved info */
mc->javaEnv = (void*) save.env;
MOCHA_SetErrorReporter(mc, save.reporter);
mocha_MErrorToJException(mc, (ExecEnv*)env);
}
/* this function calls doit on the mozilla thread with a
* valid mocha context */
/* it is called from a java thread */
static void
js_CallMocha(JRIEnv *env, struct netscape_javascript_JSObject* self,
LJCallback doit, void *d)
{
js_CallMocha_data data;
MochaObject *mo;
if (!self ||
!(mo = (MochaObject *)
get_netscape_javascript_JSObject_internal(env, self))) {
/* XXX use a better exception */
JRI_ThrowNew(env, JRI_FindClass(env, "java/lang/Exception"),
"not a JavaScript object");
return;
}
PR_LOG(Moja, debug, ("clearing exceptions in ee=0x%x", env));
exceptionClear((ExecEnv *)env);
data.env = env;
data.mo = mo;
data.doit = doit;
data.data = d;
PR_LOG_BEGIN(Moja, debug, ("entering mocha mo=0x%x", mo));
LJ_CallMozilla(env, js_CallMocha_stub, &data);
PR_LOG_END(Moja, debug, ("leaving mocha mo=0x%x", mo));
/* data.err holds errors from the stub itself - errors from
* mocha will already have been thrown by now */
if (data.err) {
js_throwJSException(env, data.err);
}
}
/***********************************************************************/
typedef struct {
JRIEnv *env;
MochaObject *mo;
char *cstr;
MochaSlot slot;
jref ret;
} js_GetMember_data;
static void
js_GetMember_stub(void *d)
{
js_GetMember_data *data = (js_GetMember_data*) d;
MochaDatum datum;
JRIEnv *env = data->env;
MochaContext *mc = java_MochaContext(env);
int cost = 0;
data->ret = NULL;
if (data->cstr ? MOCHA_ResolveName(mc, data->mo, data->cstr, &datum)
: MOCHA_GetSlot(mc, data->mo, data->slot, &datum))
mocha_convertMDatumToJObject(/*XXX*/(HObject**)&data->ret, mc, &datum,
0, 0, MOCHA_FALSE, &cost);
}
/* * * * * * * * */
JRI_PUBLIC_API(struct java_lang_Object *)
native_netscape_javascript_JSObject_getMember(
JRIEnv* env,
struct netscape_javascript_JSObject* self,
struct java_lang_String *name)
{
js_GetMember_data data;
const char *cstr = 0;
jref member;
if (!js_checkMayScript(env))
return 0;
if (name != NULL &&
(cstr = JRI_GetStringUTFChars(env, name))) {
data.cstr = (char*)cstr;
js_CallMocha(env, self, js_GetMember_stub, &data);
member = data.ret;
} else {
/* XXX use a better exception */
JRI_ThrowNew(env, JRI_FindClass(env, "java/lang/Exception"),
"illegal member name");
member = NULL;
}
return member;
}
/* public native getSlot(I)Ljava/lang/Object; */
JRI_PUBLIC_API(struct java_lang_Object *)
native_netscape_javascript_JSObject_getSlot(
JRIEnv* env,
struct netscape_javascript_JSObject* self,
jint slot)
{
js_GetMember_data data;
if (!js_checkMayScript(env))
return 0;
PR_LOG(Moja, debug,
("JSObject.getSlot(%d)\n", slot));
data.cstr = 0;
data.slot = slot;
js_CallMocha(env, self, js_GetMember_stub, &data);
return data.ret;
}
/***********************************************************************/
typedef struct {
JRIEnv *env;
MochaObject *mo;
char *cstr;
MochaSlot slot;
jref value;
} js_SetMember_data;
static void
js_SetMember_stub(void *d)
{
js_SetMember_data *data = (js_SetMember_data*) d;
JRIEnv *env = data->env;
MochaContext *mc = java_MochaContext(env);
MochaDatum datum, saveDatum;
MochaObject *obj = data->mo;
MochaSymbol *sym = 0;
char buf[24], *name;
MochaAtom *atom;
if (!mocha_convertJObjectToMDatum(mc, &datum,
/*XXX*/(HObject*)data->value)) {
return;
}
name = data->cstr;
if (!name) {
PR_snprintf(buf, sizeof buf, "%d", data->slot);
name = buf;
}
atom = MOCHA_Atomize(mc, name);
if (!atom)
return;
saveDatum = datum;
MOCHA_HoldRef(mc, &saveDatum);
MOCHA_HoldAtom(mc, atom);
if (mocha_LookupSymbol(mc, obj->scope, atom, MLF_SET, &sym)) {
if (!sym || sym->type != SYM_PROPERTY) {
sym = mocha_SetProperty(mc, obj->scope, atom,
data->cstr ? data->mo->scope->minslot-1
: data->slot,
datum);
}
if (sym) {
MochaProperty *prop = sym_property(sym);
MochaDatum oldDatum = prop->datum;
if (prop->datum.flags & MDF_READONLY) {
MOCHA_ReportError(mc, "%s cannot be set by assignment",
atom_name(sym_atom(sym)));
} else if ((*prop->setter)(mc, obj, sym->slot, &datum)) {
/* Store value, taking care not to smash nrefs and flags. */
MOCHA_INIT_FULL_DATUM(mc, &prop->datum, datum.tag,
prop->datum.flags, datum.taint,
u, datum.u);
MOCHA_HoldRef(mc, &prop->datum);
MOCHA_DropRef(mc, &oldDatum);
}
}
}
MOCHA_DropRef(mc, &saveDatum);
MOCHA_DropAtom(mc, atom);
PR_LOG(Moja, debug,
("JSObject.setMember(%s, 0x%x)\n", data->cstr, data->value));
}
/* * * * * * * * */
JRI_PUBLIC_API(void)
native_netscape_javascript_JSObject_setMember(
JRIEnv* env,
struct netscape_javascript_JSObject* self,
struct java_lang_String *name,
struct java_lang_Object *value)
{
js_SetMember_data data;
const char *cstr = 0;
if (!js_checkMayScript(env))
return;
if (name != NULL &&
(cstr = JRI_GetStringUTFChars(env, name))) {
data.cstr = (char*)cstr;
data.slot = -1;
data.value = value;
js_CallMocha(env, self, js_SetMember_stub, &data);
} else {
/* XXX use a better exception */
JRI_ThrowNew(env, JRI_FindClass(env, "java/lang/Exception"),
"illegal member name");
}
}
JRI_PUBLIC_API(void)
native_netscape_javascript_JSObject_setSlot(
JRIEnv* env,
struct netscape_javascript_JSObject* self,
jint slot,
struct java_lang_Object *value)
{
js_SetMember_data data;
if (!js_checkMayScript(env))
return;
PR_LOG(Moja, debug,
("JSObject.setSlot(%d, 0x%x)\n", slot, value));
data.cstr = 0;
data.slot = slot;
data.value = value;
js_CallMocha(env, self, js_SetMember_stub, &data);
}
/***********************************************************************/
typedef struct {
JRIEnv *env;
MochaObject *mo;
char *cstr;
} js_RemoveMember_data;
static void
js_RemoveMember_stub(void *d)
{
js_RemoveMember_data *data = (js_RemoveMember_data*) d;
JRIEnv *env = data->env;
MochaContext *mc = java_MochaContext(env);
MOCHA_RemoveProperty(mc, data->mo, data->cstr);
PR_LOG(Moja, debug,
("JSObject.removeMember(%s)\n", data->cstr));
}
/* * * * * * * * */
JRI_PUBLIC_API(void)
native_netscape_javascript_JSObject_removeMember(
JRIEnv* env,
struct netscape_javascript_JSObject* self,
struct java_lang_String * name)
{
js_RemoveMember_data data;
const char *cstr = 0;
if (!js_checkMayScript(env))
return;
if (name != NULL &&
(cstr = JRI_GetStringUTFChars(env, name))) {
data.cstr = (char*)cstr;
js_CallMocha(env, self, js_RemoveMember_stub, &data);
PR_LOG(Moja, debug, ("JSObject.removeMember(%s)", cstr));
} else {
/* XXX use a better exception */
JRI_ThrowNew(env, JRI_FindClass(env, "java/lang/Exception"),
"illegal member name");
}
}
/***********************************************************************/
typedef struct {
JRIEnv *env;
MochaObject *mo;
char *method;
jref args;
PRBool success;
jref ret;
} js_Call_data;
static void
js_Call_stub(void *d)
{
js_Call_data *data = (js_Call_data*) d;
MochaDatum *argv;
int argc, i;
JRIEnv *env = data->env;
MochaContext *mc = java_MochaContext(env);
int cost = 0;
if (data->args) {
argc = JRI_GetObjectArrayLength(env, data->args);
argv = sysMalloc(argc * sizeof(MochaDatum));
} else {
argc = 0;
argv = 0;
}
data->success = PR_TRUE;
for (i = 0; i < argc; i++) {
jref arg = JRI_GetObjectArrayElement(env, data->args, i);
data->success =
mocha_convertJObjectToMDatum(mc, argv + i,
/*XXX*/(HObject*)arg);
if (!data->success)
break;
}
/* XXX if argument 2 fails argument 1 will never be freed! */
if (data->success) {
MochaDatum rval;
MOCHA_MIX_TAINT(mc, mc->taintInfo->accum, MOCHA_TAINT_JAVA);
if (!MOCHA_CallMethod(mc, data->mo, data->method, argc, argv, &rval)
|| !mocha_convertMDatumToJObject((HObject **)&data->ret, mc, &rval,
0, 0, MOCHA_FALSE, &cost))
data->success = PR_FALSE;
}
sysFree(argv);
}
/* * * * * * * * */
/* XXX should check for self=null everywhere! */
JRI_PUBLIC_API(struct java_lang_Object *)
native_netscape_javascript_JSObject_call(
JRIEnv* env,
struct netscape_javascript_JSObject* self,
struct java_lang_String *method,
jobjectArray args)
{
const char *cstr;
js_Call_data data;
if (!js_checkMayScript(env))
return 0;
if (!method ||
!(cstr = JRI_GetStringUTFChars(env, method))) {
/* XXX use a better exception */
JRI_ThrowNew(env, JRI_FindClass(env, "java/lang/Exception"),
"not a method");
return NULL;
}
data.method = (char*)cstr;
data.args = args;
PR_LOG_BEGIN(Moja, debug, ("calling mocha \"%s\"", cstr));
js_CallMocha(env, self, js_Call_stub, &data);
PR_LOG_END(Moja, debug, ("returned from mocha \"%s\"", cstr));
if (data.success) {
return data.ret;
} else {
/* exception should be thrown inside CallMozilla */
return NULL;
}
}
/***********************************************************************/
typedef struct {
JRIEnv *env;
MochaObject *mo;
char *source;
size_t sourcelen;
jref ret;
} js_Eval_data;
static void
js_Eval_stub(void *d)
{
js_Eval_data *data = (js_Eval_data*) d;
MochaDatum rval;
JRIEnv *env = data->env;
MochaContext *mc = java_MochaContext(env);
int cost = 0;
data->ret = NULL;
MOCHA_MIX_TAINT(mc, mc->taintInfo->accum, MOCHA_TAINT_JAVA);
if (MOCHA_EvaluateBuffer(mc, data->mo, data->source, data->sourcelen,
"(java call)", 0, &rval))
mocha_convertMDatumToJObject((HObject**)&data->ret, mc, &rval,
0, 0, MOCHA_FALSE, &cost);
}
/* * * * * * * * */
JRI_PUBLIC_API(struct java_lang_Object *)
native_netscape_javascript_JSObject_eval(
JRIEnv* env,
struct netscape_javascript_JSObject* self,
struct java_lang_String *s)
{
js_Eval_data data;
const char *source;
jref ret = NULL;
if (!js_checkMayScript(env))
return 0;
if (s &&
(source = JRI_GetStringUTFChars(env, s))) {
data.source = (char*)source;
/* subtract 1 for the NUL */
data.sourcelen = JRI_GetStringUTFLength(env, s) - 1;
js_CallMocha(env, self, js_Eval_stub, &data);
ret = data.ret;
} else {
/* XXX use a better exception */
JRI_ThrowNew(env, JRI_FindClass(env, "java/lang/Exception"),
"bad script");
}
return ret;
}
/***********************************************************************/
typedef struct {
JRIEnv *env;
MochaObject *mo;
} js_Finalize_data;
static void
js_Finalize_stub(void *d)
{
js_Finalize_data *data = (js_Finalize_data*) d;
JRIEnv *env = data->env;
MochaContext *mc = java_MochaContext(env);
mocha_removeReflection(data->mo);
MOCHA_DropObject(mc, data->mo);
}
/* * * * * * * * */
JRI_PUBLIC_API(void)
native_netscape_javascript_JSObject_finalize(
JRIEnv* env,
struct netscape_javascript_JSObject* self)
{
js_Finalize_data data;
/* XXX checkMayScript here? what would that accomplish? */
js_CallMocha(env, self, js_Finalize_stub, &data);
}
/***********************************************************************/
typedef struct {
JRIEnv *env;
MochaObject *mo;
char *ret;
} js_toString_data;
static void
js_toString_stub(void *d)
{
js_toString_data *data = (js_toString_data*) d;
JRIEnv *env = data->env;
MochaContext *mc = java_MochaContext(env);
MochaDatum datum;
MochaAtom *atom;
MOCHA_INIT_FULL_DATUM(mc, &datum, MOCHA_OBJECT, 0, MOCHA_TAINT_IDENTITY,
u.obj, data->mo);
if (!MOCHA_DatumToString(mc, datum, &atom))
data->ret = 0;
/* freed once the stub returns */
data->ret = strdup(MOCHA_GetAtomName(mc, atom));
MOCHA_DropAtom(mc, atom);
}
/* * * * * * * * */
JRI_PUBLIC_API(struct java_lang_String *)
native_netscape_javascript_JSObject_toString(
JRIEnv* env,
struct netscape_javascript_JSObject* self)
{
js_toString_data data;
jref ret;
if (!js_checkMayScript(env))
return 0;
js_CallMocha(env, self, js_toString_stub, &data);
if (!data.ret) {
/* XXX could grab the clazz name from the MochaObject */
return JRI_NewStringUTF(env, "*JSObject*", strlen("*JSObject*"));
}
/* make a java String from str */
ret = JRI_NewStringUTF(env, data.ret, strlen(data.ret));
free(data.ret);
return ret;
}
/***********************************************************************/
typedef struct {
JRIEnv *env;
jref applet;
jref ret;
} js_getWindow_data;
static void
js_getWindow_stub(void *d)
{
js_getWindow_data *data = (js_getWindow_data*) d;
data->ret = LJ_GetAppletMochaWindow(data->env, data->applet);
}
/* * * * * * * * */
JRI_PUBLIC_API(struct netscape_javascript_JSObject*)
native_netscape_javascript_JSObject_getWindow(
JRIEnv* env,
struct java_lang_Class* clazz,
struct java_applet_Applet* applet)
{
js_getWindow_data data;
if (!js_checkMayScript(env))
return 0;
if (!applet) {
JRI_ThrowNew(env, JRI_FindClass(env, "java/lang/NullPointerException"),
0);
return 0;
}
if (!MOCHA_JavaGlueEnabled()) {
js_throwJSException(env, "Java/JavaScript communication is disabled");
return 0;
}
data.env = env;
data.applet = applet;
LJ_CallMozilla(env, js_getWindow_stub, &data);
return data.ret;
}
/***********************************************************************/
JRI_PUBLIC_API(void)
native_netscape_javascript_JSObject_initClass(
JRIEnv* env,
struct java_lang_Class* myClazz)
{
struct java_lang_Class* clazz;
/* this is to initialize the field lookup indices, and protect
* the classes from gc. */
/* XXX it would be nice to ensure that this gets called
* with mozenv, since if a different env is used these globals
* may go byebye. */
clazz = use_netscape_javascript_JSObject(env);
JRI_NewGlobalRef(env, clazz);
clazz = use_netscape_javascript_JSException(env);
JRI_NewGlobalRef(env, clazz);
/* XXX i really don't think this is the right place to do this */
clazz = use_netscape_applet_AppletClassLoader(env);
JRI_NewGlobalRef(env, clazz);
}