From 11fd8abaeec95282daba2788cef5000e57bb475d Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Tue, 18 May 2010 19:21:43 -0700 Subject: [PATCH] Implement ES Harmony Proxies (bug 546590, r=mrbkap). --- js/src/Makefile.in | 2 + js/src/js.msg | 1 + js/src/jsapi.cpp | 22 +- js/src/jsapi.h | 3 + js/src/jsatom.cpp | 14 + js/src/jsatom.h | 16 + js/src/jscntxt.h | 14 +- js/src/jscntxtinlines.h | 12 + js/src/jsgc.cpp | 7 + js/src/jsiter.cpp | 112 ++- js/src/jsiter.h | 7 + js/src/jsobj.cpp | 212 ++++-- js/src/jsobj.h | 31 +- js/src/jsobjinlines.h | 28 + js/src/jspropertytree.cpp | 1 + js/src/jsproxy.cpp | 1117 +++++++++++++++++++++++++++++ js/src/jsproxy.h | 232 ++++++ js/src/jsscope.h | 8 +- js/src/jsscopeinlines.h | 12 + js/src/jsscript.cpp | 1 + js/src/jsxdrapi.cpp | 2 + js/src/shell/js.cpp | 22 + js/src/tests/jstests.list | 1 + js/src/tests/proxies/browser.js | 0 js/src/tests/proxies/jstests.list | 2 + js/src/tests/proxies/scripted.js | 154 ++++ js/src/tests/proxies/shell.js | 0 27 files changed, 1909 insertions(+), 124 deletions(-) create mode 100644 js/src/jsproxy.cpp create mode 100644 js/src/jsproxy.h create mode 100644 js/src/tests/proxies/browser.js create mode 100644 js/src/tests/proxies/jstests.list create mode 100644 js/src/tests/proxies/scripted.js create mode 100644 js/src/tests/proxies/shell.js diff --git a/js/src/Makefile.in b/js/src/Makefile.in index e6a7a45d488..a0cb697c59a 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -152,6 +152,7 @@ CPPSRCS = \ json.cpp \ jsopcode.cpp \ jsparse.cpp \ + jsproxy.cpp \ jsprf.cpp \ jspropertycache.cpp \ jspropertytree.cpp \ @@ -208,6 +209,7 @@ INSTALLED_HEADERS = \ jsopcode.h \ jsotypes.h \ jsparse.h \ + jsproxy.h \ jsprf.h \ jspropertycache.h \ jspropertycacheinlines.h \ diff --git a/js/src/js.msg b/js/src/js.msg index c3331c622b7..2ac1914115e 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -328,3 +328,4 @@ MSG_DEF(JSMSG_TYPED_ARRAY_NEGATIVE_ARG, 245, 1, JSEXN_ERR, "argument {0} must be MSG_DEF(JSMSG_TYPED_ARRAY_BAD_ARGS, 246, 0, JSEXN_ERR, "invalid arguments") MSG_DEF(JSMSG_CSP_BLOCKED_FUNCTION, 247, 0, JSEXN_ERR, "call to Function() blocked by CSP") MSG_DEF(JSMSG_BAD_GET_SET_FIELD, 248, 1, JSEXN_TYPEERR, "property descriptor's {0} field is neither undefined nor a function") +MSG_DEF(JSMSG_BAD_PROXY_FIX, 249, 0, JSEXN_TYPEERR, "proxy was fixed while executing the handler") diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 96f58e2f866..d97d5609bce 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -74,6 +74,7 @@ #include "jsobj.h" #include "jsopcode.h" #include "jsparse.h" +#include "jsproxy.h" #include "jsregexp.h" #include "jsscan.h" #include "jsscope.h" @@ -1213,7 +1214,8 @@ JS_InitStandardClasses(JSContext *cx, JSObject *obj) #if JS_HAS_GENERATORS js_InitIteratorClasses(cx, obj) && #endif - js_InitDateClass(cx, obj); + js_InitDateClass(cx, obj) && + js_InitProxyClass(cx, obj); } #define CLASP(name) (&js_##name##Class) @@ -1340,6 +1342,8 @@ static JSStdName standard_class_names[] = { {js_InitTypedArrayClasses, EAGER_CLASS_ATOM(Float64Array), NULL}, {js_InitTypedArrayClasses, EAGER_CLASS_ATOM(Uint8ClampedArray), NULL}, + {js_InitProxyClass, EAGER_ATOM(Proxy), NULL}, + {NULL, 0, NULL, NULL} }; @@ -1502,13 +1506,15 @@ JS_EnumerateStandardClasses(JSContext *cx, JSObject *obj) return JS_TRUE; } -static JSIdArray * +namespace js { + +JSIdArray * NewIdArray(JSContext *cx, jsint length) { JSIdArray *ida; ida = (JSIdArray *) - cx->malloc(offsetof(JSIdArray, vector) + length * sizeof(jsval)); + cx->calloc(offsetof(JSIdArray, vector) + length * sizeof(jsval)); if (ida) { ida->self = ida; ida->length = length; @@ -1516,6 +1522,8 @@ NewIdArray(JSContext *cx, jsint length) return ida; } +} + /* * Unlike realloc(3), this function frees ida on failure. */ @@ -1524,13 +1532,13 @@ SetIdArrayLength(JSContext *cx, JSIdArray *ida, jsint length) { JSIdArray *rida; + JS_ASSERT(ida->self == ida); rida = (JSIdArray *) JS_realloc(cx, ida, offsetof(JSIdArray, vector) + length * sizeof(jsval)); if (!rida) { JS_DestroyIdArray(cx, ida); } else { - rida->self = rida; rida->length = length; } return rida; @@ -3120,6 +3128,12 @@ GetPropertyAttributesById(JSContext *cx, JSObject *obj, jsid id, uintN flags, ? obj2->lockedGetSlot(sprop->slot) : JSVAL_VOID; } else { + if (obj->isProxy()) { + JSAutoResolveFlags rf(cx, flags); + return own + ? JSProxy::getOwnPropertyDescriptor(cx, obj, id, desc) + : JSProxy::getPropertyDescriptor(cx, obj, id, desc); + } desc->getter = NULL; desc->setter = NULL; desc->value = JSVAL_VOID; diff --git a/js/src/jsapi.h b/js/src/jsapi.h index a7118dd9734..63278a54d73 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -284,6 +284,8 @@ BOOLEAN_TO_JSVAL(JSBool b) object that delegates to a prototype containing this property */ #define JSPROP_INDEX 0x80 /* name is actually (jsint) index */ +#define JSPROP_SHORTID 0x100 /* set in JSPropertyDescriptor.attrs + if getters/setters use a shortid */ /* Function flags, set in JSFunctionSpec and passed to JS_NewFunction etc. */ #define JSFUN_LAMBDA 0x08 /* expressed, not declared, function */ @@ -1821,6 +1823,7 @@ struct JSPropertyDescriptor { uintN attrs; JSPropertyOp getter; JSPropertyOp setter; + uintN shortid; jsval value; }; diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp index 78e79453f06..08a77c95934 100644 --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -182,6 +182,20 @@ const char *const js_common_atom_names[] = { js_ExecutionContext_str, /* ExecutionContextAtom */ js_current_str, /* currentAtom */ #endif + + "Proxy", /* ProxyAtom */ + + "getOwnPropertyDescriptor", /* getOwnPropertyDescriptorAtom */ + "getPropertyDescriptor", /* getPropertyDescriptorAtom */ + "defineProperty", /* definePropertyAtom */ + "delete", /* deleteAtom */ + "getOwnPropertyNames", /* getOwnPropertyNames */ + "enumerate", /* enumerateAtom */ + "fix", + + "has", /* hasAtom */ + "hasOwn", /* hasOwnAtom */ + "enumerateOwn" /* enumerateOwnAtom */ }; JS_STATIC_ASSERT(JS_ARRAY_LENGTH(js_common_atom_names) * sizeof(JSAtom *) == diff --git a/js/src/jsatom.h b/js/src/jsatom.h index ee85eac192d..1cb5a1880fd 100644 --- a/js/src/jsatom.h +++ b/js/src/jsatom.h @@ -295,6 +295,20 @@ struct JSAtomState { JSAtom *currentAtom; #endif + JSAtom *ProxyAtom; + + JSAtom *getOwnPropertyDescriptorAtom; + JSAtom *getPropertyDescriptorAtom; + JSAtom *definePropertyAtom; + JSAtom *deleteAtom; + JSAtom *getOwnPropertyNamesAtom; + JSAtom *enumerateAtom; + JSAtom *fixAtom; + + JSAtom *hasAtom; + JSAtom *hasOwnAtom; + JSAtom *enumerateOwnAtom; + /* Less frequently used atoms, pinned lazily by JS_ResolveStandardClass. */ struct { JSAtom *InfinityAtom; @@ -325,6 +339,8 @@ struct JSAtomState { } lazy; }; +#define ATOM(name) cx->runtime->atomState.name##Atom + #define ATOM_OFFSET_START offsetof(JSAtomState, emptyAtom) #define LAZY_ATOM_OFFSET_START offsetof(JSAtomState, lazy) #define ATOM_OFFSET_LIMIT (sizeof(JSAtomState)) diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index b5d73811b78..999a9bac641 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -2174,7 +2174,8 @@ class AutoGCRooter { XML = -10, /* js::AutoXMLRooter */ OBJECT = -11, /* js::AutoObjectRooter */ ID = -12, /* js::AutoIdRooter */ - VECTOR = -13 /* js::AutoValueVector */ + VECTOR = -13, /* js::AutoValueVector */ + DESCRIPTOR = -14 /* js::AutoDescriptor */ }; private: @@ -2404,11 +2405,17 @@ class AutoIdArray : private AutoGCRooter { friend void AutoGCRooter::trace(JSTracer *trc); + JSIdArray *steal() { + JSIdArray *copy = idArray; + idArray = NULL; + return copy; + } + protected: inline void trace(JSTracer *trc); private: - JSIdArray * const idArray; + JSIdArray * idArray; JS_DECL_USE_GUARD_OBJECT_NOTIFIER /* No copy or assignment semantics. */ @@ -3023,6 +3030,9 @@ class AutoValueVector : private AutoGCRooter JS_DECL_USE_GUARD_OBJECT_NOTIFIER }; +JSIdArray * +NewIdArray(JSContext *cx, jsint length); + } #ifdef _MSC_VER diff --git a/js/src/jscntxtinlines.h b/js/src/jscntxtinlines.h index 0cc3b66196a..550d6f19dd5 100644 --- a/js/src/jscntxtinlines.h +++ b/js/src/jscntxtinlines.h @@ -235,6 +235,7 @@ AutoGCRooter::trace(JSTracer *trc) for (size_t i = 0, len = descriptors.length(); i < len; i++) { PropertyDescriptor &desc = descriptors[i]; + JS_CALL_VALUE_TRACER(trc, desc.pd, "PropertyDescriptor::pd"); JS_CALL_VALUE_TRACER(trc, desc.value, "PropertyDescriptor::value"); JS_CALL_VALUE_TRACER(trc, desc.get, "PropertyDescriptor::get"); JS_CALL_VALUE_TRACER(trc, desc.set, "PropertyDescriptor::set"); @@ -243,6 +244,17 @@ AutoGCRooter::trace(JSTracer *trc) return; } + case DESCRIPTOR : { + AutoDescriptor &desc = *static_cast(this); + JS_CALL_OBJECT_TRACER(trc, desc.obj, "Descriptor::obj"); + JS_CALL_VALUE_TRACER(trc, desc.value, "Descriptor::value"); + if (desc.attrs & JSPROP_GETTER) + JS_CALL_VALUE_TRACER(trc, jsval(desc.getter), "Descriptor::get"); + if (desc.attrs & JSPROP_SETTER) + JS_CALL_VALUE_TRACER(trc, jsval(desc.setter), "Descriptor::set"); + return; + } + case NAMESPACES: { JSXMLArray &array = static_cast(this)->array; TraceObjectVector(trc, reinterpret_cast(array.vector), array.length); diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index b5a1ab3e0da..d58ac79e057 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -73,6 +73,7 @@ #include "jsnum.h" #include "jsobj.h" #include "jsparse.h" +#include "jsproxy.h" #include "jsscope.h" #include "jsscript.h" #include "jsstaticcheck.h" @@ -2384,6 +2385,12 @@ FinalizeObject(JSContext *cx, JSObject *obj, unsigned thingKind) static_cast(scope)->dropFromGC(cx); else scope->destroy(cx); + } else { + if (obj->isProxy()) { + jsval handler = obj->getProxyHandler(); + if (JSVAL_IS_PRIMITIVE(handler)) + ((JSProxyHandler *) JSVAL_TO_PRIVATE(handler))->finalize(cx, obj); + } } if (obj->hasSlotsArray()) obj->freeSlotsArray(cx); diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index dd9a619e810..0a94f39cace 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -63,6 +63,7 @@ #include "jsnum.h" #include "jsobj.h" #include "jsopcode.h" +#include "jsproxy.h" #include "jsscan.h" #include "jsscope.h" #include "jsscript.h" @@ -163,7 +164,7 @@ Enumerate(JSContext *cx, JSObject *obj, JSObject *pobj, jsid id, if (pobj->getProto() && !ht.add(p, id)) return false; } - if (enumerable) { + if (enumerable || (flags & JSITER_HIDDEN)) { if (!vec.append(ID_TO_VALUE(id))) { JS_ReportOutOfMemory(cx); return false; @@ -232,6 +233,32 @@ EnumerateDenseArrayProperties(JSContext *cx, JSObject *obj, JSObject *pobj, uint return true; } +static bool +MakeNativeIterator(JSContext *cx, uintN flags, uint32 *sarray, uint32 slength, uint32 key, + jsval *parray, uint32 plength, NativeIterator **nip) +{ + NativeIterator *ni = (NativeIterator *) + cx->malloc(sizeof(NativeIterator) + plength * sizeof(jsval) + slength * sizeof(uint32)); + if (!ni) { + JS_ReportOutOfMemory(cx); + return false; + } + ni->props_array = ni->props_cursor = (jsval *) (ni + 1); + ni->props_end = ni->props_array + plength; + if (plength) + memcpy(ni->props_array, parray, plength * sizeof(jsval)); + ni->shapes_array = (uint32 *) ni->props_end; + ni->shapes_length = slength; + ni->shapes_key = key; + ni->flags = flags; + if (slength) + memcpy(ni->shapes_array, sarray, slength * sizeof(uint32)); + + *nip = ni; + + return true; +} + static bool InitNativeIterator(JSContext *cx, JSObject *obj, uintN flags, uint32 *sarray, uint32 slength, uint32 key, NativeIterator **nip) @@ -256,6 +283,23 @@ InitNativeIterator(JSContext *cx, JSObject *obj, uintN flags, uint32 *sarray, ui if (!EnumerateDenseArrayProperties(cx, obj, pobj, flags, ht, props)) return false; } else { + if (pobj->isProxy()) { + JSIdArray *ida; + if (flags & JSITER_OWNONLY) { + if (!JSProxy::enumerateOwn(cx, pobj, &ida)) + return false; + } else { + if (!JSProxy::enumerate(cx, pobj, &ida)) + return false; + } + AutoIdArray idar(cx, ida); + for (size_t n = 0; n < size_t(ida->length); ++n) { + if (!Enumerate(cx, obj, pobj, ida->vector[n], true, flags, ht, props)) + return false; + } + /* Proxy objects enumerate the prototype on their own, so we are done here. */ + break; + } jsval state; if (!pobj->enumerate(cx, JSENUMERATE_INIT, &state, NULL)) return false; @@ -281,37 +325,12 @@ InitNativeIterator(JSContext *cx, JSObject *obj, uintN flags, uint32 *sarray, ui pobj = pobj->getProto(); } - size_t plength = props.length(); - - NativeIterator *ni = (NativeIterator *) - cx->malloc(sizeof(NativeIterator) + plength * sizeof(jsval) + slength * sizeof(uint32)); - if (!ni) { - JS_ReportOutOfMemory(cx); - return false; - } - ni->props_array = ni->props_cursor = (jsval *) (ni + 1); - ni->props_end = ni->props_array + plength; - if (plength) - memcpy(ni->props_array, props.begin(), plength * sizeof(jsval)); - ni->shapes_array = (uint32 *) ni->props_end; - ni->shapes_length = slength; - ni->shapes_key = key; - ni->flags = flags; - if (slength) - memcpy(ni->shapes_array, sarray, slength * sizeof(uint32)); - - *nip = ni; - - return true; + return MakeNativeIterator(cx, flags, sarray, slength, key, props.begin(), props.length(), nip); } bool -EnumerateOwnProperties(JSContext *cx, JSObject *obj, JSIdArray **idap) +NativeIteratorToJSIdArray(JSContext *cx, NativeIterator *ni, JSIdArray **idap) { - NativeIterator *ni; - if (!InitNativeIterator(cx, obj, JSITER_OWNONLY, NULL, 0, true, &ni)) - return false; - /* Morph the NativeIterator into a JSIdArray. The caller will deallocate it. */ JS_ASSERT(sizeof(NativeIterator) > sizeof(JSIdArray)); JS_ASSERT(ni->props_array == (jsid *) (ni + 1)); @@ -324,6 +343,33 @@ EnumerateOwnProperties(JSContext *cx, JSObject *obj, JSIdArray **idap) return true; } +bool +EnumerateOwnProperties(JSContext *cx, JSObject *obj, JSIdArray **idap) +{ + NativeIterator *ni; + if (!InitNativeIterator(cx, obj, JSITER_OWNONLY, NULL, 0, true, &ni)) + return false; + return NativeIteratorToJSIdArray(cx, ni, idap); +} + +bool +EnumerateAllProperties(JSContext *cx, JSObject *obj, JSIdArray **idap) +{ + NativeIterator *ni; + if (!InitNativeIterator(cx, obj, 0, NULL, 0, true, &ni)) + return false; + return NativeIteratorToJSIdArray(cx, ni, idap); +} + +bool +GetOwnProperties(JSContext *cx, JSObject *obj, JSIdArray **idap) +{ + NativeIterator *ni; + if (!InitNativeIterator(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, NULL, 0, true, &ni)) + return false; + return NativeIteratorToJSIdArray(cx, ni, idap); +} + static inline bool GetCustomIterator(JSContext *cx, JSObject *obj, uintN flags, jsval *vp) { @@ -418,10 +464,12 @@ GetIterator(JSContext *cx, JSObject *obj, uintN flags, jsval *vp) } miss: - if (!GetCustomIterator(cx, obj, flags, vp)) - return false; - if (*vp != JSVAL_VOID) - return true; + if (!obj->isProxy()) { + if (!GetCustomIterator(cx, obj, flags, vp)) + return false; + if (*vp != JSVAL_VOID) + return true; + } } JSObject *iterobj = escaping diff --git a/js/src/jsiter.h b/js/src/jsiter.h index e92840f4fe2..95c430a00c8 100644 --- a/js/src/jsiter.h +++ b/js/src/jsiter.h @@ -58,6 +58,7 @@ JS_BEGIN_EXTERN_C #define JSITER_FOREACH 0x2 /* return [key, value] pair rather than key */ #define JSITER_KEYVALUE 0x4 /* destructuring for-in wants [key, value] */ #define JSITER_OWNONLY 0x8 /* iterate over obj's own properties only */ +#define JSITER_HIDDEN 0x10 /* also enumerate non-enumerable properties */ struct NativeIterator { jsval *props_array; @@ -82,6 +83,12 @@ static const jsval JSVAL_NATIVE_ENUMERATE_COOKIE = SPECIAL_TO_JSVAL(0x220576); bool EnumerateOwnProperties(JSContext *cx, JSObject *obj, JSIdArray **idap); +bool +EnumerateAllProperties(JSContext *cx, JSObject *obj, JSIdArray **idap); + +bool +GetOwnProperties(JSContext *cx, JSObject *obj, JSIdArray **idap); + /* * Convert the value stored in *vp to its iteration object. The flags should * contain JSITER_ENUMERATE if js_ValueToIterator is called when enumerating diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 7b7aea40884..24d80e14f43 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -69,6 +69,7 @@ #include "jsobj.h" #include "jsopcode.h" #include "jsparse.h" +#include "jsproxy.h" #include "jsscope.h" #include "jsscript.h" #include "jsstaticcheck.h" @@ -871,8 +872,13 @@ obj_toString(JSContext *cx, uintN argc, jsval *vp) obj = JS_THIS_OBJECT(cx, vp); if (!obj) return JS_FALSE; - obj = js_GetWrappedObject(cx, obj); - clazz = obj->getClass()->name; + if (obj->isProxy()) { + if (!JS_GetProxyObjectClass(cx, obj, &clazz)) + return false; + } else { + obj = js_GetWrappedObject(cx, obj); + clazz = obj->getClass()->name; + } nchars = 9 + strlen(clazz); /* 9 for "[object ]" */ chars = (jschar *) cx->malloc((nchars + 1) * sizeof(jschar)); if (!chars) @@ -1472,7 +1478,16 @@ js_HasOwnPropertyHelper(JSContext *cx, JSLookupPropOp lookup, uintN argc, JSObject *obj = JS_THIS_OBJECT(cx, vp); JSObject *obj2; JSProperty *prop; - if (!obj || !js_HasOwnProperty(cx, lookup, obj, id, &obj2, &prop)) + if (!obj) + return false; + if (obj->isProxy()) { + bool has; + if (!JSProxy::hasOwn(cx, obj, id, &has)) + return false; + *vp = BOOLEAN_TO_JSVAL(has); + return true; + } + if (!js_HasOwnProperty(cx, lookup, obj, id, &obj2, &prop)) return JS_FALSE; if (prop) { *vp = JSVAL_TRUE; @@ -1755,9 +1770,49 @@ obj_getPrototypeOf(JSContext *cx, uintN argc, jsval *vp) JSACC_PROTO, vp, &attrs); } +extern JSBool +js_NewPropertyDescriptorObject(JSContext *cx, jsid id, uintN attrs, jsval getter, jsval setter, jsval value, jsval *vp) +{ + /* We have our own property, so start creating the descriptor. */ + JSObject *desc = NewObject(cx, &js_ObjectClass, NULL, NULL); + if (!desc) + return false; + *vp = OBJECT_TO_JSVAL(desc); /* Root and return. */ + + const JSAtomState &atomState = cx->runtime->atomState; + if (attrs & (JSPROP_GETTER | JSPROP_SETTER)) { + if (!desc->defineProperty(cx, ATOM_TO_JSID(atomState.getAtom), getter, + JS_PropertyStub, JS_PropertyStub, JSPROP_ENUMERATE) || + !desc->defineProperty(cx, ATOM_TO_JSID(atomState.setAtom), setter, + JS_PropertyStub, JS_PropertyStub, JSPROP_ENUMERATE)) { + return false; + } + } else { + if (!desc->defineProperty(cx, ATOM_TO_JSID(atomState.valueAtom), value, + JS_PropertyStub, JS_PropertyStub, JSPROP_ENUMERATE) || + !desc->defineProperty(cx, ATOM_TO_JSID(atomState.writableAtom), + BOOLEAN_TO_JSVAL((attrs & JSPROP_READONLY) == 0), + JS_PropertyStub, JS_PropertyStub, JSPROP_ENUMERATE)) { + return false; + } + } + + return desc->defineProperty(cx, ATOM_TO_JSID(atomState.enumerableAtom), + BOOLEAN_TO_JSVAL((attrs & JSPROP_ENUMERATE) != 0), + JS_PropertyStub, JS_PropertyStub, JSPROP_ENUMERATE) && + desc->defineProperty(cx, ATOM_TO_JSID(atomState.configurableAtom), + BOOLEAN_TO_JSVAL((attrs & JSPROP_PERMANENT) == 0), + JS_PropertyStub, JS_PropertyStub, JSPROP_ENUMERATE); +} + JSBool js_GetOwnPropertyDescriptor(JSContext *cx, JSObject *obj, jsid id, jsval *vp) { + if (obj->isProxy()) { + if (!JSProxy::getOwnPropertyDescriptor(cx, obj, id, vp)) + return false; + } + JSObject *pobj; JSProperty *prop; if (!js_HasOwnProperty(cx, obj->map->ops->lookupProperty, obj, id, &pobj, &prop)) @@ -1773,7 +1828,7 @@ js_GetOwnPropertyDescriptor(JSContext *cx, JSObject *obj, jsid id, jsval *vp) return false; } - jsval roots[] = { JSVAL_VOID, JSVAL_VOID }; + jsval roots[] = { JSVAL_VOID, JSVAL_VOID, JSVAL_VOID }; AutoArrayRooter tvr(cx, JS_ARRAY_LENGTH(roots), roots); if (attrs & (JSPROP_GETTER | JSPROP_SETTER)) { if (obj->isNative()) { @@ -1788,41 +1843,16 @@ js_GetOwnPropertyDescriptor(JSContext *cx, JSObject *obj, jsid id, jsval *vp) } else { pobj->dropProperty(cx, prop); - if (!obj->getProperty(cx, id, &roots[0])) + if (!obj->getProperty(cx, id, &roots[2])) return false; } - - /* We have our own property, so start creating the descriptor. */ - JSObject *desc = NewObject(cx, &js_ObjectClass, NULL, NULL); - if (!desc) - return false; - *vp = OBJECT_TO_JSVAL(desc); /* Root and return. */ - - const JSAtomState &atomState = cx->runtime->atomState; - if (attrs & (JSPROP_GETTER | JSPROP_SETTER)) { - if (!desc->defineProperty(cx, ATOM_TO_JSID(atomState.getAtom), roots[0], - JS_PropertyStub, JS_PropertyStub, JSPROP_ENUMERATE) || - !desc->defineProperty(cx, ATOM_TO_JSID(atomState.setAtom), roots[1], - JS_PropertyStub, JS_PropertyStub, JSPROP_ENUMERATE)) { - return false; - } - } else { - if (!desc->defineProperty(cx, ATOM_TO_JSID(atomState.valueAtom), roots[0], - JS_PropertyStub, JS_PropertyStub, JSPROP_ENUMERATE) || - !desc->defineProperty(cx, ATOM_TO_JSID(atomState.writableAtom), - BOOLEAN_TO_JSVAL((attrs & JSPROP_READONLY) == 0), - JS_PropertyStub, JS_PropertyStub, JSPROP_ENUMERATE)) { - return false; - } - } - - return desc->defineProperty(cx, ATOM_TO_JSID(atomState.enumerableAtom), - BOOLEAN_TO_JSVAL((attrs & JSPROP_ENUMERATE) != 0), - JS_PropertyStub, JS_PropertyStub, JSPROP_ENUMERATE) && - desc->defineProperty(cx, ATOM_TO_JSID(atomState.configurableAtom), - BOOLEAN_TO_JSVAL((attrs & JSPROP_PERMANENT) == 0), - JS_PropertyStub, JS_PropertyStub, JSPROP_ENUMERATE); + return js_NewPropertyDescriptorObject(cx, id, + attrs, + roots[0], /* getter */ + roots[1], /* setter */ + roots[2], /* value */ + vp); } static JSBool @@ -1849,6 +1879,7 @@ obj_keys(JSContext *cx, uintN argc, jsval *vp) } JSObject *obj = JSVAL_TO_OBJECT(v); + AutoIdArray ida(cx, JS_Enumerate(cx, obj)); if (!ida) return JS_FALSE; @@ -1902,7 +1933,8 @@ HasProperty(JSContext* cx, JSObject* obj, jsid id, jsval* vp, JSBool* answerp) } PropertyDescriptor::PropertyDescriptor() - : id(INT_JSVAL_TO_JSID(JSVAL_ZERO)), + : pd(JSVAL_VOID), + id(INT_JSVAL_TO_JSID(JSVAL_ZERO)), value(JSVAL_VOID), get(JSVAL_VOID), set(JSVAL_VOID), @@ -1928,6 +1960,9 @@ PropertyDescriptor::initialize(JSContext* cx, jsid id, jsval v) } JSObject* desc = JSVAL_TO_OBJECT(v); + /* Make a copy of the descriptor. We might need it later. */ + pd = v; + /* Start with the proper defaults. */ attrs = JSPROP_PERMANENT | JSPROP_READONLY; @@ -2375,8 +2410,11 @@ DefineProperty(JSContext *cx, JSObject *obj, const PropertyDescriptor &desc, boo if (obj->isArray()) return DefinePropertyOnArray(cx, obj, desc, throwError, rval); - if (obj->map->ops->lookupProperty != js_LookupProperty) + if (obj->map->ops->lookupProperty != js_LookupProperty) { + if (obj->isProxy()) + return JSProxy::defineProperty(cx, obj, desc.id, desc.pd); return Reject(cx, JSMSG_OBJECT_NOT_EXTENSIBLE, throwError, rval); + } return DefinePropertyOnObject(cx, obj, desc, throwError, rval); } @@ -2422,6 +2460,41 @@ obj_defineProperty(JSContext* cx, uintN argc, jsval* vp) return js_DefineOwnProperty(cx, obj, nameidr.id(), descval, &junk); } +static bool +DefineProperties(JSContext *cx, JSObject *obj, JSObject *props) +{ + AutoIdArray ida(cx, JS_Enumerate(cx, props)); + if (!ida) + return false; + + AutoDescriptorArray descs(cx); + size_t len = ida.length(); + for (size_t i = 0; i < len; i++) { + jsid id = ida[i]; + PropertyDescriptor* desc = descs.append(); + AutoValueRooter tvr(cx); + if (!desc || + !JS_GetPropertyById(cx, props, id, tvr.addr()) || + !desc->initialize(cx, id, tvr.value())) { + return false; + } + } + + bool dummy; + for (size_t i = 0; i < len; i++) { + if (!DefineProperty(cx, obj, descs[i], true, &dummy)) + return false; + } + + return true; +} + +extern JSBool +js_PopulateObject(JSContext *cx, JSObject *newborn, JSObject *props) +{ + return DefineProperties(cx, newborn, props); +} + /* ES5 15.2.3.7: Object.defineProperties(O, Properties) */ static JSBool obj_defineProperties(JSContext* cx, uintN argc, jsval* vp) @@ -2430,43 +2503,23 @@ obj_defineProperties(JSContext* cx, uintN argc, jsval* vp) if (argc < 2) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED, "Object.defineProperties", "0", "s"); - return JS_FALSE; + return false; } *vp = vp[2]; if (JSVAL_IS_PRIMITIVE(vp[2])) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_NONNULL_OBJECT); - return JS_FALSE; + return false; } JSObject* props = js_ValueToNonNullObject(cx, vp[3]); if (!props) - return JS_FALSE; + return false; vp[3] = OBJECT_TO_JSVAL(props); - AutoIdArray ida(cx, JS_Enumerate(cx, props)); - if (!ida) - return JS_FALSE; - - AutoDescriptorArray descs(cx); - size_t len = ida.length(); - for (size_t i = 0; i < len; i++) { - jsid id = ida[i]; - PropertyDescriptor* desc = descs.append(); - if (!desc || !JS_GetPropertyById(cx, props, id, &vp[1]) || - !desc->initialize(cx, id, vp[1])) { - return JS_FALSE; - } - } - JSObject *obj = JSVAL_TO_OBJECT(*vp); - bool dummy; - for (size_t i = 0; i < len; i++) { - if (!DefineProperty(cx, obj, descs[i], true, &dummy)) - return JS_FALSE; - } - return JS_TRUE; + return DefineProperties(cx, obj, props); } /* ES5 15.2.3.5: Object.create(O [, Properties]) */ @@ -3127,6 +3180,32 @@ js_DefineBlockVariable(JSContext *cx, JSObject *obj, jsid id, intN index) JSScopeProperty::HAS_SHORTID, index, NULL); } +/* + * Use this method with extreme caution. It trades the guts of two objects and updates + * scope ownership. This operation is not thread-safe, just as fast array to slow array + * transitions are inherently not thread-safe. Don't perform a swap operation on objects + * shared across threads or, or bad things will happen. You have been warned. + */ +void +JSObject::swap(JSObject *other) +{ + /* For both objects determine whether they own their respective scopes. */ + bool thisOwns = this->isNative() && scope()->object == this; + bool otherOwns = other->isNative() && other->scope()->object == other; + + /* Trade the guts of the objects. */ + JSObject tmp; + memcpy(&tmp, this, sizeof(JSObject)); + memcpy(this, other, sizeof(JSObject)); + memcpy(other, &tmp, sizeof(JSObject)); + + /* Fixup scope ownerships. */ + if (otherOwns) + scope()->object = this; + if (thisOwns) + other->scope()->object = other; +} + #if JS_HAS_XDR #define NO_PARENT_INDEX ((uint32)-1) @@ -6166,15 +6245,6 @@ JSObject::getGlobal() return obj; } -bool -JSObject::isCallable() -{ - if (isNative()) - return isFunction() || getClass()->call; - - return !!map->ops->call; -} - JSBool js_ReportGetterOnlyAssignment(JSContext *cx) { diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 8859404bbb8..691d9e9fad5 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -62,10 +62,9 @@ namespace js { class AutoDescriptorArray; } struct PropertyDescriptor { friend class js::AutoDescriptorArray; - private: PropertyDescriptor(); - public: + /* 8.10.5 ToPropertyDescriptor(Obj) */ bool initialize(JSContext* cx, jsid id, jsval v); @@ -120,6 +119,7 @@ struct PropertyDescriptor { static void traceDescriptorArray(JSTracer* trc, JSObject* obj); static void finalizeDescriptorArray(JSContext* cx, JSObject* obj); + jsval pd; jsid id; jsval value, get, set; @@ -578,11 +578,19 @@ struct JSObject { inline jsval getQNameLocalName() const; inline void setQNameLocalName(jsval decl); + /* + * Proxy-specific getters and setters. + */ + + inline jsval getProxyHandler() const; + inline jsval getProxyPrivate() const; + inline void setProxyPrivate(jsval priv); + /* * Back to generic stuff. */ - bool isCallable(); + inline bool isCallable(); /* The map field is not initialized here and should be set separately. */ void init(JSClass *clasp, JSObject *proto, JSObject *parent, @@ -678,6 +686,8 @@ struct JSObject { map->ops->dropProperty(cx, this, prop); } + void swap(JSObject *obj); + inline bool isArguments() const; inline bool isArray() const; inline bool isDenseArray() const; @@ -693,6 +703,10 @@ struct JSObject { inline bool isNamespace() const; inline bool isQName() const; + inline bool isProxy() const; + inline bool isObjectProxy() const; + inline bool isFunctionProxy() const; + inline bool unbrand(JSContext *cx); }; @@ -867,6 +881,9 @@ extern JSBool js_HasOwnProperty(JSContext *cx, JSLookupPropOp lookup, JSObject *obj, jsid id, JSObject **objp, JSProperty **propp); +extern JSBool +js_NewPropertyDescriptorObject(JSContext *cx, jsid id, uintN attrs, jsval getter, jsval setter, jsval value, jsval *vp); + extern JSBool js_PropertyIsEnumerable(JSContext *cx, JSObject *obj, jsid id, jsval *vp); @@ -919,6 +936,9 @@ extern JSObject* js_NewObjectWithClassProto(JSContext *cx, JSClass *clasp, JSObject *proto, jsval privateSlotValue); +extern JSBool +js_PopulateObject(JSContext *cx, JSObject *newborn, JSObject *props); + /* * Fast access to immutable standard objects (constructors and prototypes). */ @@ -1281,11 +1301,6 @@ extern const char * js_ComputeFilename(JSContext *cx, JSStackFrame *caller, JSPrincipals *principals, uintN *linenop); -static inline bool -js_IsCallable(jsval v) { - return !JSVAL_IS_PRIMITIVE(v) && JSVAL_TO_OBJECT(v)->isCallable(); -} - extern JSBool js_ReportGetterOnlyAssignment(JSContext *cx); diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h index 4b0de046dc7..421283f67a7 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -500,6 +500,21 @@ JSObject::unbrand(JSContext *cx) return true; } +inline bool +JSObject::isCallable() +{ + if (isNative()) + return isFunction() || getClass()->call; + + return !!map->ops->call; +} + +static inline bool +js_IsCallable(jsval v) +{ + return !JSVAL_IS_PRIMITIVE(v) && JSVAL_TO_OBJECT(v)->isCallable(); +} + namespace js { typedef Vector PropertyDescriptorArray; @@ -528,6 +543,19 @@ class AutoDescriptorArray : private AutoGCRooter PropertyDescriptorArray descriptors; }; +class AutoDescriptor : private AutoGCRooter, public JSPropertyDescriptor +{ + public: + AutoDescriptor(JSContext *cx) : AutoGCRooter(cx, DESCRIPTOR) { + obj = NULL; + attrs = 0; + getter = setter = (JSPropertyOp) NULL; + value = JSVAL_VOID; + } + + friend void AutoGCRooter::trace(JSTracer *trc); +}; + static inline bool InitScopeForObject(JSContext* cx, JSObject* obj, JSClass *clasp, JSObject* proto, JSObjectOps* ops) { diff --git a/js/src/jspropertytree.cpp b/js/src/jspropertytree.cpp index 608f42ab57e..5d119b70139 100644 --- a/js/src/jspropertytree.cpp +++ b/js/src/jspropertytree.cpp @@ -47,6 +47,7 @@ #include "jspropertytree.h" #include "jsscope.h" +#include "jsnum.h" #include "jsscopeinlines.h" using namespace js; diff --git a/js/src/jsproxy.cpp b/js/src/jsproxy.cpp new file mode 100644 index 00000000000..42e6f39e203 --- /dev/null +++ b/js/src/jsproxy.cpp @@ -0,0 +1,1117 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released + * May 28, 2008. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Andreas Gal + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include +#include "jsapi.h" +#include "jscntxt.h" +#include "jsprvtd.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsproxy.h" +#include "jsscope.h" + +#include "jsobjinlines.h" + +using namespace js; + +JSProxyHandler::~JSProxyHandler() +{ +} + +bool +JSProxyHandler::has(JSContext *cx, JSObject *proxy, jsid id, bool *bp) +{ + AutoDescriptor desc(cx); + if (!getPropertyDescriptor(cx, proxy, id, &desc)) + return false; + *bp = !!desc.obj; + return true; +} + +bool +JSProxyHandler::hasOwn(JSContext *cx, JSObject *proxy, jsid id, bool *bp) +{ + AutoDescriptor desc(cx); + if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) + return false; + *bp = !!desc.obj; + return true; +} + +bool +JSProxyHandler::get(JSContext *cx, JSObject *proxy, JSObject *receiver, jsid id, jsval *vp) +{ + AutoDescriptor desc(cx); + if (!getPropertyDescriptor(cx, proxy, id, &desc)) + return false; + if (!desc.obj) { + *vp = JSVAL_VOID; + return true; + } + if (!desc.getter) { + *vp = desc.value; + return true; + } + if (desc.attrs & JSPROP_GETTER) + return js_InternalGetOrSet(cx, proxy, id, CastAsObjectJSVal(desc.getter), JSACC_READ, 0, 0, vp); + return desc.getter(cx, proxy, (desc.attrs & JSPROP_SHORTID) + ? INT_TO_JSID(desc.shortid) + : id, vp); +} + +bool +JSProxyHandler::set(JSContext *cx, JSObject *proxy, JSObject *receiver, jsid id, jsval *vp) +{ + AutoDescriptor desc(cx); + if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) + return false; + if (desc.obj) { + if (desc.setter) { + if (desc.attrs & JSPROP_GETTER) + return js_InternalGetOrSet(cx, proxy, id, CastAsObjectJSVal(desc.setter), JSACC_READ, 0, 0, vp); + return desc.setter(cx, proxy, (desc.attrs & JSPROP_SHORTID) + ? INT_TO_JSID(desc.shortid) + : id, vp); + } else { + if (desc.attrs & JSPROP_READONLY) + return true; + desc.value = *vp; + return defineProperty(cx, proxy, id, &desc); + } + } + if (!getPropertyDescriptor(cx, proxy, id, &desc)) + return false; + if (desc.obj) { + if (desc.setter) { + if (desc.attrs & JSPROP_GETTER) + return js_InternalGetOrSet(cx, proxy, id, CastAsObjectJSVal(desc.setter), JSACC_READ, 0, 0, vp); + return desc.setter(cx, proxy, (desc.attrs & JSPROP_SHORTID) + ? INT_TO_JSID(desc.shortid) + : id, vp); + } else { + if (desc.attrs & JSPROP_READONLY) + return true; + /* fall through */ + } + } + desc.obj = proxy; + desc.value = *vp; + desc.attrs = 0; + desc.getter = JSVAL_NULL; + desc.setter = JSVAL_NULL; + desc.shortid = 0; + return defineProperty(cx, proxy, id, &desc); +} + +bool +JSProxyHandler::enumerateOwn(JSContext *cx, JSObject *proxy, JSIdArray **idap) +{ + if (!getOwnPropertyNames(cx, proxy, idap)) + return false; + AutoIdArray ida(cx, *idap); + size_t w = 0; + jsid *vector = (*idap)->vector; + AutoDescriptor desc(cx); + for (size_t n = 0; n < ida.length(); ++n) { + JS_ASSERT(n >= w); + vector[w] = vector[n]; + if (!getOwnPropertyDescriptor(cx, proxy, vector[n], &desc)) + return false; + if (desc.obj && (desc.attrs & JSPROP_ENUMERATE)) + ++w; + } + (*idap)->length = w; + ida.steal(); + return true; +} + +void +JSProxyHandler::finalize(JSContext *cx, JSObject *proxy) +{ +} + +JSNoopProxyHandler::JSNoopProxyHandler(JSObject *obj) : mWrappedObject(obj) +{ +} + +JSNoopProxyHandler::~JSNoopProxyHandler() +{ +} + +bool +JSNoopProxyHandler::getPropertyDescriptor(JSContext *cx, JSObject *proxy, jsid id, JSPropertyDescriptor *desc) +{ + JSObject *wobj = wrappedObject(proxy); + return JS_GetPropertyDescriptorById(cx, wobj, id, JSRESOLVE_QUALIFIED, desc); +} + +bool +JSNoopProxyHandler::getOwnPropertyDescriptor(JSContext *cx, JSObject *proxy, jsid id, JSPropertyDescriptor *desc) +{ + return JS_GetPropertyDescriptorById(cx, wrappedObject(proxy), id, JSRESOLVE_QUALIFIED, desc); +} + +bool +JSNoopProxyHandler::defineProperty(JSContext *cx, JSObject *proxy, jsid id, JSPropertyDescriptor *desc) +{ + return JS_DefinePropertyById(cx, wrappedObject(proxy), id, desc->value, + desc->getter, desc->setter, desc->attrs); +} + +bool +JSNoopProxyHandler::getOwnPropertyNames(JSContext *cx, JSObject *proxy, JSIdArray **idap) +{ + return GetOwnProperties(cx, wrappedObject(proxy), idap); +} + +bool +JSNoopProxyHandler::delete_(JSContext *cx, JSObject *proxy, jsid id, bool *bp) +{ + AutoValueRooter tvr(cx); + if (!JS_DeletePropertyById2(cx, wrappedObject(proxy), id, tvr.addr())) + return false; + *bp = js_ValueToBoolean(tvr.value()); + return true; +} + +bool +JSNoopProxyHandler::enumerate(JSContext *cx, JSObject *proxy, JSIdArray **idap) +{ + return EnumerateAllProperties(cx, wrappedObject(proxy), idap); +} + +bool +JSNoopProxyHandler::fix(JSContext *cx, JSObject *proxy, jsval *vp) +{ + *vp = JSVAL_VOID; + return true; +} + +bool +JSNoopProxyHandler::has(JSContext *cx, JSObject *proxy, jsid id, bool *bp) +{ + JSBool found; + if (!JS_HasPropertyById(cx, wrappedObject(proxy), id, &found)) + return false; + *bp = !!found; + return true; +} + +bool +JSNoopProxyHandler::hasOwn(JSContext *cx, JSObject *proxy, jsid id, bool *bp) +{ + JSPropertyDescriptor desc; + JSObject *wobj = wrappedObject(proxy); + if (!JS_GetPropertyDescriptorById(cx, wobj, id, JSRESOLVE_QUALIFIED, &desc)) + return false; + *bp = (desc.obj == wobj); + return true; +} + +bool +JSNoopProxyHandler::get(JSContext *cx, JSObject *proxy, JSObject *receiver, jsid id, jsval *vp) +{ + return JS_GetPropertyById(cx, wrappedObject(proxy), id, vp); +} + +bool +JSNoopProxyHandler::set(JSContext *cx, JSObject *proxy, JSObject *receiver, jsid id, jsval *vp) +{ + return JS_SetPropertyById(cx, wrappedObject(proxy), id, vp); +} + +bool +JSNoopProxyHandler::enumerateOwn(JSContext *cx, JSObject *proxy, JSIdArray **idap) +{ + return EnumerateOwnProperties(cx, wrappedObject(proxy), idap); +} + +void +JSNoopProxyHandler::finalize(JSContext *cx, JSObject *proxy) +{ + if (mWrappedObject) + delete this; +} + +void * +JSNoopProxyHandler::family() +{ + return &singleton; +} + +JSNoopProxyHandler JSNoopProxyHandler::singleton(NULL); + +static bool +GetTrap(JSContext *cx, JSObject *handler, JSAtom *atom, jsval *fvalp) +{ + if (!handler->getProperty(cx, ATOM_TO_JSID(atom), fvalp)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNDEFINED_PROP, + JS_GetStringBytes(JSVAL_TO_STRING(ID_TO_VALUE(atom)))); + return false; + } + if (!js_IsCallable(*fvalp)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_FUNCTION, + JS_GetStringBytes(JSVAL_TO_STRING(ID_TO_VALUE(atom)))); + return false; + } + return true; +} + +static bool +TryHandlerTrap(JSContext *cx, JSObject *proxy, bool ok = true) +{ + if (ok && !proxy->isProxy()) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_PROXY_FIX); + ok = false; + } + return ok; +} + +static bool +Trap(JSContext *cx, JSObject *handler, JSAtom *atom, uintN argc, jsval* argv, jsval *rval) +{ + jsval fval; + if (!GetTrap(cx, handler, atom, &fval)) + return false; + return js_InternalCall(cx, handler, fval, argc, argv, rval); +} + +static bool +ParsePropertyDescriptorObject(JSContext *cx, JSObject *obj, jsid id, jsval v, JSPropertyDescriptor *desc) +{ + AutoDescriptorArray descs(cx); + PropertyDescriptor *d = descs.append(); + if (!d || !d->initialize(cx, id, v)) + return false; + desc->obj = obj; + desc->value = d->value; + JS_ASSERT(!(d->attrs & JSPROP_SHORTID)); + desc->attrs = d->attrs; + desc->getter = d->getter(); + desc->setter = d->setter(); + desc->shortid = 0; + return true; +} + +static bool +MakePropertyDescriptorObject(JSContext *cx, jsid id, JSPropertyDescriptor *desc, jsval *vp) +{ + if (!desc->obj) { + *vp = JSVAL_VOID; + return true; + } + return js_NewPropertyDescriptorObject(cx, id, desc->attrs, + CastAsObjectJSVal(desc->getter), + CastAsObjectJSVal(desc->setter), + desc->value, vp); +} + +bool +JSProxy::getPropertyDescriptor(JSContext *cx, JSObject *proxy, jsid id, JSPropertyDescriptor *desc) +{ + jsval handler = proxy->getProxyHandler(); + if (JSVAL_IS_OBJECT(handler)) { + AutoValueRooter tvr(cx); + jsval argv[] = { ID_TO_VALUE(id) }; + if (!TryHandlerTrap(cx, proxy, Trap(cx, JSVAL_TO_OBJECT(handler), ATOM(getPropertyDescriptor), 1, argv, tvr.addr()))) + return false; + return ParsePropertyDescriptorObject(cx, proxy, id, tvr.value(), desc); + } + return TryHandlerTrap(cx, proxy, ((JSProxyHandler *) JSVAL_TO_PRIVATE(handler))->getPropertyDescriptor(cx, proxy, id, desc)); +} + +bool +JSProxy::getPropertyDescriptor(JSContext *cx, JSObject *proxy, jsid id, jsval *vp) +{ + jsval handler = proxy->getProxyHandler(); + if (JSVAL_IS_OBJECT(handler)) { + jsval argv[] = { ID_TO_VALUE(id) }; + return TryHandlerTrap(cx, proxy, Trap(cx, JSVAL_TO_OBJECT(handler), ATOM(getPropertyDescriptor), 1, argv, vp)); + } + AutoDescriptor desc(cx); + if (!TryHandlerTrap(cx, proxy, ((JSProxyHandler *) JSVAL_TO_PRIVATE(handler))->getPropertyDescriptor(cx, proxy, id, &desc))) + return false; + return MakePropertyDescriptorObject(cx, id, &desc, vp); +} + +bool +JSProxy::getOwnPropertyDescriptor(JSContext *cx, JSObject *proxy, jsid id, JSPropertyDescriptor *desc) +{ + jsval handler = proxy->getProxyHandler(); + if (JSVAL_IS_OBJECT(handler)) { + AutoValueRooter tvr(cx); + jsval argv[] = { ID_TO_VALUE(id) }; + if (!TryHandlerTrap(cx, proxy, Trap(cx, JSVAL_TO_OBJECT(handler), ATOM(getOwnPropertyDescriptor), 1, argv, tvr.addr()))) + return false; + return ParsePropertyDescriptorObject(cx, proxy, id, tvr.value(), desc); + } + return TryHandlerTrap(cx, proxy, ((JSProxyHandler *) JSVAL_TO_PRIVATE(handler))->getOwnPropertyDescriptor(cx, proxy, id, desc)); +} + +bool +JSProxy::getOwnPropertyDescriptor(JSContext *cx, JSObject *proxy, jsid id, jsval *vp) +{ + jsval handler = proxy->getProxyHandler(); + if (JSVAL_IS_OBJECT(handler)) { + jsval argv[] = { ID_TO_VALUE(id) }; + return TryHandlerTrap(cx, proxy, Trap(cx, JSVAL_TO_OBJECT(handler), ATOM(getOwnPropertyDescriptor), 1, argv, vp)); + } + AutoDescriptor desc(cx); + if (!TryHandlerTrap(cx, proxy, ((JSProxyHandler *) JSVAL_TO_PRIVATE(handler))->getOwnPropertyDescriptor(cx, proxy, id, &desc))) + return false; + return MakePropertyDescriptorObject(cx, id, &desc, vp); +} + +bool +JSProxy::defineProperty(JSContext *cx, JSObject *proxy, jsid id, JSPropertyDescriptor *desc) +{ + jsval handler = proxy->getProxyHandler(); + if (JSVAL_IS_OBJECT(handler)) { + AutoValueRooter tvr(cx); + if (!MakePropertyDescriptorObject(cx, id, desc, tvr.addr())) + return false; + jsval argv[] = { ID_TO_VALUE(id), tvr.value() }; + return TryHandlerTrap(cx, proxy, Trap(cx, JSVAL_TO_OBJECT(handler), ATOM(defineProperty), 2, argv, tvr.addr())); + } + return TryHandlerTrap(cx, proxy, ((JSProxyHandler *) JSVAL_TO_PRIVATE(handler))->defineProperty(cx, proxy, id, desc)); +} + +bool +JSProxy::defineProperty(JSContext *cx, JSObject *proxy, jsid id, jsval v) +{ + jsval handler = proxy->getProxyHandler(); + if (JSVAL_IS_OBJECT(handler)) { + AutoValueRooter tvr(cx); + jsval argv[] = { ID_TO_VALUE(id), v }; + return TryHandlerTrap(cx, proxy, Trap(cx, JSVAL_TO_OBJECT(handler), ATOM(defineProperty), 2, argv, tvr.addr())); + } + AutoDescriptor desc(cx); + if (!ParsePropertyDescriptorObject(cx, proxy, id, v, &desc)) + return false; + return TryHandlerTrap(cx, proxy, ((JSProxyHandler *) JSVAL_TO_PRIVATE(handler))->defineProperty(cx, proxy, id, &desc)); +} + +bool +ArrayToJSIDArray(JSContext *cx, jsval array, JSIdArray **idap) +{ + if (JSVAL_IS_PRIMITIVE(array)) + return (*idap = NewIdArray(cx, 0)) != NULL; + + JSObject *obj = JSVAL_TO_OBJECT(array); + jsuint length; + if (!js_GetLengthProperty(cx, obj, &length)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_ARRAY_LENGTH); + return false; + } + AutoIdArray ida(cx, *idap = NewIdArray(cx, length)); + if (!ida) + return false; + AutoValueRooter tvr(cx); + jsid *vector = (*idap)->vector; + for (jsuint n = 0; n < length; ++n) { + if (!js_IndexToId(cx, n, &vector[n])) + return false; + if (!obj->getProperty(cx, vector[n], tvr.addr())) + return false; + if (!JS_ValueToId(cx, tvr.value(), &vector[n])) + return false; + } + *idap = ida.steal(); + return true; +} + +bool +JSProxy::getOwnPropertyNames(JSContext *cx, JSObject *proxy, JSIdArray **idap) +{ + jsval handler = proxy->getProxyHandler(); + if (JSVAL_IS_OBJECT(handler)) { + AutoValueRooter rval(cx); + if (!TryHandlerTrap(cx, proxy, Trap(cx, JSVAL_TO_OBJECT(handler), ATOM(getOwnPropertyNames), 0, NULL, rval.addr()))) + return false; + return ArrayToJSIDArray(cx, rval.value(), idap); + } + return TryHandlerTrap(cx, proxy, ((JSProxyHandler *) JSVAL_TO_PRIVATE(handler))->getOwnPropertyNames(cx, proxy, idap)); +} + +bool +JSProxy::delete_(JSContext *cx, JSObject *proxy, jsid id, bool *bp) +{ + jsval handler = proxy->getProxyHandler(); + if (JSVAL_IS_OBJECT(handler)) { + AutoValueRooter rval(cx); + jsval argv[] = { ID_TO_VALUE(id) }; + if (!TryHandlerTrap(cx, proxy, Trap(cx, JSVAL_TO_OBJECT(handler), ATOM(delete), 1, argv, rval.addr()))) + return false; + JSBool deleted; + if (!JS_ValueToBoolean(cx, rval.value(), &deleted)) + return false; + *bp = !!deleted; + return true; + } + return TryHandlerTrap(cx, proxy, ((JSProxyHandler *) JSVAL_TO_PRIVATE(handler))->delete_(cx, proxy, id, bp)); +} + +bool +JSProxy::enumerate(JSContext *cx, JSObject *proxy, JSIdArray **idap) +{ + jsval handler = proxy->getProxyHandler(); + if (JSVAL_IS_OBJECT(handler)) { + AutoValueRooter rval(cx); + if (!TryHandlerTrap(cx, proxy, Trap(cx, JSVAL_TO_OBJECT(handler), ATOM(enumerate), 0, NULL, rval.addr()))) + return false; + return ArrayToJSIDArray(cx, rval.value(), idap); + } + return TryHandlerTrap(cx, proxy, ((JSProxyHandler *) JSVAL_TO_PRIVATE(handler))->enumerate(cx, proxy, idap)); +} + +bool +JSProxy::fix(JSContext *cx, JSObject *proxy, jsval *vp) +{ + jsval handler = proxy->getProxyHandler(); + if (JSVAL_IS_OBJECT(handler)) + return Trap(cx, JSVAL_TO_OBJECT(handler), ATOM(fix), 0, NULL, vp); + return ((JSProxyHandler *) JSVAL_TO_PRIVATE(handler))->fix(cx, proxy, vp); +} + +bool +JSProxy::has(JSContext *cx, JSObject *proxy, jsid id, bool *bp) +{ + jsval handler = proxy->getProxyHandler(); + if (JSVAL_IS_OBJECT(handler)) { + AutoValueRooter rval(cx); + jsval argv[] = { ID_TO_VALUE(id) }; + if (!TryHandlerTrap(cx, proxy, Trap(cx, JSVAL_TO_OBJECT(handler), ATOM(has), 1, argv, rval.addr()))) + return false; + JSBool has; + if (!JS_ValueToBoolean(cx, rval.value(), &has)) + return false; + *bp = !!has; + return true; + } + return TryHandlerTrap(cx, proxy, ((JSProxyHandler *) JSVAL_TO_PRIVATE(handler))->has(cx, proxy, id, bp)); +} + +bool +JSProxy::hasOwn(JSContext *cx, JSObject *proxy, jsid id, bool *bp) +{ + jsval handler = proxy->getProxyHandler(); + if (JSVAL_IS_OBJECT(handler)) { + AutoValueRooter rval(cx); + jsval argv[] = { ID_TO_VALUE(id) }; + if (!TryHandlerTrap(cx, proxy, Trap(cx, JSVAL_TO_OBJECT(handler), ATOM(hasOwn), 1, argv, rval.addr()))) + return false; + JSBool has; + if (!JS_ValueToBoolean(cx, rval.value(), &has)) + return false; + *bp = !!has; + return true; + } + return TryHandlerTrap(cx, proxy, ((JSProxyHandler *) JSVAL_TO_PRIVATE(handler))->hasOwn(cx, proxy, id, bp)); +} + +bool +JSProxy::get(JSContext *cx, JSObject *proxy, JSObject *receiver, jsid id, jsval *vp) +{ + jsval handler = proxy->getProxyHandler(); + if (JSVAL_IS_OBJECT(handler)) { + jsval argv[] = { OBJECT_TO_JSVAL(receiver), ID_TO_VALUE(id) }; + return TryHandlerTrap(cx, proxy, Trap(cx, JSVAL_TO_OBJECT(handler), ATOM(get), 2, argv, vp)); + } + return TryHandlerTrap(cx, proxy, ((JSProxyHandler *) JSVAL_TO_PRIVATE(handler))->get(cx, proxy, receiver, id, vp)); +} + +bool +JSProxy::set(JSContext *cx, JSObject *proxy, JSObject *receiver, jsid id, jsval *vp) +{ + jsval handler = proxy->getProxyHandler(); + if (JSVAL_IS_OBJECT(handler)) { + AutoValueRooter rval(cx); + jsval argv[] = { OBJECT_TO_JSVAL(receiver), ID_TO_VALUE(id), *vp }; + return TryHandlerTrap(cx, proxy, Trap(cx, JSVAL_TO_OBJECT(handler), ATOM(set), 3, argv, rval.addr())); + } + return TryHandlerTrap(cx, proxy, ((JSProxyHandler *) JSVAL_TO_PRIVATE(handler))->set(cx, proxy, receiver, id, vp)); +} + +bool +JSProxy::enumerateOwn(JSContext *cx, JSObject *proxy, JSIdArray **idap) +{ + jsval handler = proxy->getProxyHandler(); + if (JSVAL_IS_OBJECT(handler)) { + AutoValueRooter rval(cx); + if (!TryHandlerTrap(cx, proxy, Trap(cx, JSVAL_TO_OBJECT(handler), ATOM(enumerateOwn), 0, NULL, rval.addr()))) + return false; + return ArrayToJSIDArray(cx, rval.value(), idap); + } + return TryHandlerTrap(cx, proxy, ((JSProxyHandler *) JSVAL_TO_PRIVATE(handler))->enumerateOwn(cx, proxy, idap)); +} + +JS_PUBLIC_API(JSBool) +JS_GetProxyObjectClass(JSContext *cx, JSObject *proxy, const char **namep) +{ + if (!proxy->isProxy()) { + char *bytes = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, + OBJECT_TO_JSVAL(proxy), NULL); + if (!bytes) + return JS_FALSE; + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_UNEXPECTED_TYPE, bytes, "not a proxy"); + return false; + } + if (proxy->isFunctionProxy()) { + *namep = "Function"; + return true; + } + jsval nameval = proxy->fslots[JSSLOT_PROXY_CLASS]; + if (nameval == JSVAL_VOID) { + *namep ="Object"; + return true; + } + JS_ASSERT(JSVAL_IS_STRING(nameval)); + *namep = JS_GetStringBytesZ(cx, JSVAL_TO_STRING(nameval)); + return *namep != NULL; +} + +static JSBool +proxy_LookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp, + JSProperty **propp) +{ + bool found; + if (!JSProxy::has(cx, obj, id, &found)) + return false; + + if (found) { + *propp = (JSProperty *)id; + *objp = obj; + } else { + *objp = NULL; + *propp = NULL; + } + return true; +} + +static JSBool +proxy_DefineProperty(JSContext *cx, JSObject *obj, jsid id, jsval value, + JSPropertyOp getter, JSPropertyOp setter, uintN attrs) +{ + AutoDescriptor desc(cx); + desc.obj = obj; + desc.value = value; + desc.attrs = (attrs & (~JSPROP_SHORTID)); + desc.getter = getter; + desc.setter = setter; + desc.shortid = 0; + return JSProxy::defineProperty(cx, obj, id, &desc); +} + +static JSBool +proxy_GetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp) +{ + return JSProxy::get(cx, obj, obj, id, vp); +} + +static JSBool +proxy_SetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + return JSProxy::set(cx, obj, obj, id, vp); +} + +static JSBool +proxy_GetAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop, + uintN *attrsp) +{ + AutoDescriptor desc(cx); + if (!JSProxy::getOwnPropertyDescriptor(cx, obj, id, &desc)) + return false; + *attrsp = desc.attrs; + return true; +} + +static JSBool +proxy_SetAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop, + uintN *attrsp) +{ + /* Lookup the current property descriptor so we have setter/getter/value. */ + AutoDescriptor desc(cx); + if (!JSProxy::getOwnPropertyDescriptor(cx, obj, id, &desc)) + return false; + desc.attrs = (*attrsp & (~JSPROP_SHORTID)); + return JSProxy::defineProperty(cx, obj, id, &desc); +} + +static JSBool +proxy_DeleteProperty(JSContext *cx, JSObject *obj, jsval id, jsval *rval) +{ + bool deleted; + if (!JSProxy::delete_(cx, obj, id, &deleted)) + return false; + *rval = BOOLEAN_TO_JSVAL(deleted); + return true; +} + +static void +proxy_TraceObject(JSTracer *trc, JSObject *obj) +{ + JSContext *cx = trc->context; + + if (!JS_CLIST_IS_EMPTY(&cx->runtime->watchPointList)) + js_TraceWatchPoints(trc, obj); + + JSClass *clasp = obj->getClass(); + if (clasp->mark) { + if (clasp->flags & JSCLASS_MARK_IS_TRACE) + ((JSTraceOp) clasp->mark)(trc, obj); + else if (IS_GC_MARKING_TRACER(trc)) + (void) clasp->mark(cx, obj, trc); + } + + obj->traceProtoAndParent(trc); + + if (!JSVAL_IS_PRIMITIVE(obj->fslots[JSSLOT_PROXY_HANDLER])) { + JSObject *handler = JSVAL_TO_OBJECT(obj->fslots[JSSLOT_PROXY_HANDLER]); + JS_CALL_OBJECT_TRACER(trc, handler, "__handler__"); + } + if (obj->isFunctionProxy()) { + JSObject *call = JSVAL_TO_OBJECT(obj->fslots[JSSLOT_PROXY_CALL]); + if (call) + JS_CALL_OBJECT_TRACER(trc, call, "__call__"); + JSObject *construct = JSVAL_TO_OBJECT(obj->fslots[JSSLOT_PROXY_CONSTRUCT]); + if (construct) + JS_CALL_OBJECT_TRACER(trc, construct, "__construct__"); + } else { + JS_CALL_VALUE_TRACER(trc, obj->fslots[JSSLOT_PROXY_PRIVATE], "__private__"); + } +} + +static void +proxy_DropProperty(JSContext *cx, JSObject *obj, JSProperty *prop) +{ + JS_ASSERT(obj->isProxy()); +} + +static JSType +proxy_TypeOf_obj(JSContext *cx, JSObject *obj) +{ + return JSTYPE_OBJECT; +} + +extern JSObjectOps js_ObjectProxyObjectOps; + +static const JSObjectMap SharedObjectProxyMap(&js_ObjectProxyObjectOps, JSObjectMap::SHAPELESS); + +JSObjectOps js_ObjectProxyObjectOps = { + &SharedObjectProxyMap, + proxy_LookupProperty, proxy_DefineProperty, + proxy_GetProperty, proxy_SetProperty, + proxy_GetAttributes, proxy_SetAttributes, + proxy_DeleteProperty, js_DefaultValue, + js_Enumerate, js_CheckAccess, + proxy_TypeOf_obj, proxy_TraceObject, + NULL, proxy_DropProperty, + NULL, NULL, + js_HasInstance, NULL +}; + +static JSObjectOps * +obj_proxy_getObjectOps(JSContext *cx, JSClass *clasp) +{ + return &js_ObjectProxyObjectOps; +} + +JS_FRIEND_API(JSClass) js_ObjectProxyClass = { + "ObjectProxy", + JSCLASS_HAS_RESERVED_SLOTS(3) | + JSCLASS_NEW_ENUMERATE, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, NULL, + obj_proxy_getObjectOps, NULL, NULL, NULL, + NULL, NULL, NULL, NULL +}; + +JSBool +proxy_Call(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSObject *proxy = JSVAL_TO_OBJECT(argv[-2]); + JS_ASSERT(proxy->isProxy()); + return js_InternalCall(cx, obj, proxy->fslots[JSSLOT_PROXY_CALL], argc, argv, rval); +} + +JSBool +proxy_Construct(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSObject *proxy = JSVAL_TO_OBJECT(argv[-2]); + JS_ASSERT(proxy->isProxy()); + jsval fval = proxy->fslots[JSSLOT_PROXY_CONSTRUCT]; + if (fval == JSVAL_VOID) { + /* We don't have an explicit constructor trap so allocate a new object and use the call trap. */ + fval = proxy->fslots[JSSLOT_PROXY_CALL]; + JS_ASSERT(JSVAL_IS_OBJECT(fval)); + + /* proxy is the constructor, so get proxy.prototype as the proto of the new object. */ + if (!JSProxy::get(cx, proxy, obj, ATOM_TO_JSID(ATOM(classPrototype)), rval)) + return false; + JSObject *proto = !JSVAL_IS_PRIMITIVE(*rval) ? JSVAL_TO_OBJECT(*rval) : NULL; + + JSObject *newobj = NewObject(cx, &js_ObjectClass, proto, NULL); + *rval = OBJECT_TO_JSVAL(newobj); + + /* If the call returns an object, return that, otherwise the original newobj. */ + if (!js_InternalCall(cx, newobj, proxy->fslots[JSSLOT_PROXY_CALL], argc, argv, rval)) + return false; + if (JSVAL_IS_PRIMITIVE(*rval)) + *rval = OBJECT_TO_JSVAL(newobj); + + return true; + } + return js_InternalCall(cx, obj, fval, argc, argv, rval); +} + +static JSType +proxy_TypeOf_fun(JSContext *cx, JSObject *obj) +{ + return JSTYPE_FUNCTION; +} + +extern JSObjectOps js_FunctionProxyObjectOps; + +static const JSObjectMap SharedFunctionProxyMap(&js_FunctionProxyObjectOps, JSObjectMap::SHAPELESS); + +#define proxy_HasInstance js_FunctionClass.hasInstance + +JSObjectOps js_FunctionProxyObjectOps = { + &SharedFunctionProxyMap, + proxy_LookupProperty, proxy_DefineProperty, + proxy_GetProperty, proxy_SetProperty, + proxy_GetAttributes, proxy_SetAttributes, + proxy_DeleteProperty, js_DefaultValue, + js_Enumerate, js_CheckAccess, + proxy_TypeOf_fun, proxy_TraceObject, + NULL, proxy_DropProperty, + proxy_Call, proxy_Construct, + proxy_HasInstance, NULL +}; + +static JSObjectOps * +fun_proxy_getObjectOps(JSContext *cx, JSClass *clasp) +{ + return &js_FunctionProxyObjectOps; +} + +JS_FRIEND_API(JSClass) js_FunctionProxyClass = { + "FunctionProxy", + JSCLASS_HAS_RESERVED_SLOTS(3) | + JSCLASS_NEW_ENUMERATE, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, NULL, + fun_proxy_getObjectOps, NULL, NULL, NULL, + NULL, NULL, NULL, NULL +}; + +JS_PUBLIC_API(JSObject *) +JS_NewObjectProxy(JSContext *cx, jsval handler, JSObject *proto, JSObject *parent, JSString *className) +{ + JSObject *obj = NewObject(cx, &js_ObjectProxyClass, proto, parent); + if (!obj) + return NULL; + obj->fslots[JSSLOT_PROXY_HANDLER] = handler; + obj->fslots[JSSLOT_PROXY_CLASS] = className ? STRING_TO_JSVAL(className) : JSVAL_VOID; + obj->fslots[JSSLOT_PROXY_PRIVATE] = JSVAL_VOID; + return obj; +} + +JS_PUBLIC_API(JSObject *) +JS_NewFunctionProxy(JSContext *cx, jsval handler, JSObject *proto, JSObject *parent, + JSObject *call, JSObject *construct) +{ + JSObject *obj = NewObject(cx, &js_FunctionProxyClass, proto, parent); + if (!obj) + return NULL; + obj->fslots[JSSLOT_PROXY_HANDLER] = handler; + obj->fslots[JSSLOT_PROXY_CALL] = call ? OBJECT_TO_JSVAL(call) : JSVAL_VOID; + obj->fslots[JSSLOT_PROXY_CONSTRUCT] = construct ? OBJECT_TO_JSVAL(construct) : JSVAL_VOID; + return obj; +} + +static JSObject * +NonNullObject(JSContext *cx, jsval v) +{ + if (JSVAL_IS_PRIMITIVE(v)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_NONNULL_OBJECT); + return NULL; + } + return JSVAL_TO_OBJECT(v); +} + +static JSBool +proxy_create(JSContext *cx, uintN argc, jsval *vp) +{ + if (argc < 1) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED, + "Proxy.create", "0", "s"); + return false; + } + JSObject *handler; + if (!(handler = NonNullObject(cx, vp[2]))) + return false; + JSObject *proto, *parent; + if (argc > 1 && !JSVAL_IS_PRIMITIVE(vp[3])) { + proto = JSVAL_TO_OBJECT(vp[3]); + parent = proto->getParent(); + } else { + JS_ASSERT(VALUE_IS_FUNCTION(cx, vp[0])); + proto = NULL; + parent = JSVAL_TO_OBJECT(vp[0])->getParent(); + } + JSString *className = (argc > 2 && JSVAL_IS_STRING(vp[4])) ? JSVAL_TO_STRING(vp[4]) : NULL; + JSObject *proxy = JS_NewObjectProxy(cx, OBJECT_TO_JSVAL(handler), proto, parent, className); + if (!proxy) + return false; + + *vp = OBJECT_TO_JSVAL(proxy); + return true; +} + +static JSBool +proxy_createFunction(JSContext *cx, uintN argc, jsval *vp) +{ + if (argc < 2) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED, + "Proxy.createFunction", "1", ""); + return false; + } + JSObject *handler; + if (!(handler = NonNullObject(cx, vp[2]))) + return false; + JSObject *proto, *parent; + parent = JSVAL_TO_OBJECT(vp[0])->getParent(); + if (!js_GetClassPrototype(cx, parent, JSProto_Function, &proto)) + return false; + parent = proto->getParent(); + + JSObject *call = js_ValueToCallableObject(cx, &vp[3], JSV2F_SEARCH_STACK); + if (!call) + return false; + JSObject *construct = NULL; + if (argc > 2) { + construct = js_ValueToCallableObject(cx, &vp[4], JSV2F_SEARCH_STACK); + if (!construct) + return false; + } + + JSObject *proxy = JS_NewFunctionProxy(cx, OBJECT_TO_JSVAL(handler), proto, parent, call, construct); + if (!proxy) + return false; + + *vp = OBJECT_TO_JSVAL(proxy); + return true; +} + +#ifdef DEBUG + +static JSBool +proxy_isTrapping(JSContext *cx, uintN argc, jsval *vp) +{ + if (argc < 1) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED, + "Proxy.isTrapping", "0", "s"); + return false; + } + JSObject *obj; + if (!(obj = NonNullObject(cx, vp[2]))) + return false; + *vp = BOOLEAN_TO_JSVAL(obj->isProxy()); + return true; +} + +static JSBool +proxy_fix(JSContext *cx, uintN argc, jsval *vp) +{ + if (argc < 1) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED, + "Proxy.fix", "0", "s"); + return false; + } + JSObject *obj; + if (!(obj = NonNullObject(cx, vp[2]))) + return false; + if (obj->isProxy()) { + JSBool flag; + if (!JS_FixProxy(cx, obj, &flag)) + return false; + *vp = BOOLEAN_TO_JSVAL(flag); + } else { + *vp = JSVAL_TRUE; + } + return true; +} + +#endif + +static JSFunctionSpec static_methods[] = { + JS_FN("create", proxy_create, 2, 0), + JS_FN("createFunction", proxy_createFunction, 3, 0), +#ifdef DEBUG + JS_FN("isTrapping", proxy_isTrapping, 1, 0), + JS_FN("fix", proxy_fix, 1, 0), +#endif + JS_FS_END +}; + +JS_FRIEND_API(JSObject *) +js_InitProxyClass(JSContext *cx, JSObject *obj) +{ + JSObject *module = NewObject(cx, &js_ObjectClass, NULL, obj); + if (!module) + return NULL; + if (!JS_DefineProperty(cx, obj, "Proxy", OBJECT_TO_JSVAL(module), + JS_PropertyStub, JS_PropertyStub, 0)) { + return NULL; + } + if (!JS_DefineFunctions(cx, module, static_methods)) + return NULL; + return obj; +} + +extern JSClass js_CallableObjectClass; + +static const uint32 JSSLOT_CALLABLE_CALL = JSSLOT_PRIVATE; +static const uint32 JSSLOT_CALLABLE_CONSTRUCT = JSSLOT_PRIVATE + 1; + +static JSBool +callable_Call(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSObject *callable = JSVAL_TO_OBJECT(argv[-2]); + JS_ASSERT(callable->getClass() == &js_CallableObjectClass); + jsval fval = callable->fslots[JSSLOT_CALLABLE_CALL]; + return js_InternalCall(cx, obj, fval, argc, argv, rval); +} + +static JSBool +callable_Construct(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSObject *callable = JSVAL_TO_OBJECT(argv[-2]); + JS_ASSERT(callable->getClass() == &js_CallableObjectClass); + jsval fval = callable->fslots[JSSLOT_CALLABLE_CONSTRUCT]; + if (fval == JSVAL_VOID) { + /* We don't have an explicit constructor so allocate a new object and use the call. */ + fval = callable->fslots[JSSLOT_CALLABLE_CALL]; + JS_ASSERT(JSVAL_IS_OBJECT(fval)); + + /* callable is the constructor, so get callable.prototype is the proto of the new object. */ + if (!callable->getProperty(cx, ATOM_TO_JSID(ATOM(classPrototype)), rval)) + return false; + JSObject *proto = !JSVAL_IS_PRIMITIVE(*rval) ? JSVAL_TO_OBJECT(*rval) : NULL; + + JSObject *newobj = NewObject(cx, &js_ObjectClass, proto, NULL); + *rval = OBJECT_TO_JSVAL(newobj); + + /* If the call returns an object, return that, otherwise the original newobj. */ + if (!js_InternalCall(cx, newobj, callable->fslots[JSSLOT_CALLABLE_CALL], argc, argv, rval)) + return false; + if (JSVAL_IS_PRIMITIVE(*rval)) + *rval = OBJECT_TO_JSVAL(newobj); + + return true; + } + return js_InternalCall(cx, obj, fval, argc, argv, rval); +} + +JSClass js_CallableObjectClass = { + "CallableObject", + JSCLASS_HAS_RESERVED_SLOTS(2) | + JSCLASS_NEW_ENUMERATE, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, NULL, + NULL, NULL, callable_Call, callable_Construct, + NULL, NULL, NULL, NULL +}; + +JS_PUBLIC_API(JSBool) +JS_FixProxy(JSContext *cx, JSObject *proxy, JSBool *bp) +{ + AutoValueRooter tvr(cx); + if (!JSProxy::fix(cx, proxy, tvr.addr())) + return false; + if (tvr.value() == JSVAL_VOID) { + *bp = false; + return true; + } + + JSObject *props; + if (!(props = NonNullObject(cx, tvr.value()))) + return false; + + JSObject *proto = proxy->getProto(); + JSObject *parent = proxy->getParent(); + JSClass *clasp = proxy->isFunctionProxy() ? &js_CallableObjectClass : &js_ObjectClass; + + /* Make a blank object from the recipe fix provided to us. */ + JSObject *newborn = NewObjectWithGivenProto(cx, clasp, proto, parent); + if (!newborn) + return NULL; + AutoValueRooter tvr2(cx, newborn); + + if (clasp == &js_CallableObjectClass) { + newborn->fslots[JSSLOT_CALLABLE_CALL] = proxy->fslots[JSSLOT_PROXY_CALL]; + newborn->fslots[JSSLOT_CALLABLE_CONSTRUCT] = proxy->fslots[JSSLOT_PROXY_CONSTRUCT]; + } + + if (!js_PopulateObject(cx, newborn, props)) + return false; + + /* Trade spaces between the newborn object and the proxy. */ + proxy->swap(newborn); + + /* The GC will dispose of the proxy object. */ + + *bp = true; + return true; +} + +JS_PUBLIC_API(JSBool) +JS_Becomes(JSContext *cx, JSObject *obj, JSObject *obj2) +{ +#ifdef JS_THREADSAFE + JS_ASSERT_IF(obj->isNative(), obj->scope()->title.ownercx == cx); + JS_ASSERT_IF(obj2->isNative(), obj2->scope()->title.ownercx == cx); +#endif + obj->swap(obj2); + return true; +} diff --git a/js/src/jsproxy.h b/js/src/jsproxy.h new file mode 100644 index 00000000000..70a313752d5 --- /dev/null +++ b/js/src/jsproxy.h @@ -0,0 +1,232 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released + * May 28, 2008. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Andreas Gal + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsproxy_h___ +#define jsproxy_h___ + +#include "jsapi.h" +#include "jsobj.h" + +/* Base class for all C++ proxy handlers. */ +class JSProxyHandler { + public: + virtual ~JSProxyHandler(); + + /* ES5 Harmony fundamental proxy traps. */ + virtual bool getPropertyDescriptor(JSContext *cx, JSObject *proxy, jsid id, JSPropertyDescriptor *desc) = 0; + virtual bool getOwnPropertyDescriptor(JSContext *cx, JSObject *proxy, jsid id, JSPropertyDescriptor *desc) = 0; + virtual bool defineProperty(JSContext *cx, JSObject *proxy, jsid id, JSPropertyDescriptor *desc) = 0; + virtual bool getOwnPropertyNames(JSContext *cx, JSObject *proxy, JSIdArray **idap) = 0; + virtual bool delete_(JSContext *cx, JSObject *proxy, jsid id, bool *bp) = 0; + virtual bool enumerate(JSContext *cx, JSObject *proxy, JSIdArray **idap) = 0; + virtual bool fix(JSContext *cx, JSObject *proxy, jsval *vp) = 0; + + /* ES5 Harmony derived proxy traps. */ + virtual bool has(JSContext *cx, JSObject *proxy, jsid id, bool *bp); + virtual bool hasOwn(JSContext *cx, JSObject *proxy, jsid id, bool *bp); + virtual bool get(JSContext *cx, JSObject *proxy, JSObject *receiver, jsid id, jsval *vp); + virtual bool set(JSContext *cx, JSObject *proxy, JSObject *receiver, jsid id, jsval *vp); + virtual bool enumerateOwn(JSContext *cx, JSObject *proxy, JSIdArray **idap); + + /* Spidermonkey extensions. */ + virtual void finalize(JSContext *cx, JSObject *proxy); + virtual void *family() = 0; +}; + +/* No-op wrapper handler base class. */ +class JSNoopProxyHandler { + JSObject *mWrappedObject; + + protected: + JS_FRIEND_API(JSNoopProxyHandler(JSObject *)); + + public: + JS_FRIEND_API(virtual ~JSNoopProxyHandler()); + + /* ES5 Harmony fundamental proxy traps. */ + virtual JS_FRIEND_API(bool) getPropertyDescriptor(JSContext *cx, JSObject *proxy, jsid id, JSPropertyDescriptor *desc); + virtual JS_FRIEND_API(bool) getOwnPropertyDescriptor(JSContext *cx, JSObject *proxy, jsid id, JSPropertyDescriptor *desc); + virtual JS_FRIEND_API(bool) defineProperty(JSContext *cx, JSObject *proxy, jsid id, JSPropertyDescriptor *desc); + virtual JS_FRIEND_API(bool) getOwnPropertyNames(JSContext *cx, JSObject *proxy, JSIdArray **idap); + virtual JS_FRIEND_API(bool) delete_(JSContext *cx, JSObject *proxy, jsid id, bool *bp); + virtual JS_FRIEND_API(bool) enumerate(JSContext *cx, JSObject *proxy, JSIdArray **idap); + virtual JS_FRIEND_API(bool) fix(JSContext *cx, JSObject *proxy, jsval *vp); + + /* ES5 Harmony derived proxy traps. */ + virtual JS_FRIEND_API(bool) has(JSContext *cx, JSObject *proxy, jsid id, bool *bp); + virtual JS_FRIEND_API(bool) hasOwn(JSContext *cx, JSObject *proxy, jsid id, bool *bp); + virtual JS_FRIEND_API(bool) get(JSContext *cx, JSObject *proxy, JSObject *receiver, jsid id, jsval *vp); + virtual JS_FRIEND_API(bool) set(JSContext *cx, JSObject *proxy, JSObject *receiver, jsid id, jsval *vp); + virtual JS_FRIEND_API(bool) enumerateOwn(JSContext *cx, JSObject *proxy, JSIdArray **idap); + + /* Spidermonkey extensions. */ + virtual JS_FRIEND_API(void) finalize(JSContext *cx, JSObject *proxy); + virtual JS_FRIEND_API(void) *family(); + + static JSNoopProxyHandler singleton; + + template + static JSObject *wrap(JSContext *cx, JSObject *obj, JSObject *proto, JSObject *parent, JSString *className); + + inline JSObject *wrappedObject(JSObject *proxy) { + return mWrappedObject ? mWrappedObject : JSVAL_TO_OBJECT(proxy->getProxyPrivate()); + } +}; + +/* Dispatch point for handlers that executes the appropriate C++ or scripted traps. */ +class JSProxy { + public: + /* ES5 Harmony fundamental proxy traps. */ + static bool getPropertyDescriptor(JSContext *cx, JSObject *proxy, jsid id, JSPropertyDescriptor *desc); + static bool getPropertyDescriptor(JSContext *cx, JSObject *proxy, jsid id, jsval *vp); + static bool getOwnPropertyDescriptor(JSContext *cx, JSObject *proxy, jsid id, JSPropertyDescriptor *desc); + static bool getOwnPropertyDescriptor(JSContext *cx, JSObject *proxy, jsid id, jsval *vp); + static bool defineProperty(JSContext *cx, JSObject *proxy, jsid id, JSPropertyDescriptor *desc); + static bool defineProperty(JSContext *cx, JSObject *proxy, jsid id, jsval v); + static bool getOwnPropertyNames(JSContext *cx, JSObject *proxy, JSIdArray **idap); + static bool delete_(JSContext *cx, JSObject *proxy, jsid id, bool *bp); + static bool enumerate(JSContext *cx, JSObject *proxy, JSIdArray **idap); + static bool fix(JSContext *cx, JSObject *proxy, jsval *vp); + + /* ES5 Harmony derived proxy traps. */ + static bool has(JSContext *cx, JSObject *proxy, jsid id, bool *bp); + static bool hasOwn(JSContext *cx, JSObject *proxy, jsid id, bool *bp); + static bool get(JSContext *cx, JSObject *proxy, JSObject *receiver, jsid id, jsval *vp); + static bool set(JSContext *cx, JSObject *proxy, JSObject *receiver, jsid id, jsval *vp); + static bool enumerateOwn(JSContext *cx, JSObject *proxy, JSIdArray **idap); +}; + +/* Shared between object and function proxies. */ +const uint32 JSSLOT_PROXY_HANDLER = JSSLOT_PRIVATE + 0; +/* Object proxies only. */ +const uint32 JSSLOT_PROXY_CLASS = JSSLOT_PRIVATE + 1; +const uint32 JSSLOT_PROXY_PRIVATE = JSSLOT_PRIVATE + 2; +/* Function proxies only. */ +const uint32 JSSLOT_PROXY_CALL = JSSLOT_PRIVATE + 1; +const uint32 JSSLOT_PROXY_CONSTRUCT = JSSLOT_PRIVATE + 2; + +extern JS_FRIEND_API(JSClass) js_ObjectProxyClass; +extern JS_FRIEND_API(JSClass) js_FunctionProxyClass; +extern JSClass js_CallableObjectClass; + +inline bool +JSObject::isObjectProxy() const +{ + return getClass() == &js_ObjectProxyClass; +} + +inline bool +JSObject::isFunctionProxy() const +{ + return getClass() == &js_FunctionProxyClass; +} + +inline bool +JSObject::isProxy() const +{ + return isObjectProxy() || isFunctionProxy(); +} + +inline jsval +JSObject::getProxyHandler() const +{ + JS_ASSERT(isProxy()); + jsval handler = fslots[JSSLOT_PROXY_HANDLER]; + JS_ASSERT(JSVAL_IS_OBJECT(handler) || JSVAL_IS_INT(handler)); + return handler; +} + +inline jsval +JSObject::getProxyPrivate() const +{ + JS_ASSERT(isObjectProxy()); + return fslots[JSSLOT_PROXY_PRIVATE]; +} + +inline void +JSObject::setProxyPrivate(jsval priv) +{ + JS_ASSERT(isObjectProxy()); + fslots[JSSLOT_PROXY_PRIVATE] = priv; +} + +JS_PUBLIC_API(JSObject *) +JS_NewObjectProxy(JSContext *cx, jsval handler, JSObject *proto, JSObject *parent, JSString *className); + +JS_PUBLIC_API(JSObject *) +JS_NewFunctionProxy(JSContext *cx, jsval handler, JSObject *proto, JSObject *parent, JSObject *call, JSObject *construct); + +JS_PUBLIC_API(JSBool) +JS_GetProxyObjectClass(JSContext *cx, JSObject *proxy, const char **namep); + +JS_PUBLIC_API(JSBool) +JS_FixProxy(JSContext *cx, JSObject *proxy, JSBool *bp); + +JS_PUBLIC_API(JSBool) +JS_Becomes(JSContext *cx, JSObject *obj, JSObject *obj2); + +template +JSObject * +JSNoopProxyHandler::wrap(JSContext *cx, JSObject *obj, JSObject *proto, JSObject *parent, JSString *className) +{ + if (obj->isCallable()) { + JSNoopProxyHandler *handler = new T(obj); + if (!handler) + return NULL; + JSObject *wrapper = JS_NewFunctionProxy(cx, PRIVATE_TO_JSVAL(handler), proto, parent, obj, NULL); + if (!wrapper) + delete handler; + return wrapper; + } + JSObject *wrapper = JS_NewObjectProxy(cx, PRIVATE_TO_JSVAL(&T::singleton), proto, parent, className); + if (wrapper) + wrapper->setProxyPrivate(OBJECT_TO_JSVAL(obj)); + return wrapper; +} + +JS_BEGIN_EXTERN_C + +extern JS_FRIEND_API(JSObject *) +js_InitProxyClass(JSContext *cx, JSObject *obj); + +JS_END_EXTERN_C + +#endif diff --git a/js/src/jsscope.h b/js/src/jsscope.h index 501e3ab0791..b7f01fd7f99 100644 --- a/js/src/jsscope.h +++ b/js/src/jsscope.h @@ -675,13 +675,7 @@ struct JSScopeProperty { }; JSScopeProperty(jsid id, JSPropertyOp getter, JSPropertyOp setter, uint32 slot, - uintN attrs, uintN flags, intN shortid) - : id(id), rawGetter(getter), rawSetter(setter), slot(slot), attrs(uint8(attrs)), - flags(uint8(flags)), shortid(int16(shortid)) - { - JS_ASSERT_IF(getter && (attrs & JSPROP_GETTER), getterObj->isCallable()); - JS_ASSERT_IF(setter && (attrs & JSPROP_SETTER), setterObj->isCallable()); - } + uintN attrs, uintN flags, intN shortid); bool marked() const { return (flags & MARK) != 0; } void mark() { flags |= MARK; } diff --git a/js/src/jsscopeinlines.h b/js/src/jsscopeinlines.h index a9a074c9b91..8460ac4f6f5 100644 --- a/js/src/jsscopeinlines.h +++ b/js/src/jsscopeinlines.h @@ -46,6 +46,8 @@ #include "jsobj.h" #include "jsscope.h" +#include "jsobjinlines.h" + inline JSEmptyScope * JSScope::createEmptyScope(JSContext *cx, JSClass *clasp) { @@ -209,6 +211,16 @@ JSScope::trace(JSTracer *trc) } } +inline +JSScopeProperty::JSScopeProperty(jsid id, JSPropertyOp getter, JSPropertyOp setter, + uint32 slot, uintN attrs, uintN flags, intN shortid) + : id(id), rawGetter(getter), rawSetter(setter), slot(slot), attrs(uint8(attrs)), + flags(uint8(flags)), shortid(int16(shortid)) +{ + JS_ASSERT_IF(getter && (attrs & JSPROP_GETTER), getterObj->isCallable()); + JS_ASSERT_IF(setter && (attrs & JSPROP_SETTER), setterObj->isCallable()); +} + inline JSDHashNumber JSScopeProperty::hash() const { diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index e295eee0c7e..17f4d0d1392 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -65,6 +65,7 @@ #include "jsxdrapi.h" #endif +#include "jsobjinlines.h" #include "jsscriptinlines.h" using namespace js; diff --git a/js/src/jsxdrapi.cpp b/js/src/jsxdrapi.cpp index 5f5707f5e81..d9aedca9d93 100644 --- a/js/src/jsxdrapi.cpp +++ b/js/src/jsxdrapi.cpp @@ -54,6 +54,8 @@ #include "jsstr.h" #include "jsxdrapi.h" +#include "jsobjinlines.h" + #ifdef DEBUG #define DBG(x) x #else diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 53a456247d2..2b0ea91a046 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -53,6 +53,7 @@ #include "jsarena.h" #include "jsutil.h" #include "jsprf.h" +#include "jsproxy.h" #include "jsapi.h" #include "jsarray.h" #include "jsatom.h" @@ -86,6 +87,8 @@ #include "jsworkers.h" +#include "jsobjinlines.h" + #ifdef XP_UNIX #include #include @@ -3858,6 +3861,23 @@ Snarf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) return JS_TRUE; } +JSBool +Wrap(JSContext *cx, uintN argc, jsval *vp) +{ + jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID; + if (JSVAL_IS_PRIMITIVE(v)) { + JS_SET_RVAL(cx, vp, v); + return true; + } + + JSObject *wrapped = JSNoopProxyHandler::wrap(cx, JSVAL_TO_OBJECT(v), NULL, NULL, NULL); + if (!wrapped) + return false; + + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(wrapped)); + return true; +} + /* We use a mix of JS_FS and JS_FN to test both kinds of natives. */ static JSFunctionSpec shell_functions[] = { JS_FS("version", Version, 0,0,0), @@ -3942,6 +3962,7 @@ static JSFunctionSpec shell_functions[] = { JS_FN("timeout", Timeout, 1,0), JS_FN("elapsed", Elapsed, 0,0), JS_FN("parent", Parent, 1,0), + JS_FN("wrap", Wrap, 1,0), JS_FS_END }; @@ -4051,6 +4072,7 @@ static const char *const shell_help_messages[] = { " A negative value (default) means that the execution time is unlimited.", "elapsed() Execution time elapsed for the current context.", "parent(obj) Returns the parent of obj.\n", +"wrap(obj) Wrap an object into a noop wrapper.\n" }; /* Help messages must match shell functions. */ diff --git a/js/src/tests/jstests.list b/js/src/tests/jstests.list index 93253d9a8a9..b324344ed38 100644 --- a/js/src/tests/jstests.list +++ b/js/src/tests/jstests.list @@ -14,3 +14,4 @@ include js1_7/jstests.list include js1_8/jstests.list include js1_8_1/jstests.list include js1_8_5/jstests.list +include proxies/jstests.list diff --git a/js/src/tests/proxies/browser.js b/js/src/tests/proxies/browser.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/js/src/tests/proxies/jstests.list b/js/src/tests/proxies/jstests.list new file mode 100644 index 00000000000..2513473e034 --- /dev/null +++ b/js/src/tests/proxies/jstests.list @@ -0,0 +1,2 @@ +url-prefix ../../jsreftest.html?test=proxies/ +script scripted.js diff --git a/js/src/tests/proxies/scripted.js b/js/src/tests/proxies/scripted.js new file mode 100644 index 00000000000..ed9b66f22a1 --- /dev/null +++ b/js/src/tests/proxies/scripted.js @@ -0,0 +1,154 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + * Contributor: Andreas Gal + */ + +/* Test object proxies. */ + +function noopHandlerMaker(obj) { + return { + getOwnPropertyDescriptor: function(name) { + var desc = Object.getOwnPropertyDescriptor(obj); + // a trapping proxy's properties must always be configurable + desc.configurable = true; + return desc; + }, + getPropertyDescriptor: function(name) { + var desc = Object.getPropertyDescriptor(obj); // assumed + // a trapping proxy's properties must always be configurable + desc.configurable = true; + return desc; + }, + getOwnPropertyNames: function() { + return Object.getOwnPropertyNames(obj); + }, + defineProperty: function(name, desc) { + return Object.defineProperty(obj, name, desc); + }, + delete: function(name) { return delete obj[name]; }, + fix: function() { + // As long as obj is not frozen, the proxy won't allow itself to be fixed + // if (!Object.isFrozen(obj)) [not implemented in SpiderMonkey] + // return undefined; + // return Object.getOwnProperties(obj); // assumed [not implemented in SpiderMonkey] + var props = {}; + for (x in obj) + props[x] = Object.getOwnPropertyDescriptor(obj, x); + return props; + }, + has: function(name) { return name in obj; }, + hasOwn: function(name) { return ({}).hasOwnProperty.call(obj, name); }, + get: function(receiver, name) { return obj[name]; }, + set: function(receiver, name, val) { obj[name] = val; return true; }, // bad behavior when set fails in non-strict mode + enumerate: function() { + var result = []; + for (name in obj) { result.push(name); }; + return result; + }, + enumerateOwn: function() { return Object.keys(obj); } + }; +}; + +function testNoopHandler(obj, proxy) { + /* Check that both objects see the same properties. */ + for (x in obj) + assertEq(obj[x], proxy[x]); + for (x in proxy) + assertEq(obj[x], proxy[x]); + /* Check that the iteration order is the same. */ + var a = [], b = []; + for (x in obj) + a.push(x); + for (x in proxy) + b.push(x); + assertEq(uneval(a), uneval(b)); +} + +function testObj(obj) { + var proxy = Proxy.create(noopHandlerMaker(obj)); + testNoopHandler(obj, proxy); + assertEq(typeof proxy, "object"); + if ("isTrapping" in Proxy) { + assertEq(Proxy.isTrapping(proxy), true); + assertEq(Proxy.fix(proxy), true); + assertEq(Proxy.isTrapping(proxy), false); + assertEq(typeof proxy, "object"); + testNoopHandler(obj, proxy); + } +} + +testObj({ foo: 1, bar: 2 }); +testObj({ 1: 2, 3: 4 }); +testObj([ 1, 2, 3 ]); +testObj(new Date()); +testObj(new Array()); +testObj(new RegExp()); +testObj(Date); +testObj(Array); +testObj(RegExp); + +reportCompare(0, 0, "Proxies: Object proxies."); + +/* Test function proxies. */ + +var proxy = Proxy.createFunction({ + get: function(obj,name) { return Function.prototype[name]; }, + fix: function() { + return ({}); + } +}, function() { return "call"; }); + +assertEq(proxy(), "call"); +assertEq(typeof proxy, "function"); +if ("isTrapping" in Proxy) { + assertEq(Proxy.isTrapping(proxy), true); + assertEq(Proxy.fix(proxy), true); + assertEq(Proxy.isTrapping(proxy), false); + assertEq(typeof proxy, "function"); + assertEq(proxy(), "call"); +} + +reportCompare(0, 0, "Proxies: Function proxies."); + +/* Test function proxies as constructors. */ + +var proxy = Proxy.createFunction({ + get: function(obj, name) { return Function.prototype[name]; }, + fix: function() { return ({}); } +}, +function() { var x = {}; x.origin = "call"; return x; }, +function() { var x = {}; x.origin = "new"; return x; }) + +assertEq(proxy().origin, "call"); +assertEq((new proxy()).origin, "new"); +if ("fix" in Proxy) { + assertEq(Proxy.fix(proxy), true); + assertEq(proxy().origin, "call"); + assertEq((new proxy()).origin, "new"); +} + +reportCompare(0, 0, "Proxies: Function proxies as constructor."); + +/* Test fallback on call if no construct trap was given. */ + +var proxy = Proxy.createFunction({ + get: function(obj, name) { return Function.prototype[name]; }, + fix: function() { return ({}); } +}, +function() { this.origin = "new"; return "new-ret"; }); + +assertEq((new proxy()).origin, "new"); +if ("fix" in Proxy) { + assertEq(Proxy.fix(proxy), true); + assertEq((new proxy()).origin, "new"); +} + +reportCompare(0, 0, "Proxies: No constructor trap supplied."); + +/* Test invoke. */ + +var proxy = Proxy.create({ get: function(obj,name) { return function(a,b,c) { return name + uneval([a,b,c]); } }}); +assertEq(proxy.foo(1,2,3), "foo[1, 2, 3]"); + +reportCompare(0, 0, "Proxies: Test invoke."); diff --git a/js/src/tests/proxies/shell.js b/js/src/tests/proxies/shell.js new file mode 100644 index 00000000000..e69de29bb2d