netscape-revival
mocha/src/mo_java.c
/*
** Mocha 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 mocha object
*/
/* XXX these entry points to java need to use mocha_CallJava
* which should set env->mochaContext to the current MochaContext.
* makeJavaString?
* allocCString?
* exceptiondescribe?
*/
/* better exception handling needed in mocha<->java calls */
#ifdef JAVA
#include "mo_cntxt.h"
#include "mocha.h"
#include "mochaapi.h"
#include "mochalib.h"
/* java joy */
#include "oobj.h"
#include "interpreter.h"
#include "tree.h"
#include "opcodes.h"
#include "javaString.h"
#include "exceptions.h"
#include "jri.h"
/* libjava */
#include "lj.h"
/* nspr stuph */
#include "prgc.h"
#include "prhash.h"
#include "prmon.h" /* for PR_XLock and PR_XUNlock */
#include "prlog.h"
#include "prprf.h"
#include "mo_java.h"
/*
** Those namespace terrorists up in Redmond are holding "exception" hostage
** Free it now !!!
*/
#if defined(exception)
#undef exception
#endif
/* 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"
#include "java_applet_Applet.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 MochaJavaPackage MochaJavaPackage;
/* either a java class or a java object.
* class.field is a static field or method
* class(...) constructs an object which is an instance of the class
* object.field is a field or method */
typedef struct MochaJava MochaJava;
/* type associated with a MochaJava structure */
/* the MochaClass java_class uses OBJECT and CLASS
* javaarray_class uses ARRAY */
typedef enum {
JAVA_UNDEF,
JAVA_OBJECT,
JAVA_CLASS,
JAVA_ARRAY
} MochaJavaType;
/* 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 MochaJavaSlot MochaJavaSlot;
/*
* globals for convenience, set up in the init routine
*/
static ClassClass * JSObjectClassBlock = 0;
static ClassClass * StringClassBlock = 0;
static ClassClass * BooleanClassBlock = 0;
static ClassClass * DoubleClassBlock = 0;
static ClassClass * ThrowableClassBlock = 0;
/*
* 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 mocha
* object reflection. there is a root finder registered with the
* gc that marks all the java objects (keys); the mocha objects
* are not referenced and are responsible for removing themselves
* from the table upon finalization.
*/
static PRHashTable *javaReflections = NULL;
static PRMonitor *javaReflectionsMonitor = 0;
/*
* similarly for java reflections of mocha objects - in this case
* the keys are mocha objects. when the corresponding JSObject is
* finalized the entry is removed from the table, and a reference
* to the mocha object is dropped.
*/
static PRHashTable *mochaReflections = NULL;
static PRMonitor *mochaReflectionsMonitor = 0;
PR_LOG_DEFINE(Moja);
/* lazy initialization */
static int mocha_java_initialized = 0;
static void mocha_FinishInitJava(MochaContext *mc);
#define FINISH_MOCHA_JAVA_INIT(mc) \
if (!mocha_java_initialized) { \
mocha_FinishInitJava(mc); \
}
/* check if a field is static/nonstatic */
#define CHECK_STATIC(isStatic, fb) (((fb)->access & ACC_STATIC) \
? (isStatic) : !(isStatic))
/* forward declarations */
static MochaBoolean
mocha_CallJava(MochaContext *mc, LJCallback doit, void *d,
MochaBoolean pushSafeFrame);
static ClassClass *
mocha_FindJavaClass(MochaContext *mc, char *name, ClassClass *from);
static MochaBoolean
mocha_ExecuteJavaMethod(MochaContext *mc, void *raddr, size_t rsize,
HObject *ho, char *name, char *sig,
struct methodblock *mb, bool_t isStaticCall, ...);
static HObject *
mocha_ConstructJava(MochaContext *mc, char *name, ClassClass *cb,
char *sig, ...);
static HObject *
mocha_ConstructJavaPrivileged(MochaContext *mc, char *name, ClassClass *cb,
char *sig, ...);
static MochaObject *
mocha_reflectJavaSlot(MochaContext *mc, MochaObject *obj, MochaAtom *atom);
static MochaBoolean
mocha_javaMethodWrapper(MochaContext *mc, MochaObject *obj,
unsigned argc, MochaDatum *argv, MochaDatum *rval);
static MochaBoolean
mocha_javaConstructorWrapper(MochaContext *mc, MochaObject *obj,
unsigned argc, MochaDatum *argv,
MochaDatum *rval);
static MochaBoolean mocha_JArrayElementType(HObject *handle, char **sig,
ClassClass **clazz);
static MochaBoolean
mocha_convertMDatumToJSObject(HObject **objp, MochaContext *mc,
MochaObject *mpo, ClassClass *paramcb,
MochaBoolean checkOnly);
static MochaBoolean
mocha_convertMDatumToJElement(MochaContext *mc, MochaDatum *dp,
char *addr, char *sig, ClassClass *fromClass,
char **sigRest);
static MochaBoolean
mocha_convertMDatumToJValue(MochaContext *mc, MochaDatum *dp,
OBJECT *addr, char *sig, ClassClass *fromClass,
MochaBoolean checkOnly, char **sigRestPtr,
int *cost);
static MochaBoolean
mocha_convertMDatumToJField(MochaContext *mc, MochaDatum *dp,
HObject *ho, struct fieldblock *fb);
static MochaBoolean
mocha_convertJElementToMDatum(MochaContext *mc, char *addr, char *sig,
MochaDatum *dp, MochaTag desired);
static MochaBoolean
mocha_convertJValueToMDatum(MochaContext *mc, OBJECT *addr, char *sig,
MochaDatum *dp, MochaTag desired);
static MochaBoolean
mocha_convertJFieldToMDatum(MochaContext *mc, HObject *ho, struct fieldblock *fb,
MochaDatum *dp, MochaTag desired);
static MochaBoolean
mocha_convertJObjectToMString(MochaContext *mc, HObject *ho,
bool_t isClass, MochaDatum *dp);
static MochaBoolean
mocha_convertJObjectToMNumber(MochaContext *mc, HObject *ho,
bool_t isClass, MochaDatum *dp);
static MochaBoolean
mocha_convertJObjectToMBoolean(MochaContext *mc, HObject *ho,
bool_t isClass, MochaDatum *dp);
static struct methodblock *
mocha_findSafeMethod(MochaContext *mc);
static MochaBoolean
mocha_pushSafeFrame(MochaContext *mc, ExecEnv *ee);
static void
mocha_popSafeFrame(MochaContext *mc, ExecEnv *ee);
/* handy macro from agent.c */
#define obj_getoffset(o, off) (*(OBJECT *)((char *)unhand(o)+(off)))
/**** **** **** **** **** **** **** **** ****
*
* java packages are just strings
*
**** **** **** **** **** **** **** **** ****/
struct MochaJavaPackage {
int lastslot; /* current slot index */
char *name; /* e.g. "java/lang" or 0 if it's the top level */
};
/* javapackage uses standard get_property */
static MochaBoolean
javapackage_set_property(MochaContext *mc, MochaObject *obj,
MochaSlot slot, MochaDatum *dp)
{
MochaJavaPackage *package = obj->data;
MOCHA_ReportError(mc, "%s doesn't refer to any Java value",
package->name);
return MOCHA_FALSE;
}
static MochaBoolean
javapackage_list_properties(MochaContext *mc, MochaObject *obj)
{
/* XXX can't do this without reading directories... */
return MOCHA_TRUE;
}
/* forward declaration */
static MochaBoolean
javapackage_resolve_name(MochaContext *mc, MochaObject *obj, const char *name);
static MochaBoolean
javapackage_convert(MochaContext *mc, MochaObject *obj,
MochaTag tag, MochaDatum *dp)
{
MochaJavaPackage *package = obj->data;
MochaAtom *atom;
char *str, *cp;
switch(tag) {
case MOCHA_STRING:
/* convert '/' to '.' so it looks like the entry syntax */
if (!package->name)
break;
str = PR_smprintf("[JavaPackage %s]", package->name);
if (!str) {
MOCHA_ReportOutOfMemory(mc);
return MOCHA_FALSE;
}
for (cp = str; *cp != '\0'; cp++)
if (*cp == '/')
*cp = '.';
atom = MOCHA_Atomize(mc, str);
if (!atom) {
MOCHA_ReportOutOfMemory(mc);
return MOCHA_FALSE;
}
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_STRING,
MDF_TAINTED, MOCHA_TAINT_JAVA,
u.atom, atom);
free(str);
break;
case MOCHA_OBJECT:
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_OBJECT,
MDF_TAINTED, MOCHA_TAINT_JAVA,
u.obj, obj);
break;
default:
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_UNDEF,
MDF_TAINTED, MOCHA_TAINT_JAVA,
u.ptr, 0);
break;
}
return MOCHA_TRUE;
}
static void
javapackage_finalize(MochaContext *mc, MochaObject *obj)
{
MochaJavaPackage *package = obj->data;
/* get rid of the private data */
if (package->name)
free(package->name);
free(package);
obj->data = 0;
}
static MochaClass javapackage_class = {
"JavaPackage",
MOCHA_PropertyStub, javapackage_set_property, javapackage_list_properties,
javapackage_resolve_name, javapackage_convert, javapackage_finalize
};
/* needs pointer to javapackage_class */
static MochaBoolean
javapackage_resolve_name(MochaContext *mc, MochaObject *obj, const char *name)
{
MochaJavaPackage *package = obj->data;
char *fullname;
int namelen = strlen(name);
ClassClass *cb;
MochaObject *mo;
MochaDatum d;
if (package->name) {
int packagelen = strlen(package->name);
fullname = (char*) malloc(packagelen + namelen + 2);
if (!fullname) {
MOCHA_ReportOutOfMemory(mc);
return MOCHA_FALSE;
}
strcpy(fullname, package->name);
fullname[packagelen] = '/';
strcpy(fullname + packagelen + 1, name);
fullname[packagelen + namelen + 1] = '\0';
} else {
fullname = (char *) malloc(namelen + 1);
if (!fullname) {
MOCHA_ReportOutOfMemory(mc);
return MOCHA_FALSE;
}
strcpy(fullname, name);
fullname[namelen] = '\0';
}
PR_LOG(Moja, debug, ("looking for java class \"%s\"", fullname));
/* see if the name is a class */
cb = mocha_FindJavaClass(mc, fullname, 0);
/* if not, it's a package */
if (!cb) {
MochaJavaPackage *newpackage = malloc(sizeof(MochaJavaPackage));
if (!newpackage) {
MOCHA_ReportOutOfMemory(mc);
free(fullname);
return MOCHA_FALSE;
}
PR_LOG(Moja, debug, ("creating package %s", fullname));
newpackage->name = fullname;
newpackage->lastslot = 0;
mo = MOCHA_NewObject(mc, &javapackage_class, newpackage, 0, 0, 0, 0);
} else {
/* reflect the Class object */
mo = MOCHA_ReflectJClassToMObject(mc, cb);
}
if (!mo) {
/* XXX error message? */
return MOCHA_FALSE;
}
MOCHA_INIT_FULL_DATUM(mc, &d, MOCHA_OBJECT,
MDF_READONLY | MDF_ENUMERATE | MDF_TAINTED,
MOCHA_TAINT_JAVA, u.obj, mo);
return MOCHA_SetProperty(mc, obj, name, ++package->lastslot, d);
}
/**** **** **** **** **** **** **** **** ****/
struct MochaJava {
MochaJavaType type; /* object / array / class */
HObject * handle; /* handle to the java Object */
ClassClass * cb; /* classblock, or element
* classblock for array of
* object */
char * signature; /* array element signature */
MochaSlot nextSlot; /* next slot to assign */
};
/* look up the slot name by index - this is used to pass the
* property name from java_resolve_name to java_get_property */
static MochaBoolean
java_slot_to_name(MochaContext *mc, MochaObject *obj, MochaSlot slot,
MochaAtom **atomp)
{
char buf[20];
MochaProperty *prop;
MochaAtom *atom;
MochaSymbol *sym;
PR_snprintf(buf, sizeof(buf), "%d", slot);
atom = mocha_Atomize(mc, buf, ATOM_NUMBER);
if (!atom) {
MOCHA_ReportOutOfMemory(mc);
return MOCHA_FALSE;
}
atom->fval = slot;
if (!mocha_LookupSymbol(mc, obj->scope, atom, MLF_GET, &sym)) {
/* this should never happen, since resolve_name should have
* added a property with this slot number already */
PR_ASSERT(0);
return MOCHA_FALSE;
}
PR_ASSERT(sym && sym->type == SYM_PROPERTY);
prop = sym_property(sym);
for (sym = prop->lastsym; sym; sym = sym->next) {
if (sym_atom(sym) != atom) {
/* must be a named property symbol */
*atomp = sym_atom(sym);
return MOCHA_TRUE;
}
}
return MOCHA_FALSE;
}
static struct fieldblock *
java_lookup_field(MochaContext *mc, ClassClass *cb,
PRBool isStatic, const char *name)
{
while (cb) {
int i;
for (i = 0; i < cb->fields_count; 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 = unhand(cbSuperclass(cb));
else
cb = 0;
}
return 0;
}
static MochaBoolean
java_get_property(MochaContext *mc, MochaObject *obj, MochaSlot slot,
MochaDatum *dp)
{
MochaAtom *atom;
MochaObject *slotobj;
/* XXX reflect a getter/setter pair as a property! */
if (!java_slot_to_name(mc, obj, slot, &atom)) {
PR_LOG(Moja, error, ("couldn't find a name for slot %d",
slot));
return MOCHA_FALSE;
}
PR_LOG(Moja, debug, ("looked up slot \"%s\" with index %d",
MOCHA_GetAtomName(mc, atom), slot));
slotobj = mocha_reflectJavaSlot(mc, obj, atom);
if (!slotobj)
return MOCHA_FALSE;
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_OBJECT, MDF_ENUMERATE | MDF_TAINTED,
MOCHA_TAINT_JAVA, u.obj, slotobj);
return MOCHA_TRUE;
}
static MochaBoolean
java_set_property(MochaContext *mc, MochaObject *obj, MochaSlot slot,
MochaDatum *dp)
{
MochaJava *java = obj->data;
ClassClass *cb = java->cb;
struct fieldblock *fb = 0;
MochaAtom *atom;
const char *name;
if (!java_slot_to_name(mc, obj, slot, &atom)) {
PR_LOG(Moja, error, ("couldn't find a name for slot %d",
slot));
return MOCHA_FALSE;
}
name = MOCHA_GetAtomName(mc, atom);
PR_LOG(Moja, debug, ("looked up slot \"%s\" with index %d",
name, slot));
fb = java_lookup_field(mc, cb, java->type == JAVA_CLASS, name);
if (!fb) {
MOCHA_ReportError(mc, "no Java %sfield found with name %s",
(java->type == JAVA_CLASS ? "static " : ""),
name);
return MOCHA_FALSE;
}
if (mocha_convertMDatumToJField(mc, dp, java->handle, fb))
return MOCHA_TRUE;
MOCHA_ReportError(mc, "can't set Java field %s", name);
return MOCHA_FALSE;
}
static MochaBoolean
java_list_one_property(MochaContext *mc, MochaObject *obj,
struct fieldblock *fb)
{
MochaJava *java = obj->data;
MochaAtom *atom;
MochaSymbol *sym;
PR_ASSERT(java->type == JAVA_OBJECT || java->type == JAVA_CLASS);
if (!(fb->access & ACC_PUBLIC) ||
!CHECK_STATIC(java->type == JAVA_CLASS, fb)) {
return MOCHA_TRUE;
}
atom = mocha_Atomize(mc, fieldname(fb), ATOM_HELD | ATOM_NAME);
if (!atom) {
MOCHA_ReportOutOfMemory(mc);
return MOCHA_FALSE;
}
if (!mocha_LookupSymbol(mc, obj->scope, atom, MLF_GET, &sym)) {
mocha_DropAtom(mc, atom);
return MOCHA_FALSE;
}
if (!sym) {
MochaSlot slot = java->nextSlot++;
MochaDatum d = MOCHA_null;
d.flags |= MDF_ENUMERATE;
mocha_SetProperty(mc, obj->scope, atom, slot, d);
}
mocha_DropAtom(mc, atom);
return MOCHA_TRUE;
}
static MochaBoolean
java_list_properties(MochaContext *mc, MochaObject *obj)
{
MochaJava *java = obj->data;
ClassClass *cb = java->cb;
while (cb) {
int i;
for (i = 0; i < cb->fields_count; i++) {
struct fieldblock *fb = cbFields(cb) + i;
if (!java_list_one_property(mc, obj, fb))
return MOCHA_FALSE;
}
for (i = cb->methods_count; i--;) {
struct methodblock *mb = cbMethods(cb) + i;
if (!java_list_one_property(mc, obj, &mb->fb))
return MOCHA_FALSE;
}
/* check the parent */
if (cbSuperclass(cb))
cb = unhand(cbSuperclass(cb));
else
cb = 0;
}
return MOCHA_TRUE;
}
static MochaBoolean
java_resolve_name(MochaContext *mc, MochaObject *obj, const char *name)
{
MochaJava *java = obj->data;
MochaSlot slot;
MochaDatum d;
PR_LOG(Moja, debug, ("resolve_name(0x%x) (handle 0x%x)",
obj, java->handle));
/* the slot number we choose for this property is unimportant:
* when we arrive in java_get_property or java_set_property we
* will look at the properties to convert it back into a name */
slot = java->nextSlot++;
PR_LOG(Moja, debug, ("field \"%s\" -> slot %d", name, slot));
d = MOCHA_null;
d.flags |= MDF_ENUMERATE;
return MOCHA_SetProperty(mc, obj, name, slot, d);
}
static void
java_finalize(MochaContext *mc, MochaObject *obj)
{
MochaJava *java = obj->data;
/* remove it from the reflection table */
if (java->type==JAVA_CLASS) {
PR_LOG(Moja, debug, ("removing class 0x%x from table", java->cb));
PR_EnterMonitor(javaReflectionsMonitor);
PR_HashTableRemove(javaReflections, java->cb);
PR_ExitMonitor(javaReflectionsMonitor);
} else {
PR_LOG(Moja, debug, ("removing handle 0x%x from table", java->handle));
PR_EnterMonitor(javaReflectionsMonitor);
PR_HashTableRemove(javaReflections, java->handle);
PR_ExitMonitor(javaReflectionsMonitor);
}
/* get rid of the private data */
free(java);
obj->data = 0;
#ifdef DEBUG
obj->data = (void*)0xdeadbeef;
#endif
}
static MochaBoolean
java_convert(MochaContext *mc, MochaObject *obj, MochaTag tag,
MochaDatum *dp)
{
MochaJava *java = obj->data;
PR_LOG(Moja, debug, ("java_convert to %d", tag));
switch (tag) {
case MOCHA_OBJECT:
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_OBJECT,
MDF_TAINTED, MOCHA_TAINT_JAVA,
u.obj, obj);
break;
case MOCHA_FUNCTION:
/* only classes convert to functions (constructors) */
if (java->type != JAVA_CLASS) {
/* random java objects do not convert to functions */
MOCHA_ReportError(mc, "can't convert Java object %s to function",
"XXX" /* XXX obj->scope.name */);
return MOCHA_FALSE;
} else {
MochaFunction *fun;
MochaAtom *atom;
PR_LOG(Moja, debug, ("making a constructor\n"));
atom = MOCHA_Atomize(mc, classname(java->cb));
if (!atom) {
MOCHA_ReportOutOfMemory(mc);
return MOCHA_FALSE;
}
fun = mocha_NewFunction(mc, mocha_javaConstructorWrapper, 0,
0, atom);
/* XXX the private data for the function object is the
* classblock: gc problem? */
fun->object.data = java->cb;
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_FUNCTION,
MDF_TAINTED, MOCHA_TAINT_JAVA,
u.fun, fun);
}
break;
case MOCHA_STRING:
/* either pull out the string or call toString */
return mocha_convertJObjectToMString(mc, java->handle,
java->type==JAVA_CLASS, dp);
case MOCHA_NUMBER:
/* call doubleValue() */
return mocha_convertJObjectToMNumber(mc, java->handle,
java->type==JAVA_CLASS, dp);
case MOCHA_BOOLEAN:
/* call booleanValue() */
return mocha_convertJObjectToMBoolean(mc, java->handle,
java->type==JAVA_CLASS, dp);
default:
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_UNDEF,
MDF_TAINTED, MOCHA_TAINT_JAVA,
u.ptr, 0);
break;
}
return MOCHA_TRUE;
}
static MochaClass java_class = {
"Java",
java_get_property, java_set_property, java_list_properties,
java_resolve_name, java_convert, java_finalize
};
/**** **** **** **** **** **** **** **** ****/
#define ARRAY_LENGTH_SLOT -1
static MochaBoolean
javaarray_get_property(MochaContext *mc, MochaObject *obj,
MochaSlot slot, MochaDatum *dp)
{
MochaJava *array = obj->data;
HObject *ho = array->handle;
if (slot == ARRAY_LENGTH_SLOT) {
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_NUMBER,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA,
u.fval, (MochaFloat) obj_length(ho));
return MOCHA_TRUE;
} else if (slot >= 0 && slot < obj_length(ho)) {
int size = sizearray(obj_flags(ho), 1);
char *addr = ((char*) unhand(ho)) + slot * size;
if (!mocha_convertJElementToMDatum(mc, addr, array->signature,
dp, MOCHA_UNDEF)) {
MOCHA_ReportError(mc,
"can't convert Java array element to JavaScript datum");
return MOCHA_FALSE;
}
return MOCHA_TRUE;
}
MOCHA_ReportError(mc, "Java array index %d out of range", slot);
return MOCHA_FALSE;
}
static MochaBoolean
javaarray_set_property(MochaContext *mc, MochaObject *obj,
MochaSlot slot, MochaDatum *dp)
{
MochaJava *array = obj->data;
HObject *ho = array->handle;
if (slot == ARRAY_LENGTH_SLOT) {
MOCHA_ReportError(mc, "can't set length of a Java array");
return MOCHA_FALSE;
} else if (slot >= 0 && slot < obj_length(ho)) {
int size = sizearray(obj_flags(ho), 1);
char *addr = ((char*) unhand(ho)) + slot * size;
if (!mocha_convertMDatumToJElement(mc, dp, addr,
array->signature, array->cb, 0)) {
MOCHA_ReportError(mc, "illegal assignment to Java array element");
return MOCHA_FALSE;
}
return MOCHA_TRUE;
}
MOCHA_ReportError(mc, "Java array index %d out of range", slot);
return MOCHA_FALSE;
}
static MochaBoolean
javaarray_list_properties(MochaContext *mc, MochaObject *obj)
{
MochaJava *array = obj->data;
HObject *ho = array->handle;
MochaSlot slot;
MochaDatum d;
for (slot = 0; slot < obj_length(ho); slot++) {
int size = sizearray(obj_flags(ho), 1);
char *addr = ((char*) unhand(ho)) + slot * size;
if (!mocha_convertJElementToMDatum(mc, addr, array->signature,
&d, MOCHA_UNDEF)) {
return MOCHA_FALSE;
}
if (!mocha_SetProperty(mc, obj->scope, 0, slot, d)) {
return MOCHA_FALSE;
}
}
return MOCHA_TRUE;
}
static MochaBoolean
javaarray_resolve_name(MochaContext *mc, MochaObject *obj, const char *name)
{
MOCHA_ReportError(mc, "Java array doesn't have a field named \"%s\"", name);
return MOCHA_FALSE;
}
static void
javaarray_finalize(MochaContext *mc, MochaObject *obj)
{
MochaJava *array = obj->data;
/* get rid of the private data */
/* array->signature is a static string */ ;
array->signature = 0;
/* remove it from the reflection table */
java_finalize(mc, obj);
}
static MochaBoolean
javaarray_convert(MochaContext *mc, MochaObject *obj, MochaTag tag,
MochaDatum *dp)
{
switch (tag) {
case MOCHA_OBJECT:
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_OBJECT,
MDF_TAINTED, MOCHA_TAINT_JAVA, u.obj, obj);
break;
case MOCHA_STRING:
/* XXX how should arrays convert to strings? */
default:
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_UNDEF,
MDF_TAINTED, MOCHA_TAINT_JAVA,
u.ptr, 0);
break;
}
return MOCHA_TRUE;
}
static MochaClass javaarray_class = {
"JavaArray",
javaarray_get_property, javaarray_set_property, javaarray_list_properties,
javaarray_resolve_name, javaarray_convert, javaarray_finalize
};
static MochaPropertySpec javaarray_props[] = {
{"length", ARRAY_LENGTH_SLOT },
{0}
};
/**** **** **** **** **** **** **** **** ****/
/* 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(MochaContext *mc, char *sig, ClassClass *from)
{
if (sig[1] != '\0') {
char *name = getSignatureBase(sig);
ClassClass *cb;
if (!name) {
MOCHA_ReportOutOfMemory(mc);
return 0;
}
cb = mocha_FindJavaClass(mc, name, from);
free(name);
return cb;
} else {
/* special hack: if the signature is "L" it means
* just use the fromClass */
return from;
}
}
/* this doesn't need to run in a java env, but it may throw an
* exception so we need a temporary one */
static MochaBoolean
mocha_isSubclassOf(MochaContext *mc, ClassClass *cb1, ClassClass *cb2)
{
ExecEnv *ee = (ExecEnv *)mozenv;
Bool ret;
exceptionClear(ee);
ret = is_subclass_of(cb1, cb2, ee);
if (exceptionOccurred(ee)) {
#ifdef DEBUG
char *message;
/* exceptionDescribe(ee); */
/* XXX 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);
message = allocCString(hdetail);
PR_LOG(Moja, error,
("exception in is_subclass_of %s (\"%s\")",
classname(cb), message));
free(message);
#endif
exceptionClear(ee);
return MOCHA_FALSE;
}
return ret ? MOCHA_TRUE : MOCHA_FALSE;
}
static MochaBoolean
mocha_convertMDatumToJSObject(HObject **objp, MochaContext *mc,
MochaObject *mpo, ClassClass *paramcb,
MochaBoolean checkOnly)
{
MochaObject *mo = mpo;
/* check if a JSObject would be an acceptable argument */
if (mocha_isSubclassOf(mc, JSObjectClassBlock, paramcb)) {
/* JSObject is ok, convert to one */
/* a mocha object which is not really a java object
* converts to JSObject */
if (!checkOnly) {
/* reflect the object as a JSObject */
*objp = MOCHA_ReflectMObjectToJObject(mc, mo);
if (!objp)
return MOCHA_FALSE;
}
return MOCHA_TRUE;
}
return MOCHA_FALSE;
}
static MochaBoolean
mocha_convertMDatumToJArray(HObject **objp, MochaContext *mc,
MochaDatum *arg, char *sig, ClassClass *fromClass,
MochaBoolean checkOnly, int *cost)
{
/* XXX bump the cost counter when necessary */
MochaJava *java;
HArrayOfObject *harr;
ClassClass *acb;
ClassClass *paramcb;
MochaObject *mo;
char *elementsig;
ClassClass *elementClazz;
/* the only legal conversions are from null or from a java
* array */
if (arg->tag != MOCHA_OBJECT)
return MOCHA_FALSE;
mo = arg->u.obj;
/* can always pass null */
if (!mo) {
if (!checkOnly)
*objp = 0;
return MOCHA_TRUE;
}
/* otherwise, it must be a mocha reflection of a java array */
if (mo->clazz != &javaarray_class)
return MOCHA_FALSE;
java = (MochaJava *) mo->data;
sig++; /* skip to array element type */
switch (*sig) {
case SIGNATURE_CLASS: /* object array */
paramcb = getSignatureClass(mc, sig, fromClass);
PR_LOG(Moja, debug, ("desired array element signature \"%s\"(0x%x)",
sig, paramcb));
if (!paramcb) {
PR_LOG(Moja, warn,
("couldn't find class for signature \"%s\"\n", sig));
return MOCHA_FALSE;
}
harr = (HArrayOfObject *) java->handle;
acb = (ClassClass *)(unhand(harr)->body[obj_length(harr)]);
if (!acb) {
PR_LOG(Moja, warn,
("couldn't find class of array element\n"));
return MOCHA_FALSE;
}
/* elements must convert */
if (mocha_isSubclassOf(mc, acb, paramcb)) {
if (!checkOnly)
*objp = (HObject *) harr;
return MOCHA_TRUE;
}
break;
case SIGNATURE_ARRAY: /* nested array */
/* XXX 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. */
/* XXX throw an exception */
break;
default: /* primitive array */
/* for any other array, the signature must match exactly */
if (mocha_JArrayElementType(java->handle,
&elementsig, &elementClazz)) {
if (elementsig[0] == sig[0]) {
if (!checkOnly)
*objp = java->handle;
return MOCHA_TRUE;
}
}
break;
}
return MOCHA_FALSE;
}
MochaBoolean
mocha_convertMDatumToJObject(HObject **objp, MochaContext *mc,
MochaDatum *arg, char *sig, ClassClass *fromClass,
MochaBoolean checkOnly, int *cost)
{
/* XXX bump the cost counter when necessary */
ClassClass *paramcb = sig ? getSignatureClass(mc, sig, fromClass)
: mocha_FindJavaClass(mc, "java/lang/Object", 0); /* XXX */
PR_LOG(Moja, debug, ("desired argument class signature \"%s\"(0x%x)",
sig, paramcb));
if (!paramcb) {
PR_LOG(Moja, warn,
("couldn't find class for signature \"%s\"", sig));
return MOCHA_FALSE;
}
#if 0
if (arg->taint != MOCHA_TAINT_IDENTITY && arg->taint != MOCHA_TAINT_JAVA) {
MOCHA_ReportError(mc,
"can't convert tainted JavaScript datum to Java object");
return MOCHA_FALSE;
}
#endif
/* mocha wrappers around java objects do the same check
* as the java compiler */
/* XXX except classes, which become JSObject (circular problem?) */
switch (arg->tag) {
case MOCHA_OBJECT: {
MochaObject *mo = arg->u.obj;
/* null is always a valid object */
if (!mo) {
if (!checkOnly)
*objp = 0;
return MOCHA_TRUE;
}
if (mo->clazz == &java_class || mo->clazz == &javaarray_class) {
MochaJava *java = mo->data;
HObject *ho = java->handle;
ClassClass *cb;
/* class reflections convert to JSObject or String only */
if (java->type == JAVA_CLASS) {
if (mocha_convertMDatumToJSObject(objp, mc, mo,
paramcb, checkOnly))
return MOCHA_TRUE;
} else {
/* get the classblock for the java argument type */
cb = obj_array_classblock(ho);
PR_LOG(Moja, debug, ("actual argument 0x%x, class 0x%x",
ho, cb));
/* check against the expected class */
if (mocha_isSubclassOf(mc, cb, paramcb)) {
if (!checkOnly)
*objp = ho;
return MOCHA_TRUE;
}
}
} else {
/* otherwise see if it will take a JSObject */
if (mocha_convertMDatumToJSObject(objp, mc, mo,
paramcb, checkOnly))
return MOCHA_TRUE;
}
}
break;
case MOCHA_FUNCTION: {
MochaObject *mo = arg->u.obj;
if (mocha_convertMDatumToJSObject(objp, mc, mo, paramcb, checkOnly))
return MOCHA_TRUE;
}
break;
case MOCHA_NUMBER: /* java.lang.Double */
if (mocha_isSubclassOf(mc, DoubleClassBlock, paramcb)) {
/* Float is ok */
if (!checkOnly)
*objp = mocha_ConstructJava(mc, "java/lang/Double",
0, "(D)", arg->u.fval);
return MOCHA_TRUE;
}
break;
case MOCHA_BOOLEAN: /* java.lang.Boolean */
/* XXX this should return Boolean.TRUE or Boolean.FALSE
* instead of constructing a new one? */
if (mocha_isSubclassOf(mc, BooleanClassBlock, paramcb)) {
if (!checkOnly)
*objp = mocha_ConstructJava(mc,
"java/lang/Boolean",
0, "(Z)", arg->u.bval);
return MOCHA_TRUE;
}
break;
}
/* last ditch attempt: is a String acceptable? */
{
if (mocha_isSubclassOf(mc, StringClassBlock, paramcb)) {
MochaAtom *atom;
/* string is ok, convert to one */
if (mocha_DatumToString(mc, *arg, &atom)) {
/* make a java String from str */
if (!checkOnly) {
*objp = (JHandle *)
makeJavaString((char*)MOCHA_GetAtomName(mc, atom),
strlen(MOCHA_GetAtomName(mc, atom)));
}
mocha_DropAtom(mc, atom);
return MOCHA_TRUE;
}
}
}
return MOCHA_FALSE;
}
MochaBoolean
mocha_convertJObjectToMDatum(MochaContext *mc, MochaDatum *dp,
HObject *ho)
{
MochaObject *mo;
FINISH_MOCHA_JAVA_INIT(mc);
if (!ho) {
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_OBJECT,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA,
u.obj, 0);
return MOCHA_TRUE;
}
/* if it's a JSObject, pull out the original mocha
* object when it comes back in to the mocha world */
if (obj_array_classblock(ho) == JSObjectClassBlock) {
jref jso = (jref) ho /* XXX */;
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_OBJECT,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA, u.obj,
(MochaObject *)
get_netscape_javascript_JSObject_internal((JRIEnv*)EE(), jso));
return MOCHA_TRUE;
}
/* instances of java.lang.String are wrapped so we can
* call methods on them, but they convert to a mocha string
* if used in a string context */
/* otherwise, wrap it */
mo = MOCHA_ReflectJObjectToMObject(mc, ho);
if (!mo) {
MOCHA_ReportError(mc,
"can't convert Java object to JavaScript object");
return MOCHA_FALSE;
}
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_OBJECT,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA,
u.obj, mo);
return MOCHA_TRUE;
}
PR_STATIC_CALLBACK(PRHashNumber)
java_hashHandle(void *key)
{
return (PRHashNumber)key;
}
static int
java_pointerEq(void *v1, void *v2)
{
return v1 == v2;
}
HObject *
MOCHA_ReflectMObjectToJObject(MochaContext *mc, MochaObject *mo)
{
jref jso, tmp;
FINISH_MOCHA_JAVA_INIT(mc);
/* see if it's already there */
PR_EnterMonitor(mochaReflectionsMonitor);
jso = /* XXX */ PR_HashTableLookup(mochaReflections, mo);
/* release the monitor temporarily while we call the
* java constructor, which could contain arbitrary
* scariness */
/*
** XXX Actually, the right thing to do here if the lookup fails is to
** put a placeholder in the hash table, saying it's in progress. And
** if you get the placeholder, you wait on the monitor.
*/
PR_ExitMonitor(mochaReflectionsMonitor);
if (jso)
return (HObject *) jso /*XXX*/;
/* nope, reflect it */
jso = (jref) /*XXX*/
mocha_ConstructJavaPrivileged(mc, 0,
JSObjectClassBlock, "()");
/* XXX check for exceptions */
if (!jso) {
return 0;
}
set_netscape_javascript_JSObject_internal((JRIEnv*)EE(), jso, 0);
/* need to check again since we released the monitor */
PR_EnterMonitor(mochaReflectionsMonitor);
tmp = /* XXX */ PR_HashTableLookup(mochaReflections, mo);
if (tmp) {
PR_ExitMonitor(mochaReflectionsMonitor);
/* let the one we constructed die in gc and use the one from
* the table */
return (HObject *) tmp;
}
MOCHA_HoldObject(mc, mo);
set_netscape_javascript_JSObject_internal((JRIEnv*)EE(), jso,
(jint) mo);
/* add it to the table */
PR_HashTableAdd(mochaReflections, mo, jso);
PR_ExitMonitor(mochaReflectionsMonitor);
return (HObject *) jso;/*XXX*/
}
void
mocha_removeReflection(MochaObject *mo)
{
/* remove it from the reflection table */
PR_LOG(Moja, debug, ("removing mocha object 0x%x from table", mo));
PR_EnterMonitor(mochaReflectionsMonitor);
PR_HashTableRemove(mochaReflections, mo);
PR_ExitMonitor(mochaReflectionsMonitor);
}
static MochaBoolean
mocha_convertMDatumToJValue(MochaContext *mc, MochaDatum *dp,
OBJECT *addr, char *sig, ClassClass *fromClass,
MochaBoolean checkOnly, char **sigRestPtr,
int *cost)
{
Java8 tdub;
char *p = sig;
MochaDatum td;
#if 0
if (dp->taint != MOCHA_TAINT_IDENTITY && dp->taint != MOCHA_TAINT_JAVA) {
MOCHA_ReportError(mc,
"can't convert tainted JavaScript datum to Java value");
return MOCHA_FALSE;
}
#endif
switch (*p) {
case SIGNATURE_BOOLEAN:
if (dp->tag == MOCHA_BOOLEAN)
td = *dp;
else {
/* XXX 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 */
/* XXX */ return MOCHA_FALSE;
(*cost)++;
if (!MOCHA_ConvertDatum(mc, *dp, MOCHA_BOOLEAN, &td))
return MOCHA_FALSE;
}
if (!checkOnly)
*(long*)addr = (td.u.bval == MOCHA_TRUE);
break;
case SIGNATURE_SHORT:
case SIGNATURE_BYTE:
case SIGNATURE_CHAR:
case SIGNATURE_INT:
/* XXX should really do a range check... */
if (dp->tag == MOCHA_NUMBER)
td = *dp;
else {
/* XXX */ return MOCHA_FALSE;
(*cost)++;
if (!MOCHA_ConvertDatum(mc, *dp, MOCHA_NUMBER, &td))
return MOCHA_FALSE;
}
if (!checkOnly)
*(long*)addr = (long) td.u.fval;
break;
case SIGNATURE_LONG:
if (dp->tag == MOCHA_NUMBER)
td = *dp;
else {
/* XXX */ return MOCHA_FALSE;
(*cost)++;
if (!MOCHA_ConvertDatum(mc, *dp, MOCHA_NUMBER, &td))
return MOCHA_FALSE;
}
if (!checkOnly) {
double dval;
int64 llval;
dval = (double) td.u.fval;
LL_D2L(llval, dval);
SET_INT64(tdub, addr, llval);
}
break;
case SIGNATURE_FLOAT:
if (dp->tag == MOCHA_NUMBER)
td = *dp;
else {
/* XXX */ return MOCHA_FALSE;
(*cost)++;
if (!MOCHA_ConvertDatum(mc, *dp, MOCHA_NUMBER, &td))
return MOCHA_FALSE;
}
if (!checkOnly)
*(float*)addr = td.u.fval;
break;
case SIGNATURE_DOUBLE:
if (dp->tag == MOCHA_NUMBER)
td = *dp;
else {
/* XXX */ return MOCHA_FALSE;
(*cost)++;
if (!MOCHA_ConvertDatum(mc, *dp, MOCHA_NUMBER, &td))
return MOCHA_FALSE;
}
if (!checkOnly)
SET_DOUBLE(tdub, addr, (double) td.u.fval);
break;
case SIGNATURE_CLASS:
/* XXX cost? */
if (mocha_convertMDatumToJObject((HObject **)/*XXX*/addr,
mc, dp, p, fromClass,
checkOnly, cost)) {
while (*p != SIGNATURE_ENDCLASS) p++;
} else
return MOCHA_FALSE;
break;
case SIGNATURE_ARRAY:
/* XXX cost? */
if (mocha_convertMDatumToJArray((HObject **)/*XXX*/addr,
mc, dp, p, fromClass,
checkOnly, cost)) {
while (*p == SIGNATURE_ARRAY) p++; /* skip array beginning */
/* skip the element type */
if (*p == SIGNATURE_CLASS) {
while (*p != SIGNATURE_ENDCLASS) p++;
}
} else
return MOCHA_FALSE;
break;
default:
PR_LOG(Moja, warn,
("unknown value signature '%s'", p));
return MOCHA_FALSE;
break;
}
if (sigRestPtr)
*sigRestPtr = p+1;
return MOCHA_TRUE;
}
/*
* this code mimics the method overloading resolution
* done in java, for use in mocha->java calls
*/
/* push a MochaDatum 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 MochaBoolean
mocha_convertMToJArgs(MochaContext *mc, stack_item *optop,
struct methodblock *mb, int argc, MochaDatum *argv,
MochaBoolean checkOnly, char **sigRestPtr,
int *cost)
{
struct fieldblock *fb = &mb->fb;
char *sig = fieldsig(fb);
MochaDatum *dp = argv;
int argsleft = argc;
char *p;
void *addr;
*cost = 0;
for (p = sig + 1; *p != SIGNATURE_ENDFUNC; dp++,argsleft--) {
if (argsleft == 0) /* not enough arguments passed */
return MOCHA_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(Moja, warn,
("Invalid method signature '%s' for method '%s'\n",
fieldsig(fb), fieldname(fb)));
return MOCHA_FALSE;
break;
}
/* this bumps p to the next argument */
if (!mocha_convertMDatumToJValue(mc, dp, addr, p, fieldclass(&mb->fb),
checkOnly, &p, cost)) {
return MOCHA_FALSE;
}
}
if (argsleft > 0) {
/* too many arguments */
return MOCHA_FALSE;
}
if (sigRestPtr)
*sigRestPtr = p+1 /* go past the SIGNATURE_ENDFUNC */;
return MOCHA_TRUE;
}
/* java array elements are packed with smaller sizes */
static MochaBoolean
mocha_convertMDatumToJElement(MochaContext *mc, MochaDatum *dp,
char *addr, char *sig, ClassClass *fromClass,
char **sigRestPtr)
{
long tmp[2];
int cost = 0;
if (!mocha_convertMDatumToJValue(mc, dp, (OBJECT *)&tmp,
sig, fromClass,
MOCHA_FALSE, sigRestPtr, &cost))
return MOCHA_FALSE;
switch (sig[0]) {
case SIGNATURE_BOOLEAN:
*(char*)addr = *(long*)&tmp;
break;
case SIGNATURE_BYTE:
*(char*)addr = *(long*)&tmp;
break;
case SIGNATURE_CHAR:
*(unicode*)addr = *(long*)&tmp;
break;
case SIGNATURE_SHORT:
*(signed short*)addr = *(long*)&tmp;
break;
case SIGNATURE_INT:
*(int32_t*)addr = *(long*)&tmp;
break;
case SIGNATURE_LONG:
*(int64_t*)addr = *(int64_t*)&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(Moja, warn,
("unknown value signature '%s'\n", sig[0]));
return MOCHA_FALSE;
break;
}
return MOCHA_TRUE;
}
static OBJECT *
getJavaFieldAddress(HObject *ho, struct fieldblock *fb)
{
OBJECT *addr; /* address of the java value */
char *sig = fieldsig(fb);
if (fb->access & ACC_PUBLIC) {
if (fb->access & ACC_STATIC) {
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;
} else {
return 0;
}
}
static MochaBoolean
mocha_convertMDatumToJField(MochaContext *mc, MochaDatum *dp,
HObject *ho, struct fieldblock *fb)
{
OBJECT *addr = getJavaFieldAddress(ho, fb);
int cost = 0;
if (!addr) {
MOCHA_ReportError(mc, "can't access field %s", fieldname(fb));
return MOCHA_FALSE;
}
return mocha_convertMDatumToJValue(mc, dp, addr,
fieldsig(fb), fieldclass(fb),
MOCHA_FALSE, 0, &cost);
}
/* returns -1 if the given method is not applicable to the arguments,
* or a cost if it is */
static int
methodIsApplicable(MochaContext *mc,
struct methodblock *mb, MochaBoolean isStatic,
const char *name, int argc, MochaDatum *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 (mocha_convertMToJArgs(mc, 0, mb, argc, argv,
MOCHA_TRUE /* checkOnly */,
0 /* &sigRest */, &cost))
return cost;
return -1;
}
/* XXX 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 MochaBoolean
methodIsMoreSpecific(MochaContext *mc,
struct methodblock *mb1, struct methodblock *mb2)
{
char *sig1 = fieldsig(&mb1->fb) + 1; /* skip '(' */
char *sig2 = fieldsig(&mb2->fb) + 1;
/* XXX fill this in! */
return MOCHA_TRUE;
/* go through the args */
while (*sig1 != SIGNATURE_ENDFUNC && *sig2 != SIGNATURE_ENDFUNC) {
if (*sig1 == SIGNATURE_CLASS) {
if (*sig2 == SIGNATURE_CLASS) {
ClassClass *cb1 =
getSignatureClass(mc, sig1, fieldclass(&mb1->fb));
ClassClass *cb2 =
getSignatureClass(mc, sig2, fieldclass(&mb2->fb));
if (! mocha_isSubclassOf(mc, cb1, cb2))
return MOCHA_FALSE;
/* next argument */
while (*sig1++ != SIGNATURE_ENDCLASS);
while (*sig2++ != SIGNATURE_ENDCLASS);
}
}
}
if (*sig1 != *sig2) {
return MOCHA_FALSE;
/* arg number mismatch */
}
return MOCHA_TRUE;
}
/* based on sun.tools.java.Environment */
/**
* Return true if an implicit cast from this type to
* the given type is allowed.
*/
#if 0
static bool_t
implicitCast(MochaContext *mc, char *sigfrom, char *sigto)
{
switch(*sigfrom) {
case SIGNATURE_BYTE:
if (*sigto == SIGNATURE_SHORT) return TRUE;
case SIGNATURE_SHORT:
case SIGNATURE_CHAR:
if (*sigto == SIGNATURE_INT) return TRUE;
case SIGNATURE_INT:
if (*sigto == SIGNATURE_LONG) return TRUE;
case SIGNATURE_LONG:
if (*sigto == SIGNATURE_FLOAT) return TRUE;
case SIGNATURE_FLOAT:
if (*sigto == SIGNATURE_DOUBLE) return TRUE;
case SIGNATURE_DOUBLE:
if (*sigfrom == *sigto) return TRUE;
return FALSE;
break;
#if 0
case SIGNATURE_NULL:
return (*sigto == SIGNATURE_NULL ||
*sigto == SIGNATURE_CLASS ||
*sigto == SIGNATURE_ARRAY);
break;
#endif
case SIGNATURE_ARRAY:
switch (*sigto) {
case SIGNATURE_ARRAY: {
/* find the base types */
char *basefrom = strdup(sigfrom);
char *baseto = strdup(sigto);
do {
char *tmpfrom = basefrom;
char *tmpto = baseto;
basefrom = getSignatureBase(basefrom);
if (!basefrom)
return FALSE;
baseto = getSignatureBase(baseto);
if (!baseto)
return FALSE;
free(tmpfrom);
free(tmpto);
} while (*basefrom == SIGNATURE_ARRAY &&
*baseto == SIGNATURE_ARRAY);
if ((*basefrom == SIGNATURE_ARRAY ||
(*basefrom == SIGNATURE_CLASS))
&&
(*baseto == SIGNATURE_ARRAY ||
(*baseto == SIGNATURE_CLASS))) {
bool_t ret = methodIsMoreSpecific(mc, basefrom, baseto);
free(basefrom); free(baseto);
return ret;
} else {
bool_t ret = (*basefrom == *baseto);
free(basefrom); free(baseto);
return ret;
}
}
break;
case SIGNATURE_CLASS:
/* must be Object or Cloneable */
/* XXX is the string test safe? */
return (! strncmp(sigto, "LObject;", strlen("LObject;")) ||
! strncmp(sigto, "LCloneable;", strlen("LCloneable;")));
break;
default:
return FALSE;
break;
}
break;
#if 0 /* XXX */
case SIGNATURE_CLASS:
if (*sigto == SIGNATURE_CLASS) {
ClassClass *cbfrom = getSignatureClass(mc, sigfrom);
ClassClass *cbto = getSignatureClass(mc, sigto);
return implementedBy(cbto, cbfrom);
} else {
return FALSE;
}
break;
#endif
default:
return FALSE;
break;
}
}
#endif
static MochaBoolean
mocha_convertJValueToMDatum(MochaContext *mc, OBJECT *addr, char *sig,
MochaDatum *dp, MochaTag desired)
{
Java8 tmp;
switch (sig[0]) {
case SIGNATURE_VOID:
*dp = MOCHA_void;
break;
case SIGNATURE_BYTE:
case SIGNATURE_CHAR:
case SIGNATURE_INT:
case SIGNATURE_SHORT:
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_NUMBER,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA,
u.fval, (long)*addr);
break;
case SIGNATURE_BOOLEAN:
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_BOOLEAN,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA,
u.bval, (long)*addr);
break;
case SIGNATURE_LONG:
{
int64 llval;
double dval;
llval = GET_INT64(tmp, addr);
LL_L2D(dval, llval);
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_NUMBER,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA,
u.fval, (MochaFloat) dval);
}
break;
case SIGNATURE_FLOAT:
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_NUMBER,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA,
u.fval, *(float*)addr);
break;
case SIGNATURE_DOUBLE:
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_NUMBER,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA,
u.fval, (MochaFloat)GET_DOUBLE(tmp, addr));
break;
case SIGNATURE_CLASS:
case SIGNATURE_ARRAY:
return mocha_convertJObjectToMDatum(mc, dp, *(HObject **) addr);
break;
default:
MOCHA_ReportError(mc, "unknown Java signature character '%c'", sig[0]);
return MOCHA_FALSE;
break;
}
if (desired != MOCHA_UNDEF) {
MochaDatum tmp = *dp;
return MOCHA_ConvertDatum(mc, tmp, desired, dp);
}
return MOCHA_TRUE;
}
/* java array elements are packed with smaller sizes */
static MochaBoolean
mocha_convertJElementToMDatum(MochaContext *mc, char *addr, char *sig,
MochaDatum *dp, MochaTag desired)
{
switch (sig[0]) {
case SIGNATURE_BYTE:
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_NUMBER,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA,
u.fval, *(char*)addr);
break;
case SIGNATURE_CHAR:
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_NUMBER,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA,
u.fval, *(unicode*)addr);
break;
case SIGNATURE_SHORT:
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_NUMBER,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA,
u.fval, *(signed short*)addr);
break;
case SIGNATURE_INT:
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_NUMBER,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA,
u.fval, *(int32_t*)addr);
break;
case SIGNATURE_BOOLEAN:
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_BOOLEAN,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA,
u.bval, *(int32_t*)addr);
break;
default:
return mocha_convertJValueToMDatum(mc, (OBJECT *) addr, sig,
dp, desired);
break;
}
if (desired != MOCHA_UNDEF) {
MochaDatum tmp = *dp;
return MOCHA_ConvertDatum(mc, tmp, desired, dp);
}
return MOCHA_TRUE;
}
static MochaBoolean
mocha_convertJFieldToMDatum(MochaContext *mc, HObject *ho,
struct fieldblock *fb, MochaDatum *dp,
MochaTag desired)
{
OBJECT *addr = getJavaFieldAddress(ho, fb);
char *sig = fieldsig(fb);
if (addr)
return mocha_convertJValueToMDatum(mc, addr, sig, dp, desired);
else {
MOCHA_ReportError(mc, "can't access Java field %s",
fieldname(fb));
return MOCHA_FALSE;
}
}
static MochaBoolean
mocha_convertJObjectToMString(MochaContext *mc, HObject *ho,
bool_t isClass, MochaDatum *dp)
{
MochaAtom *atom;
if (isClass) {
char buf[256];
PR_snprintf(buf, sizeof(buf), "[JavaClass %s]",
classname(unhand((HClass*)ho)));
atom = MOCHA_Atomize(mc, buf);
if (!atom) {
MOCHA_ReportOutOfMemory(mc);
return MOCHA_FALSE;
}
} else {
HString *hstr;
char *str;
if (!ho)
return MOCHA_FALSE;
if (obj_classblock(ho) == StringClassBlock) {
/* it's a string already */
hstr = (HString*) ho;
} else {
/* call toString() to convert to a string */
if (!mocha_ExecuteJavaMethod(mc, &hstr, sizeof(hstr), ho,
"toString", "()Ljava/lang/String;",
0, FALSE))
return MOCHA_FALSE;
}
/* convert the java string to a mocha string */
str = allocCString(hstr);
atom = MOCHA_Atomize(mc, str);
if (!atom) {
sysFree(str);
MOCHA_ReportOutOfMemory(mc);
return MOCHA_FALSE;
}
sysFree(str);
}
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_STRING,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA,
u.atom, atom);
return MOCHA_TRUE;
}
static MochaBoolean
mocha_convertJObjectToMNumber(MochaContext *mc, HObject *ho,
bool_t isClass, MochaDatum *dp)
{
long foo[2], swap;
JRI_JDK_Java8 tmp;
double d;
if (isClass || !ho)
return MOCHA_FALSE;
if (!mocha_ExecuteJavaMethod(mc, foo, sizeof(foo), ho,
"doubleValue", "()D",
0, FALSE))
return MOCHA_FALSE;
swap = foo[0];
foo[0] = foo[1];
foo[1] = swap;
d = JRI_GET_DOUBLE(tmp, &foo[0]);
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_NUMBER,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA,
u.fval, d);
return MOCHA_TRUE;
}
static MochaBoolean
mocha_convertJObjectToMBoolean(MochaContext *mc, HObject *ho,
bool_t isClass, MochaDatum *dp)
{
long b;
if (isClass || !ho)
return MOCHA_FALSE;
if (!mocha_ExecuteJavaMethod(mc, &b, sizeof(b), ho,
"booleanValue", "()Z",
0, FALSE))
return MOCHA_FALSE;
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_BOOLEAN,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA,
u.bval, b);
return MOCHA_TRUE;
}
static MochaBoolean
java_returnAsMochaDatum(MochaContext *mc, MochaDatum *dp,
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 mocha_convertJValueToMDatum(mc, addr, sig, dp, MOCHA_UNDEF);
}
/*
* find the best method to call for a name and a bunch of mocha
* arguments. try each possible method, then find the most
* specific of the applicable methods. "most specific" is
* determined without reference to the mocha arguments: it is
* the same as "most specific" in the java sense of the word.
* XXX this could cause trouble since more methods will be applicable
* to a mocha call than for a similar java call!
*/
static struct methodblock *
matchMethod(MochaContext *mc, ClassClass *cb, bool_t isStatic,
const char *name, int argc, MochaDatum *argv)
{
int mindex;
int *isApplicable = 0; /* array holding costs */
struct methodblock *bestmb = 0;
PRBool isOverloaded = PR_FALSE;
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 = (int *) malloc(cb->methods_count * sizeof(int));
if (!isApplicable) {
MOCHA_ReportOutOfMemory(mc);
return 0;
}
for (mindex = cb->methods_count; mindex--;) {
struct methodblock *mb = cbMethods(cb) + mindex;
struct fieldblock *fb = &mb->fb;
isApplicable[mindex] = methodIsApplicable(mc, mb, isStatic,
name, argc, argv);
if (isApplicable[mindex] == -1)
continue;
PR_LOG(Moja, debug, ("found applicable method %s with sig %s",
fieldname(fb), fieldsig(fb)));
if (!bestmb) { /* first one found */
bestmb = mb;
continue;
}
isOverloaded = PR_TRUE;;
if (methodIsMoreSpecific(mc, 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 = unhand(cbSuperclass(cb));
else
cb = 0;
free(isApplicable);
isApplicable = 0;
}
/* if we didn't find anything, bail */
if (!bestmb)
return 0;
/* second pass: check that bestmb is more specific than all other
* applicable methods */
/* XXX if mb is equally specific, this is an ambiguous lookup */
#if 0
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;
/* hopefully we can disambiguate by the cost */
}
#endif
free(isApplicable);
return bestmb;
}
/* this is the callback to execute java bytecodes in the
* appropriate java env */
typedef struct {
JRIEnv *env;
char *pc;
bool_t ok;
} mocha_ExecuteJava_data;
static void
mocha_ExecuteJava_stub(void *d)
{
mocha_ExecuteJava_data *data = d;
extern bool_t ExecuteJava(unsigned char *, ExecEnv *ee);
data->ok = ExecuteJava((unsigned char*) data->pc, /*XXX*/(ExecEnv*)data->env);
}
/* this is a copy of do_execute_java_method_vararg modified for
* a MochaDatum* argument list instead of a va_list
*/
static MochaBoolean
do_mocha_execute_java_method(MochaContext *mc, void *obj,
struct methodblock *mb, bool_t isStaticCall,
int argc, MochaDatum *argv,
MochaDatum *rval)
{
uint16 taint;
int i;
ExecEnv *ee;
char *method_name;
char *method_signature;
JavaFrame *current_frame, *previous_frame;
JavaStack *current_stack;
char *sigRest;
int cost = 0;
stack_item *firstarg;
JRIEnv *saved;
PRBool success = PR_TRUE;
/* Don't let taint flow into Java. */
taint = mc->taintInfo->accum;
for (i = 0; i < argc; i++)
MOCHA_MIX_TAINT(mc, taint, argv[i].taint);
#if 0
if (taint != MOCHA_TAINT_IDENTITY && taint != MOCHA_TAINT_JAVA) {
MOCHA_ReportError(mc, "can't call Java method with tainted arguments");
return MOCHA_FALSE;
}
#endif
/* get the real ee that we are going to use */
/* if this is mozenv everything is fine since we're on the moz thread
* otherwise we are safe because it shouldn't be in the mochacontext
* unless the appropriate java thread is waiting for us to call it
* back (so we can walk all over it without fear */
saved = mc->javaEnv;
if (mc->javaEnv) {
ee = /*XXX*/(ExecEnv *)mc->javaEnv;
} else {
ee = /*XXX*/(ExecEnv *)(mc->javaEnv = mozenv);
}
/* push the safety frame before the call frame */
if (!mocha_pushSafeFrame(mc, ee))
return MOCHA_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) {
MOCHA_ReportOutOfMemory(mc);
success = PR_FALSE;
goto done;
}
}
/* no vars */
current_frame = (JavaFrame *)(current_stack->data);
}
}
ee->current_frame = current_frame;
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->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;
/* 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 (!mocha_convertMToJArgs(mc, firstarg, mb, argc, argv,
MOCHA_FALSE /* actually convert */,
&sigRest, &cost)) {
/* the method shouldn't have matched if this was going
* to happen! */
MOCHA_ReportError(mc, "internal error: argument conversion failed");
success = PR_FALSE;
goto done;
}
/* build the bytecodes and constant table for the call */
{
unsigned char pc[6];
cp_item_type constant_pool[10];
unsigned char cpt[10];
bool_t ok;
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;
{
mocha_ExecuteJava_data data;
data.pc = (char*) pc;
mocha_CallJava(mc, mocha_ExecuteJava_stub, &data,
MOCHA_FALSE /* we pushed the safety frame already */);
ok = data.ok;
}
if (ok) {
MochaBoolean ret;
PR_LOG(Moja, debug, ("method call succeeded\n"));
ret = java_returnAsMochaDatum(mc, rval, ee, sigRest);
if (!ret) {
MOCHA_ReportError(mc,
"can't convert Java return value with signature %s",
sigRest);
success = PR_FALSE;
}
} else
success = PR_FALSE;
}
done:
/* Our caller can look at ee->exceptionKind and ee->exception. */
ee->current_frame = previous_frame;
/* pop the safety frame */
mocha_popSafeFrame(mc, ee);
mc->javaEnv = saved;
return success;
}
static MochaBoolean
mocha_javaMethodWrapper(MochaContext *mc, MochaObject *obj,
unsigned argc, MochaDatum *argv, MochaDatum *rval)
{
MochaFunction *fun;
const char *name;
struct methodblock *realmb;
MochaJava *java;
ClassClass *cb;
MochaBoolean success;
MochaAtom *atom;
PR_ASSERT(argv[-1].tag == MOCHA_FUNCTION);
fun = argv[-1].u.fun;
atom = fun->atom;
name = MOCHA_GetAtomName(mc, atom);
if (!MOCHA_InstanceOf(mc, obj, &java_class, fun))
return MOCHA_FALSE;
java = obj->data;
cb = java->cb;
PR_LOG(Moja, debug, ("entered methodwrap, fun=0x%x, name=\"%s\"(0x%x)",
fun, name, name));
/* check that the object which we are being invoked on has
* the same java class as the one we were extracted from */
/* XXX oh well
if (fb->clazz != cb) {
MOCHA_ReportError(mc, "Java method doesn't match object");
return MOCHA_FALSE;
}
*/
/* match argc,argv against the signatures of the java methods */
realmb = matchMethod(mc, cb, java->type==JAVA_CLASS, name, argc, argv);
if (!realmb) {
char buf[256];
PR_snprintf(buf, sizeof(buf), "no Java %s.%s method matching JavaScript arguments (%d)",
cb->name, name, argc);
MOCHA_ReportError(mc, buf);
return MOCHA_FALSE;
}
PR_LOG(Moja, debug, ("calling %sjava method %s with signature %s",
java->type==JAVA_CLASS ? "static " : "",
realmb->fb.name, realmb->fb.signature));
success = do_mocha_execute_java_method(mc,
java->type==JAVA_CLASS ? 0 : java->handle, realmb,
java->type==JAVA_CLASS /* isStaticCall */,
argc, argv, rval);
return success;
}
static MochaBoolean
mocha_javaConstructorWrapper(MochaContext *mc, MochaObject *obj,
unsigned argc, MochaDatum *argv,
MochaDatum *rval)
{
MochaFunction *fun;
ClassClass *cb;
HObject *ho;
struct methodblock *realmb;
MochaBoolean success;
MochaDatum tmp;
PR_LOG(Moja, debug, ("entered java constructor wrapper\n"));
PR_ASSERT(argv[-1].tag == MOCHA_FUNCTION);
fun = argv[-1].u.fun;
cb = fun->object.data;
/* XXX these are copied from interpreter.c without much understanding */
if (cbAccess(cb) & (ACC_INTERFACE | ACC_ABSTRACT)) {
MOCHA_ReportError(mc, "can't instantiate Java class");
return MOCHA_FALSE;
}
if (!VerifyClassAccess(0, cb, FALSE)) {
MOCHA_ReportError(mc, "illegal access to Java class");
return MOCHA_FALSE;
}
/* match argc,argv against the signatures of the java constructors */
realmb = matchMethod(mc, cb, FALSE, "<init>", argc, argv);
if (!realmb) {
MOCHA_ReportError(mc, "no Java method matching arguments");
return MOCHA_FALSE;
}
if (!VerifyFieldAccess(0, fieldclass(&realmb->fb),
realmb->fb.access, FALSE)) {
MOCHA_ReportError(mc, "illegal access to Java constructor");
return MOCHA_FALSE;
}
PR_LOG(Moja, debug, ("calling java constructor with signature %s",
realmb->fb.signature));
/************* XXX RUN ME ON JAVA THREAD ***************/
/*
** Because newobject can fail, and call SignalError, which calls
** FindClassFromClass, etc.
*/
/* Allocate the object */
if ((ho = newobject(cb, 0, EE())) == 0) {
MOCHA_ReportOutOfMemory(mc);
return MOCHA_FALSE;
}
/************* END RUN ME ON JAVA THREAD ***************/
success = do_mocha_execute_java_method(mc, ho, realmb,
FALSE /* isStaticCall */,
argc, argv,
&tmp /* gets set to void */);
/* XXX the interpreter.c code calls cbDecRef(cb) at the end,
* why? should we? */
if (success)
return mocha_convertJObjectToMDatum(mc, rval, ho);
return MOCHA_FALSE;
}
/* * * * * * * * * * * * * * * * * * *
*
* mocha/java reflection tables
*
* * * * * * * * * * * * * * * * * * */
/* 16 bit needs to export this as a callback, but shouldn't do _loadds
* as not in a DLL as the PR API macros do.
* THIS NEEDS TO BE FIXED UP IN A GENERIC WAY USING THOSE MACROS,
* BY DECIDING WETHER OR NOT WE'RE COMPILING A DLL, THIS IS ONLY A
* QUICK HACK TO SHIP 3.0 BETA 4 */
#if defined(_WINDOWS) && !defined(_WIN32)
int __export
#else
static int
#endif
scanMochaJavaReflectionEntry(PRHashEntry *he, int i, void *gc)
{
GCInfo *gcInfo = gc;
MochaObject *mo = he->value;
MochaJava *java = mo->data;
/* scan the handle */
gcInfo->liveObject((void **)&java->handle, 1);
return HT_ENUMERATE_NEXT;
}
static void
scanMochaJavaReflections(void *notused)
{
PR_PROCESS_ROOT_LOG(("Scanning mocha reflections of java objects"));
/* XXX this is kind of scary long-term access inside the
* monitor - is there any alternative? */
PR_EnterMonitor(javaReflectionsMonitor);
PR_HashTableEnumerateEntries(javaReflections,
scanMochaJavaReflectionEntry,
PR_GetGCInfo());
PR_ExitMonitor(javaReflectionsMonitor);
}
static MochaFunctionSpec java_methods[] = {
{mocha_toStringStr, mocha_javaMethodWrapper, 0},
{mocha_valueOfStr, mocha_javaMethodWrapper, 0},
{0}
};
static MochaObject *
mocha_ReflectJava(MochaContext *mc, MochaJavaType type, HObject *handle,
ClassClass *cb, char *sig)
{
MochaObject *mo;
MochaJava *java;
void *key;
/* see if it's already been reflected */
switch (type) {
case JAVA_CLASS:
key = (void *) cb;
break;
case JAVA_OBJECT:
key = (void *) handle;
break;
case JAVA_ARRAY:
key = (void *) handle;
break;
default:
break;
}
PR_EnterMonitor(javaReflectionsMonitor);
mo = PR_HashTableLookup(javaReflections, key);
/* nope, reflect it */
if (!mo) {
java = (MochaJava *) malloc(sizeof(MochaJava));
if (!java) {
MOCHA_ReportOutOfMemory(mc);
return 0;
}
java->type = type;
java->handle = handle;
java->nextSlot = 1;
switch (type) {
case JAVA_CLASS:
java->cb = cb;
java->signature = sig;
mo = MOCHA_NewObject(mc, &java_class, java, 0, 0,
0, java_methods);
break;
case JAVA_OBJECT:
java->cb = cb;
java->signature = sig;
mo = MOCHA_NewObject(mc, &java_class, java, 0, 0,
0, java_methods);
break;
case JAVA_ARRAY:
java->cb = cb;
java->signature = sig;
mo = MOCHA_NewObject(mc, &javaarray_class, java, 0, 0,
javaarray_props, 0);
break;
default:
break;
}
/* add it to the table */
PR_HashTableAdd(javaReflections, key, mo);
}
PR_ExitMonitor(javaReflectionsMonitor);
return mo;
}
/* get the element type for a java array. clazz will be set to null
* if it's not of object type */
static MochaBoolean
mocha_JArrayElementType(HObject *handle, char **sig, ClassClass **clazz)
{
/* figure out the signature from the type */
int elementtype = obj_flags(handle);
char *elementsig;
*sig = 0;
*clazz = 0;
switch (elementtype) {
case T_CLASS:
*clazz = (ClassClass*)
unhand((HArrayOfObject *)handle)->body[obj_length(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 MOCHA_FALSE;
break;
}
*sig = elementsig;
return MOCHA_TRUE;
}
static MochaObject *
mocha_ReflectJArrayToMObject(MochaContext *mc, HObject *handle)
{
MochaObject *mo;
char *elementSig;
ClassClass *elementClazz;
FINISH_MOCHA_JAVA_INIT(mc);
if (!mocha_JArrayElementType(handle, &elementSig, &elementClazz))
return 0;
mo = mocha_ReflectJava(mc, JAVA_ARRAY, handle, elementClazz,
elementSig);
PR_LOG(Moja, debug, ("reflected array[%s] 0x%x as MochaObject* 0x%x",
elementSig, handle, mo));
return mo;
}
MochaObject *
MOCHA_ReflectJObjectToMObject(MochaContext *mc, HObject *handle)
{
MochaObject *mo = 0;
FINISH_MOCHA_JAVA_INIT(mc);
if (handle) {
if (obj_flags(handle)) {
mo = mocha_ReflectJArrayToMObject(mc, handle);
} else {
mo = mocha_ReflectJava(mc, JAVA_OBJECT, handle,
obj_classblock(handle), 0);
PR_LOG(Moja, debug, ("reflected HObject* 0x%x as MochaObject* 0x%x",
handle, mo));
}
}
return mo;
}
MochaObject *
MOCHA_ReflectJClassToMObject(MochaContext *mc, ClassClass *cb)
{
MochaObject *mo;
FINISH_MOCHA_JAVA_INIT(mc);
mo = mocha_ReflectJava(mc, JAVA_CLASS, (HObject *) cbHandle(cb), cb, 0);
PR_LOG(Moja, debug, ("reflected ClassClass* 0x%x as MochaObject* 0x%x",
cb, mo));
return mo;
}
/* * * * * * * * * */
/* javaslot is a java slot which will be resolved as a method
* or a field depending on context */
struct MochaJavaSlot {
MochaObject *obj; /* the object or class reflection */
MochaDatum datum; /* the field value when created */
struct fieldblock *fb; /* fieldblock if there is a field */
MochaAtom *atom; /* name of the field or method */
};
/* none of these should ever be called, since javaslot_convert will
* first turn the slot into the underlying object if there is one */
static MochaBoolean
javaslot_get_property(MochaContext *mc, MochaObject *obj, MochaSlot slot,
MochaDatum *dp)
{
MOCHA_ReportError(mc, "Java slots have no properties");
return MOCHA_FALSE;
}
static MochaBoolean
javaslot_set_property(MochaContext *mc, MochaObject *obj, MochaSlot slot,
MochaDatum *dp)
{
MOCHA_ReportError(mc, "Java slots have no properties");
return MOCHA_FALSE;
}
static MochaBoolean
javaslot_resolve_name(MochaContext *mc, MochaObject *obj, const char *name)
{
MOCHA_ReportError(mc, "Java slots have no properties");
return MOCHA_FALSE;
}
static void
javaslot_finalize(MochaContext *mc, MochaObject *obj)
{
MochaJavaSlot *slot = obj->data;
/* the datum may be holding a reference to something... */
MOCHA_DropRef(mc, &slot->datum);
/* drop the object of which this is a slot */
MOCHA_DropObject(mc, slot->obj);
/* drop the name */
MOCHA_DropAtom(mc, slot->atom);
free(slot);
PR_LOG(Moja, debug, ("finalizing MochaJavaSlot 0x%x", obj));
}
static MochaBoolean
javaslot_convert(MochaContext *mc, MochaObject *obj, MochaTag tag,
MochaDatum *dp)
{
MochaJavaSlot *slot = obj->data;
const char *name = MOCHA_GetAtomName(mc, slot->atom);
MochaFunction *fun;
MochaAtom *atom;
switch (tag) {
case MOCHA_FUNCTION:
fun = mocha_NewFunction(mc, mocha_javaMethodWrapper, 0,
slot->obj, slot->atom);
if (!fun)
return MOCHA_FALSE;
PR_LOG(Moja, debug,
("converted slot to function 0x%x with name %s(0x%x)\n",
fun, name, name));
/* Hold slot's parent object and flag fun as a bound method. */
MOCHA_HoldObject(mc, fun->object.parent);
fun->bound = MOCHA_TRUE;
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_FUNCTION,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA,
u.fun, fun);
return MOCHA_TRUE;
break;
case MOCHA_OBJECT:
PR_LOG(Moja, debug, ("converting java slot 0x%x to object", obj));
/* FALL THROUGH */
case MOCHA_ATOM:
case MOCHA_SYMBOL:
case MOCHA_NUMBER:
case MOCHA_BOOLEAN:
case MOCHA_STRING:
if (slot->fb) {
return MOCHA_ConvertDatum(mc, slot->datum, tag, dp);
}
if (tag == MOCHA_STRING) {
MochaJava *java = slot->obj->data;
char *str, *cp;
PR_ASSERT(java->type == JAVA_OBJECT || java->type == JAVA_CLASS);
str = PR_smprintf("[JavaMethod %s.%s]", classname(java->cb), name);
if (!str) {
atom = 0;
} else {
for (cp = str; *cp != '\0'; cp++)
if (*cp == '/')
*cp = '.';
atom = MOCHA_Atomize(mc, str);
free(str);
}
if (!atom) {
MOCHA_ReportOutOfMemory(mc);
return MOCHA_FALSE;
}
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_STRING,
MDF_ENUMERATE | MDF_TAINTED, MOCHA_TAINT_JAVA,
u.atom, atom);
return MOCHA_TRUE;
}
if (tag != MOCHA_OBJECT) {
MOCHA_ReportError(mc, "no field with name \"%s\"", name);
/* XXX drop atom */
return MOCHA_FALSE;
}
break;
default:
MOCHA_INIT_FULL_DATUM(mc, dp, MOCHA_UNDEF,
MDF_TAINTED, MOCHA_TAINT_JAVA,
u.ptr, 0);
break;
}
return MOCHA_TRUE;
}
static MochaClass javaslot_class = {
"JavaSlot",
javaslot_get_property, javaslot_set_property, MOCHA_ListPropStub,
javaslot_resolve_name, javaslot_convert, javaslot_finalize
};
static MochaObject *
mocha_reflectJavaSlot(MochaContext *mc, MochaObject *obj, MochaAtom *atom)
{
MochaJava *java = obj->data;
ClassClass *cb = java->cb;
MochaJavaSlot *slot;
MochaObject *mo;
const char *name;
PR_ASSERT(obj->clazz == &java_class);
slot = (MochaJavaSlot *) malloc(sizeof(MochaJavaSlot));
if (!slot) {
MOCHA_ReportOutOfMemory(mc);
return 0;
}
/* corresponding drops are in javaslot_finalize */
slot->atom = MOCHA_HoldAtom(mc, atom);
slot->obj = MOCHA_HoldObject(mc, obj);
slot->datum = MOCHA_void;
name = MOCHA_GetAtomName(mc, atom);
/* if there's a field get its value at reflection time */
slot->fb = java_lookup_field(mc, cb, java->type == JAVA_CLASS, name);
if (slot->fb) {
if (!mocha_convertJFieldToMDatum(mc, java->handle, slot->fb,
&slot->datum, MOCHA_UNDEF)) {
/* if this happens, the field had a value that couldn't
* be represented in mocha. */
PR_LOG(Moja, error,
("looking up initial field value failed!"));
/* XXX 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 */
slot->fb = 0;
}
}
/* we're hanging on to this datum, hold it if necessary.
* corresponding drop is in javaslot_finalize */
MOCHA_HoldRef(mc, &slot->datum);
mo = MOCHA_NewObject(mc, &javaslot_class, slot, 0, 0, 0, 0);
PR_LOG(Moja, debug, ("reflected slot %s of 0x%x as 0x%x",
name, java->handle, mo));
return mo;
}
/***********************************************************************/
/* a saved mocha error state */
typedef struct SavedMochaError SavedMochaError;
struct SavedMochaError {
char *message;
MochaErrorReport report;
SavedMochaError *next;
};
/*
* capture a mocha error that occurred in mocha code called by java.
* makes a copy of the mocha error data and hangs it off the mocha
* environment. when the mocha code returns, this is checked and
* used to generate a JSException. if the JSException is uncaught
* and makes it up to another layer of mocha, the error will be
* reinstated with MOCHA_ReportError
*/
void
mocha_js_ErrorReporter(MochaContext *mc, const char *message,
MochaErrorReport *error)
{
/* save the error state */
SavedMochaError *newerr;
newerr = (SavedMochaError *) malloc(sizeof(SavedMochaError));
if (!newerr) {
/* XXX not much we can do here, abort? */
return;
}
newerr->message = strdup(message);
if (!newerr->message) {
/* XXX not much we can do here, abort? */
free(newerr);
return;
}
if (error) {
newerr->report.filename = strdup(error->filename);
if (!newerr->report.filename) {
/* XXX not much we can do here, abort? */
free(newerr->message);
free(newerr);
return;
}
newerr->report.lineno = error->lineno;
if (error->linebuf) {
newerr->report.linebuf = strdup(error->linebuf);
if (!newerr->report.linebuf) {
/* XXX not much we can do here, abort? */
free((void*)newerr->report.filename);
free(newerr->message);
free(newerr);
return;
}
newerr->report.tokenptr = newerr->report.linebuf +
(error->tokenptr - error->linebuf);
} else {
newerr->report.linebuf = newerr->report.tokenptr = 0;
}
}
/* push this error */
newerr->next = mc->mochaErrors;
mc->mochaErrors = newerr;
}
static SavedMochaError *
mocha_js_FreeError(SavedMochaError *err)
{
SavedMochaError *next = err->next;
free(err->message);
free((char*)err->report.filename);/*XXX*/
free((char*)err->report.linebuf);
free(err);
return next;
}
/* this is called upon returning from mocha to java. one possibility
* is that the mocha error was actually triggered by java at some point -
* if so we throw the original java exception. otherwise, each mocha
* error will have pushed something on MochaContext->mochaErrors, so
* we convert them all to a string and throw a JSException with that
* info.
*/
void
mocha_MErrorToJException(MochaContext *mc, ExecEnv *ee)
{
SavedMochaError *err = 0;
if (!mc->mochaErrors) {
exceptionClear(ee);
PR_LOG(Moja, debug,
("j-m succeeded with no exception mc=0x%x ee=0x%x", mc, ee));
return;
}
/* if there's a pending exception in the java env, assume it
* needs to be propagated (since mocha couldn't have caught
* it and done something with it) */
if (exceptionOccurred(ee)) {
PR_LOG(Moja, debug,
("j-m propagated exception through mocha mc=0x%x ee=0x%x",
mc, ee));
return; /* propagating is easy! */
}
/* otherwise, throw a JSException */
/* get the message from the deepest saved mocha error */
err = mc->mochaErrors;
if (err) {
while (err->next)
err = err->next;
}
/* propagate any pending mocha errors upward with a java exception */
{
JRIEnv *env = (JRIEnv*) ee;
jref message =
JRI_NewStringUTF(env, err->message,
strlen(err->message));
jref filename = err->report.filename
? JRI_NewStringUTF(env, err->report.filename,
strlen(err->report.filename))
: NULL;
int lineno = err->report.lineno;
jref 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 =
netscape_javascript_JSException_new_2(env,
class_netscape_javascript_JSException(env),
/*Java_getGlobalRef(env, js_JSExceptionClass),*/
message, filename, lineno, source, index);
exceptionThrow(ee, (HObject *)exc);
PR_LOG(Moja, debug,
("j-m raised JSException \"%s\" mc=0x%x ee=0x%x",
err->message, mc, ee));
}
}
static MochaBoolean
mocha_isJSException(ExecEnv *ee, HObject *exc)
{
return strcmp(classname(obj_array_classblock(exc)),
"netscape/javascript/JSException")
? MOCHA_TRUE : MOCHA_FALSE;
}
/* this is called after returning from java to mocha. if the exception
* is actually a JSException, we pull the original mocha error state
* out of the MochaContext and use that. Otherwise we turn the
* JSException into a string and pass it up as a Mocha error
*/
static MochaBoolean
mocha_JExceptionToMError(MochaContext *mc, ExecEnv *ee)
{
SavedMochaError *err = 0;
char *message;
MochaBoolean success;
/* get rid of any mocha errors so far, but save the deepest
* one in case this was a JSException and we re-report it */
/* XXX the deepest one is the most interesting? */
err = mc->mochaErrors;
if (err) {
while (err->next)
err = mocha_js_FreeError(err);
}
/* if no exception reached us, continue on our merry way */
if (!exceptionOccurred(ee)) {
PR_LOG(Moja, debug,
("m-j succeeded, no exceptions mc=0x%x ee=0x%x",
mc, ee));
success = MOCHA_TRUE;
goto done;
}
/* if we got this far there was an error for sure */
success = MOCHA_FALSE;
switch (ee->exceptionKind) {
case EXCKIND_THROW:
if (err && mocha_isJSException(ee, ee->exception.exc)) {
mocha_ReportErrorAgain(mc, err->message, &err->report);
PR_LOG(Moja, debug,
("m-j re-reported error \"%s\" mc=0x%x ee=0x%x",
err->message, mc, ee));
}
/* otherwise, describe the exception to a string */
else if (mocha_isSubclassOf(mc,
obj_array_classblock(ee->exception.exc),
ThrowableClassBlock)) {
HString *hdetail =
unhand((Hjava_lang_Throwable *)
ee->exception.exc)->detailMessage;
ClassClass *cb = obj_array_classblock(ee->exception.exc);
message = allocCString(hdetail);
PR_LOG(Moja, debug,
("m-j converted exception %s, \"%s\" to error mc=0x%x ee=0x%x",
classname(cb), message, mc, ee));
/* pass the string to MOCHA_ReportError */
MOCHA_ReportError(mc, "uncaught Java exception %s (\"%s\")",
classname(cb), message);
free(message);
}
/* it's not a Throwable, somebody in java-land is being
* lame */
else {
ClassClass *cb = obj_array_classblock(ee->exception.exc);
MOCHA_ReportError(mc, "uncaught Java exception of class %s",
classname(cb));
PR_LOG(Moja, debug,
("m-j converted exception %s to error mc=0x%x ee=0x%x",
classname(cb), mc, ee));
}
break;
case EXCKIND_STKOVRFLW:
MOCHA_ReportError(mc, "Java stack overflow, pc=0x%x",
ee->exception.addr);
break;
default:
MOCHA_ReportError(mc,
"internal error: Java exception of unknown type %d",
ee->exceptionKind);
break;
}
done:
if (err) {
mocha_js_FreeError(err);
mc->mochaErrors = 0;
}
PR_LOG(Moja, debug,
("m-j cleared mocha errors mc=0x%x", mc));
return success;
}
/***********************************************************************/
/*
* All mocha_CallJava calls must use a data pointer that starts like
* this one:
*/
typedef struct {
JRIEnv *env;
} mocha_CallJava_data;
static MochaBoolean
mocha_CallJava(MochaContext *mc, LJCallback doit, void *d,
MochaBoolean pushSafeFrame)
{
mocha_CallJava_data *data = d;
MochaContext *saved;
MochaBoolean success = MOCHA_TRUE;
ExecEnv *ee;
FINISH_MOCHA_JAVA_INIT(mc);
if (mc->javaEnv)
data->env = mc->javaEnv;
else
data->env = mozenv;
ee = (ExecEnv*) data->env; /*XXX*/
if (!ee) {
MOCHA_ReportError(mc, "can't call Java from JavaScript");
return MOCHA_FALSE;
}
saved = ee->mochaContext;
ee->mochaContext = mc;
/* security: push the safety frame onto the java stack */
if (pushSafeFrame)
if (!mocha_pushSafeFrame(mc, ee)) {
return MOCHA_FALSE;
}
PR_LOG_BEGIN(Moja, debug, ("entering java ee=0x%x mc=0x%x", ee, mc));
LJ_CallJava(data->env, doit, data);
PR_LOG_END(Moja, debug, ("left java ee=0x%x mc=0x%x", ee, mc));
/* it's only safe to call this on the mozilla thread */
success = mocha_JExceptionToMError(mc, ee);
/* pop the safety frame */
if (pushSafeFrame)
mocha_popSafeFrame(mc, ee);
((ExecEnv*)(data->env))->mochaContext = saved;
return success;
}
/***********************************************************************/
typedef struct {
JRIEnv *env;
HObject *self;
char *name;
char *sig;
struct methodblock *mb;
bool_t isStaticCall;
va_list args;
long *raddr;
size_t rsize;
} mocha_ExecuteJavaMethod_data;
static void
mocha_ExecuteJavaMethod_stub(void *d)
{
mocha_ExecuteJavaMethod_data *data = (mocha_ExecuteJavaMethod_data *) d;
data->raddr[0] =
do_execute_java_method_vararg(/*XXX*/(ExecEnv*)data->env,
data->self,
data->name, data->sig,
data->mb, data->isStaticCall,
data->args,
(data->rsize > sizeof(long))
? &data->raddr[1] : NULL,
FALSE);
}
static MochaBoolean
mocha_ExecuteJavaMethod(MochaContext *mc, void *raddr, size_t rsize,
HObject *ho, char *name, char *sig,
struct methodblock *mb, bool_t isStaticCall, ...)
{
mocha_ExecuteJavaMethod_data data;
MochaBoolean success;
va_list args;
va_start(args, isStaticCall);
data.self = ho;
data.name = name;
data.sig = sig;
data.mb = mb;
data.isStaticCall = isStaticCall;
data.args = args;
data.raddr = raddr;
data.rsize = rsize;
success = mocha_CallJava(mc, mocha_ExecuteJavaMethod_stub, &data,
MOCHA_TRUE);
va_end(args);
return success;
}
/***********************************************************************/
typedef struct {
JRIEnv *env;
char *name;
ClassClass *fromClass;
ClassClass *ret;
char *errstr;
} mocha_FindJavaClass_data;
static void
mocha_FindJavaClass_stub(void *d)
{
mocha_FindJavaClass_data *data = d;
ExecEnv *ee = /*XXX*/(ExecEnv*)data->env;
exceptionClear(ee);
/* XXX 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 mocha
* fails to find a class it assumes it's a package instead
* of an error. */
/* XXX 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;
/* XXX 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 = allocCString(hdetail);
PR_LOG(Moja, debug,
("exception in is_subclass_of %s (\"%s\")",
classname(cb), message));
free(message);
#endif
if (0 == strcmp(classname(cb),
"netscape/applet/AppletSecurityException")) {
/* looks like we tried to get it from the net, and failed
* because we don't have the right classloader set up */
/* XXX fixme when it's possible to load java classes from
* mocha we'll have to push the right classloader to make
* this work. */
data->errstr
= PR_smprintf("can't load class %s from JavaScript",
data->name);
PR_LOG(Moja, debug, ("%s", data->errstr));
}
#ifdef DEBUG_nix
/* 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
/* XXX 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 *
mocha_FindJavaClass(MochaContext *mc, char *name, ClassClass *from)
{
mocha_FindJavaClass_data data;
data.name = name;
data.fromClass = from;
data.errstr = 0;
if (mocha_CallJava(mc, mocha_FindJavaClass_stub, &data,
MOCHA_TRUE)) {
if (data.errstr) {
MOCHA_ReportError(mc, "%s", data. errstr);
free(data.errstr);
/* XXX need to propagate error condition differently */
return 0;
}
return data.ret;
} else
return 0;
}
/***********************************************************************/
typedef struct {
JRIEnv *env;
bool_t privileged;
char *name;
ClassClass *cb;
char *sig;
va_list args;
HObject *ret;
} mocha_ConstructJava_data;
static void
mocha_ConstructJava_stub(void *d)
{
mocha_ConstructJava_data *data = d;
ExecEnv *ee = /*XXX*/(ExecEnv*)data->env;
if (data->privileged) {
/* XXX extremely fucking 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 *
mocha_ConstructJava(MochaContext *mc, char *name, ClassClass *cb,
char *sig, ...)
{
mocha_ConstructJava_data data;
va_list args;
MochaBoolean success;
va_start(args, sig);
data.name = name;
data.privileged = FALSE;
data.cb = cb;
data.sig = sig;
data.args = args;
data.env = JRI_GetCurrentEnv();
success = mocha_CallJava(mc, mocha_ConstructJava_stub, &data,
MOCHA_TRUE);
va_end(args);
if (success) return data.ret;
else return 0;
}
/* for private constructors, i.e. JSObject */
static HObject *
mocha_ConstructJavaPrivileged(MochaContext *mc, char *name, ClassClass *cb,
char *sig, ...)
{
mocha_ConstructJava_data data;
va_list args;
MochaBoolean success;
va_start(args, sig);
data.privileged = TRUE;
data.name = name;
data.cb = cb;
data.sig = sig;
data.args = args;
data.env = JRI_GetCurrentEnv();
success = mocha_CallJava(mc, mocha_ConstructJava_stub, &data,
MOCHA_TRUE);
va_end(args);
if (success) return data.ret;
else return 0;
}
/***********************************************************************/
PRBool moja_enabled = PR_TRUE;
/*
* most of the initialization is done lazily by this function
* this is never called more than once because it is called
* through a macro that tests if it's already been called...
*/
/* XXX this must be run on the mozilla thread */
static void
mocha_FinishInitJava(MochaContext *mc)
{
int tmp;
if (mocha_java_initialized)
return;
mocha_java_initialized = 1;
if (!MOCHA_JavaGlueEnabled())
return;
PR_LOG(Moja, debug, ("mocha_FinishInitJava()\n"));
/* the argument to LJ_Java_init is unused */
if (LJ_INIT_OK != (tmp = LJ_StartupJava())) {
PR_LOG(Moja, error, ("LJ_StartupJava returned %d", tmp));
goto fail;
}
/* initialize the reflection tables */
javaReflections =
PR_NewHashTable(256, (PRHashFunction) java_hashHandle,
(PRHashComparator) java_pointerEq,
(PRHashComparator) java_pointerEq, 0, 0);
PR_RegisterRootFinder(scanMochaJavaReflections,
"scan mocha reflections of java objects", 0);
javaReflectionsMonitor = PR_NewNamedMonitor(0, "javaReflections");
mochaReflections =
PR_NewHashTable(256, (PRHashFunction) java_hashHandle,
(PRHashComparator) java_pointerEq,
(PRHashComparator) java_pointerEq, 0, 0);
mochaReflectionsMonitor = PR_NewNamedMonitor(0, "mochaReflections");
/*
** Keep track of a few well-known classes for convenience. Setting
** the sticky bit keeps the classes from being collected.
*/
JSObjectClassBlock =
mocha_FindJavaClass(mc, "netscape/javascript/JSObject", 0);
if (!JSObjectClassBlock) {
PR_LOG(Moja, error,
("couldn't find class \"netscape/javascript/JSObject\"\n"));
goto fail;
}
JSObjectClassBlock->flags |= CCF_Sticky;
StringClassBlock = mocha_FindJavaClass(mc, "java/lang/String", 0);
if (!StringClassBlock) {
PR_LOG(Moja, error, ("couldn't find class \"java/lang/String\"\n"));
goto fail;
}
StringClassBlock->flags |= CCF_Sticky;
BooleanClassBlock = mocha_FindJavaClass(mc, "java/lang/Boolean", 0);
if (!BooleanClassBlock) {
PR_LOG(Moja, error, ("couldn't find class \"java/lang/Boolean\"\n"));
goto fail;
}
BooleanClassBlock->flags |= CCF_Sticky;
DoubleClassBlock = mocha_FindJavaClass(mc, "java/lang/Double", 0);
if (!DoubleClassBlock) {
PR_LOG(Moja, error, ("couldn't find class \"java/lang/Double\"\n"));
goto fail;
}
DoubleClassBlock->flags |= CCF_Sticky;
ThrowableClassBlock = mocha_FindJavaClass(mc, "java/lang/Throwable", 0);
if (!ThrowableClassBlock) {
PR_LOG(Moja, error, ("couldn't find class \"java/lang/Throwable\"\n"));
goto fail;
}
ThrowableClassBlock->flags |= CCF_Sticky;
{
JRIEnv* env = JRI_GetCurrentEnv();
use_netscape_javascript_JSObject(env);
use_java_applet_Applet(env);
}
return;
fail:
moja_enabled = PR_FALSE;
return;
}
PRBool
MOCHA_JavaGlueEnabled()
{
extern MochaBoolean LM_GetMochaEnabled(void);
return moja_enabled && LJ_GetJavaEnabled() && LM_GetMochaEnabled();
#if 0
/* we can supposedly do getenv on all platforms */
if (getenv("NS_ENABLE_MOJA"))
return MOCHA_TRUE;
return PR_FALSE;
#endif
}
/* hook from mocha_DestroyContext */
void
mocha_DestroyJavaContext(MochaContext *mc)
{
/* XXX do anything with the env? */
mc->javaEnv = 0;
}
/* get the java class associated with an instance, useful for
* access to static fields and methods of applets */
static MochaBoolean
mocha_getJavaClass(MochaContext *mc, MochaObject *obj, unsigned argc,
MochaDatum *argv, MochaDatum *rval)
{
MochaObject *mo;
MochaObject *moclass;
MochaJava *java;
/* XXX this could accept strings as well i suppose */
if (argc != 1
|| argv[0].tag != MOCHA_OBJECT
|| !argv[0].u.obj
|| ((mo = argv[0].u.obj)->clazz != &java_class)
|| ((java = mo->data)->type != JAVA_OBJECT)) {
MOCHA_ReportError(mc, "getClass expects a Java object argument");
return MOCHA_FALSE;
}
if (!(moclass = MOCHA_ReflectJClassToMObject(mc, java->cb))) {
MOCHA_ReportError(mc, "getClass can't find Java class reflection");
return MOCHA_FALSE;
}
MOCHA_INIT_DATUM(mc, rval, MOCHA_OBJECT, u.obj, moclass);
return MOCHA_TRUE;
}
static MochaObject *
mocha_DefineJavaPackage(MochaContext *mc, MochaObject *obj,
char *mname, char *package)
{
MochaJavaPackage *pack;
pack = malloc(sizeof(MochaJavaPackage));
if (!pack) {
MOCHA_ReportOutOfMemory(mc);
return 0;
}
pack->lastslot = 0;
if (package) {
pack->name = strdup(package);
if (!pack->name) {
MOCHA_ReportOutOfMemory(mc);
free(pack);
return 0;
}
} else
pack->name = 0;
return MOCHA_DefineNewObject(mc, obj, mname, &javapackage_class,
pack, 0, 0, 0, 0);
/* XXX can we make the package read-only? */
}
MochaBoolean
mocha_InitJava(MochaContext *mc, MochaObject *obj)
{
MochaObject *mo;
if (!MOCHA_JavaGlueEnabled()) {
PR_LOG(Moja, warn, ("Moja disabled\n"));
return MOCHA_TRUE;
}
PR_LOG(Moja, debug, ("mocha_InitJava(0x%x,0x%x)", mc, obj));
/* define the top of the java package namespace as "Packages" */
mo = mocha_DefineJavaPackage(mc, obj, "Packages", 0);
/* some convenience packages */
/* XXX 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 */
mocha_DefineJavaPackage(mc, obj, "java", "java");
mocha_DefineJavaPackage(mc, obj, "sun", "sun");
mocha_DefineJavaPackage(mc, obj, "netscape", "netscape");
MOCHA_DefineFunction(mc, obj, "getClass", mocha_getJavaClass, 0,
MDF_READONLY);
/* any initialization that depends on java running is done in
* mocha_FinishInitJava */
return MOCHA_TRUE;
}
/***********************************************************************
* these are the bytes for the class "JavaScript", defined as follows.
* the bytes were generated with javac, od, and emacs
***********************************************************************/
#if 0
/* XXX 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.
*/
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. */
static void callJava() {};
}
#endif /* 0 */
static jbyte JavaScript_class_bytes[] = {
0xca, 0xfe, 0xba, 0xbe, 0x00, 0x03, 0x00, 0x2d, 0x00, 0x14, 0x07, 0x00, 0x07, 0x07, 0x00, 0x10,
0x0a, 0x00, 0x02, 0x00, 0x04, 0x0c, 0x00, 0x11, 0x00, 0x13, 0x01, 0x00, 0x04, 0x74, 0x68, 0x69,
0x73, 0x01, 0x00, 0x0d, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75,
0x65, 0x01, 0x00, 0x0a, 0x4a, 0x61, 0x76, 0x61, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x01, 0x00,
0x12, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x61,
0x62, 0x6c, 0x65, 0x01, 0x00, 0x08, 0x63, 0x61, 0x6c, 0x6c, 0x4a, 0x61, 0x76, 0x61, 0x01, 0x00,
0x0a, 0x45, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x01, 0x00, 0x0f, 0x4c, 0x69,
0x6e, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x01, 0x00, 0x0a,
0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x01, 0x00, 0x0e, 0x4c, 0x6f, 0x63,
0x61, 0x6c, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x01, 0x00, 0x04, 0x43, 0x6f,
0x64, 0x65, 0x01, 0x00, 0x0f, 0x4a, 0x61, 0x76, 0x61, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2e,
0x6a, 0x61, 0x76, 0x61, 0x01, 0x00, 0x10, 0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67,
0x2f, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x01, 0x00, 0x06, 0x3c, 0x69, 0x6e, 0x69, 0x74, 0x3e,
0x01, 0x00, 0x0c, 0x4c, 0x4a, 0x61, 0x76, 0x61, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3b, 0x01,
0x00, 0x03, 0x28, 0x29, 0x56, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x02, 0x00, 0x11, 0x00, 0x13, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x2f, 0x00,
0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x2a, 0xb7, 0x00, 0x03, 0xb1, 0x00, 0x00, 0x00, 0x02,
0x00, 0x0b, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x08, 0x00, 0x00,
0x00, 0x0c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x12, 0x00, 0x00, 0x00, 0x08,
0x00, 0x09, 0x00, 0x13, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xb1, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x06, 0x00,
0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00,
0x0c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0f, 0x00,
};
/* now something to make a classloader and get a JavaScript object
* from it. */
/* XXX garbage collection of these classes? how should they be
* protected? */
#include "java_lang_ClassLoader.h"
#define USELESS_CODEBASE_URL "http://bogus-codebase.netscape.com/"
/* XXX for now there is only one class loader for all
* javascript->java calls, which has a bad codebase.
* this is undesirable because it would be nice to be able
* to pull in java classes from mocha, but the details of
* which classloader to use (probably the decoder's origin_url)
* are tricky, the netlib damage prevents us from loading new
* classes over the net from the moz thread anyway, and i
* need to go to sleep. */
/* XXX revisit this when we can load classes from the moz thread */
static JRIGlobalRef crippledJavaScriptClassLoader;
typedef struct {
JRIEnv *env;
MochaContext *mc;
char *origin_url;
JRIGlobalRef clazz;
char *err;
} mocha_findSafeMethod_data;
static void
mocha_findSafeMethod_stub(void *d)
{
mocha_findSafeMethod_data *data = (mocha_findSafeMethod_data *) d;
int length = sizeof(JavaScript_class_bytes);
HArrayOfByte *bytes;
HClass *clazz;
HObject *loader;
char *err;
ExecEnv *ee;
HObject *url;
ee = (ExecEnv*) data->env; /*XXX*/
/* turn the origin_url into an AppletClassLoader from that site */
/* make a java.net.URL from the origin_url */
if (data->origin_url) {
HString * str = makeJavaString(data->origin_url,
strlen(data->origin_url));
url =
execute_java_constructor(ee, "java/net/URL", 0,
"(Ljava/lang/String;)", str);
} else {
PR_LOG(Moja, warn,
("passing null codebaseURL for javascript classloader"));
url = 0;
}
/* make the new AppletClassLoader */
loader =
execute_java_constructor(PRIVILEGED_EE,
"netscape/applet/AppletClassLoader", 0,
"(Lnetscape/applet/MozillaAppletContext;Ljava/net/URL;Ljava/net/URL;)",
0, url, url);
if (exceptionOccurred(ee) || !loader) {
err = "couldn't create AppletClassLoader for JavaScript";
goto err;
}
/* make the array of bytes for class JavaScript */
bytes = (HArrayOfByte *) ArrayAlloc(T_BYTE, length);
if (!bytes) {
err = "couldn't load allocate bytes for JavaScript.class";
goto err;
}
memmove(unhand(bytes)->body, JavaScript_class_bytes, length);
/* make class JavaScript */
clazz = (HClass*)
do_execute_java_method(ee, loader,
"defineClass", "(Ljava/lang/String;[BII)Ljava/lang/Class;",
0, FALSE, NULL /* no required name */, bytes, 0, length);
if (exceptionOccurred(ee) || !clazz) {
err = "couldn't load class JavaScript";
goto err;
}
data->clazz = JRI_NewGlobalRef(data->env, (jref) clazz);
data->err = 0;
return;
err:
PR_LOG(Moja, error, ("%s", err));
data->clazz = 0;
data->err = err;
return;
}
/* security: this method returns a methodblock which can be
* used for secure mocha->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 struct methodblock *
mocha_findSafeMethod(MochaContext *mc)
{
mocha_findSafeMethod_data data;
JRIGlobalRef clazz;
ClassClass *cb;
struct methodblock *mb;
int i;
/* first see if this MochaContext already has one */
FINISH_MOCHA_JAVA_INIT(mc);
/* first see if this MochaContext already has one */
if (crippledJavaScriptClassLoader) {
clazz = crippledJavaScriptClassLoader;
} else {
#if 0
char *tmp;
#endif
FINISH_MOCHA_JAVA_INIT(mc);
/* call java to get the class */
data.env = mozenv;
data.mc = mc;
#if 0
/* strip off the last '/' and anything trailing it
* to get a codebase */
if (mc->script && mc->script->filename
&& (tmp = strrchr(mc->script->filename, '/'))) {
int len = tmp - mc->script->filename;
data.origin_url = (char*) malloc(len + 1);
strncpy(data.origin_url, mc->script->filename, len);
data.origin_url[len] = '\0';
} else
#endif
data.origin_url = strdup(USELESS_CODEBASE_URL); /* XXX */
if (!data.origin_url) {
MOCHA_ReportOutOfMemory(mc);
return 0;
}
PR_LOG(Moja, debug, ("using origin_url \"%s\"", data.origin_url));
LJ_CallJava(mozenv, mocha_findSafeMethod_stub, &data);
clazz = data.clazz;
free(data.origin_url);
crippledJavaScriptClassLoader = clazz;
}
if (!clazz) {
MOCHA_ReportError(mc, "%s", data.err);
return 0;
}
cb = unhand((HClass*)JRI_GetGlobalRef(mozenv, clazz));
/* find the static method callJava() for the class */
for (i = 0; i < cb->methods_count; i++) {
mb = cbMethods(cb) + i;
if (!strcmp(fieldname(&mb->fb), "callJava")
&& !strcmp(fieldsig(&mb->fb), "()V")) {
/* found it... */
return mb;
}
}
MOCHA_ReportError(mc, "can't find method JavaScript.callJava");
return NULL;
}
/*
* push a frame onto the java stack which does nothing except
* provide a classloader for the security manager
*/
static MochaBoolean
mocha_pushSafeFrame(MochaContext *mc, ExecEnv *ee)
{
JavaFrame *current_frame, *previous_frame;
JavaStack *current_stack;
struct methodblock *mb = mocha_findSafeMethod(mc);
if (!mb)
return MOCHA_FALSE;
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) {
MOCHA_ReportOutOfMemory(mc);
return MOCHA_FALSE;
}
}
/* no vars */
current_frame = (JavaFrame *)(current_stack->data);
}
}
ee->current_frame = current_frame;
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 */
/* make this be a method with the JS classloader */
current_frame->current_method = mb;
current_frame->optop += current_frame->current_method->args_size;
current_frame->constant_pool = 0;
return MOCHA_TRUE;
}
/*
* pop the safety frame from the java stack
*/
static void
mocha_popSafeFrame(MochaContext *mc, ExecEnv *ee)
{
ee->current_frame = ee->current_frame->prev;
}
#ifdef DEBUG_nix
static int
printToStdio(void* env,
void* dest, size_t destLen,
const char* srcBuf, size_t srcLen)
{
/* ignore env, context, destLen */
fwrite(srcBuf, 1, srcLen, (FILE*)dest);
return 0;
}
void
stackToStderr(jref obj)
{
printStackTrace(obj, printToStdio, stderr, 0);
}
#endif /* DEBUG_nix */
#endif /* defined(JAVA) */