mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-11 20:35:50 +00:00
Implement SCOPE_IS_SEALED, JS_SealObject, JS_UnsealObject, etc. to support
sealed standard object graphs, as well as to pave the way for optimizations to object literals (94693, r=shaver).
This commit is contained in:
parent
d53c9da12d
commit
25b813e602
30
js/src/js.c
30
js/src/js.c
@ -1415,6 +1415,32 @@ Clone(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
||||
static JSBool
|
||||
Seal(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
||||
{
|
||||
JSObject *target;
|
||||
JSBool deep = JS_FALSE;
|
||||
|
||||
if (!JS_ConvertArguments(cx, argc, argv, "o/b", &target, &deep))
|
||||
return JS_FALSE;
|
||||
if (!target)
|
||||
return JS_TRUE;
|
||||
return JS_SealObject(cx, target, deep);
|
||||
}
|
||||
|
||||
static JSBool
|
||||
Unseal(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
||||
{
|
||||
JSObject *target;
|
||||
JSBool deep = JS_FALSE;
|
||||
|
||||
if (!JS_ConvertArguments(cx, argc, argv, "o/b", &target, &deep))
|
||||
return JS_FALSE;
|
||||
if (!target)
|
||||
return JS_TRUE;
|
||||
return JS_UnsealObject(cx, target, deep);
|
||||
}
|
||||
|
||||
static JSFunctionSpec shell_functions[] = {
|
||||
{"version", Version, 0},
|
||||
{"options", Options, 0},
|
||||
@ -1444,6 +1470,8 @@ static JSFunctionSpec shell_functions[] = {
|
||||
{"clear", Clear, 0},
|
||||
{"intern", Intern, 1},
|
||||
{"clone", Clone, 1},
|
||||
{"seal", Seal, 1, 0, 1},
|
||||
{"unseal", Unseal, 1, 0, 1},
|
||||
{0}
|
||||
};
|
||||
|
||||
@ -1478,6 +1506,8 @@ static char *shell_help_messages[] = {
|
||||
"clear([obj]) Clear properties of object",
|
||||
"intern(str) Internalize str in the atom table",
|
||||
"clone(fun[, scope]) Clone function object",
|
||||
"seal(obj[, deep]) Seal object, or object graph if deep",
|
||||
"unseal(obj[, deep]) Unseal object, or object graph if deep",
|
||||
0
|
||||
};
|
||||
|
||||
|
@ -240,3 +240,5 @@ MSG_DEF(JSMSG_REDECLARED_PARAM, 164, 1, JSEXN_TYPEERR, "redeclaration of f
|
||||
MSG_DEF(JSMSG_NEWREGEXP_FLAGGED, 165, 0, JSEXN_TYPEERR, "can't supply flags when constructing one RegExp from another")
|
||||
MSG_DEF(JSMSG_RESERVED_SLOT_RANGE, 166, 0, JSEXN_RANGEERR, "reserved slot index out of range")
|
||||
MSG_DEF(JSMSG_CANT_DECODE_PRINCIPALS, 167, 0, JSEXN_INTERNALERR, "can't decode JSPrincipals")
|
||||
MSG_DEF(JSMSG_CANT_SEAL_OBJECT, 168, 1, JSEXN_ERR, "can't seal {0} objects")
|
||||
MSG_DEF(JSMSG_CANT_UNSEAL_OBJECT, 169, 1, JSEXN_ERR, "can't unseal {0} objects")
|
||||
|
@ -2020,6 +2020,100 @@ JS_NewObject(JSContext *cx, JSClass *clasp, JSObject *proto, JSObject *parent)
|
||||
return js_NewObject(cx, clasp, proto, parent);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_SealObject(JSContext *cx, JSObject *obj, JSBool deep)
|
||||
{
|
||||
JSScope *scope;
|
||||
JSIdArray *ida;
|
||||
uint32 nslots;
|
||||
jsval v, *vp, *end;
|
||||
|
||||
if (!OBJ_IS_NATIVE(obj)) {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
||||
JSMSG_CANT_SEAL_OBJECT,
|
||||
OBJ_GET_CLASS(cx, obj)->name);
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
||||
/* Nothing to do if obj's scope is already sealed. */
|
||||
scope = OBJ_SCOPE(obj);
|
||||
#ifdef JS_THREADSAFE
|
||||
JS_ASSERT(scope->ownercx == cx);
|
||||
#endif
|
||||
if (SCOPE_IS_SEALED(scope))
|
||||
return JS_TRUE;
|
||||
|
||||
/* XXX Enumerate lazy properties now, as they can't be added later. */
|
||||
ida = JS_Enumerate(cx, obj);
|
||||
if (!ida)
|
||||
return JS_FALSE;
|
||||
JS_DestroyIdArray(cx, ida);
|
||||
|
||||
/* Ensure that obj has its own, mutable scope, and seal that scope. */
|
||||
JS_LOCK_OBJ(cx, obj);
|
||||
scope = js_GetMutableScope(cx, obj);
|
||||
if (scope)
|
||||
SCOPE_SET_SEALED(scope);
|
||||
JS_UNLOCK_SCOPE(cx, scope);
|
||||
if (!scope)
|
||||
return JS_FALSE;
|
||||
|
||||
/* If we are not sealing an entire object graph, we're done. */
|
||||
if (!deep)
|
||||
return JS_TRUE;
|
||||
|
||||
/* Walk obj->slots and if any value is a non-null object, seal it. */
|
||||
nslots = JS_MIN(scope->map.freeslot, scope->map.nslots);
|
||||
for (vp = obj->slots, end = vp + nslots; vp < end; vp++) {
|
||||
v = *vp;
|
||||
if (JSVAL_IS_PRIMITIVE(v))
|
||||
continue;
|
||||
if (!JS_SealObject(cx, JSVAL_TO_OBJECT(v), deep))
|
||||
return JS_FALSE;
|
||||
}
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_UnsealObject(JSContext *cx, JSObject *obj, JSBool deep)
|
||||
{
|
||||
JSScope *scope;
|
||||
uint32 nslots;
|
||||
jsval v, *vp, *end;
|
||||
|
||||
if (!OBJ_IS_NATIVE(obj)) {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
||||
JSMSG_CANT_UNSEAL_OBJECT,
|
||||
OBJ_GET_CLASS(cx, obj)->name);
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
||||
scope = OBJ_SCOPE(obj);
|
||||
#ifdef JS_THREADSAFE
|
||||
JS_ASSERT(scope->ownercx == cx);
|
||||
#endif
|
||||
if (!SCOPE_IS_SEALED(scope))
|
||||
return JS_TRUE;
|
||||
|
||||
JS_ASSERT(scope == OBJ_SCOPE(obj));
|
||||
JS_LOCK_SCOPE(cx, scope);
|
||||
SCOPE_CLR_SEALED(scope);
|
||||
JS_UNLOCK_SCOPE(cx, scope);
|
||||
|
||||
if (!deep)
|
||||
return JS_TRUE;
|
||||
|
||||
nslots = JS_MIN(scope->map.freeslot, scope->map.nslots);
|
||||
for (vp = obj->slots, end = vp + nslots; vp < end; vp++) {
|
||||
v = *vp;
|
||||
if (JSVAL_IS_PRIMITIVE(v))
|
||||
continue;
|
||||
if (!JS_UnsealObject(cx, JSVAL_TO_OBJECT(v), deep))
|
||||
return JS_FALSE;
|
||||
}
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSObject *)
|
||||
JS_ConstructObject(JSContext *cx, JSClass *clasp, JSObject *proto,
|
||||
JSObject *parent)
|
||||
|
@ -897,6 +897,12 @@ JS_GetConstructor(JSContext *cx, JSObject *proto);
|
||||
extern JS_PUBLIC_API(JSObject *)
|
||||
JS_NewObject(JSContext *cx, JSClass *clasp, JSObject *proto, JSObject *parent);
|
||||
|
||||
extern JS_PUBLIC_API(JSBool)
|
||||
JS_SealObject(JSContext *cx, JSObject *obj, JSBool deep);
|
||||
|
||||
extern JS_PUBLIC_API(JSBool)
|
||||
JS_UnsealObject(JSContext *cx, JSObject *obj, JSBool deep);
|
||||
|
||||
extern JS_PUBLIC_API(JSObject *)
|
||||
JS_ConstructObject(JSContext *cx, JSClass *clasp, JSObject *proto,
|
||||
JSObject *parent);
|
||||
|
@ -1965,10 +1965,12 @@ js_Interpret(JSContext *cx, jsval *result)
|
||||
if (!OBJ_IS_NATIVE(obj)) { \
|
||||
ok = call; \
|
||||
} else { \
|
||||
JSScope *scope_; \
|
||||
JS_LOCK_OBJ(cx, obj); \
|
||||
PROPERTY_CACHE_TEST(&rt->propertyCache, obj, id, sprop); \
|
||||
if (sprop && !(sprop->attrs & JSPROP_READONLY)) { \
|
||||
JSScope *scope_ = OBJ_SCOPE(obj); \
|
||||
if (sprop && \
|
||||
!(sprop->attrs & JSPROP_READONLY) && \
|
||||
(scope_ = OBJ_SCOPE(obj), !SCOPE_IS_SEALED(scope_))) { \
|
||||
JS_UNLOCK_SCOPE(cx, scope_); \
|
||||
ok = SPROP_SET(cx, sprop, obj, obj, &rval); \
|
||||
JS_LOCK_SCOPE(cx, scope_); \
|
||||
|
@ -2605,8 +2605,15 @@ js_SetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
|
||||
getter = clasp->getProperty;
|
||||
setter = clasp->setProperty;
|
||||
if (sprop) {
|
||||
/*
|
||||
* Set scope for use below. It was locked by js_LookupProperty, and
|
||||
* we know pobj owns it (i.e., scope->object == pobj). Therefore we
|
||||
* optimize JS_UNLOCK_OBJ(cx, pobj) into JS_UNLOCK_SCOPE(cx, scope).
|
||||
*/
|
||||
scope = OBJ_SCOPE(pobj);
|
||||
|
||||
attrs = sprop->attrs;
|
||||
if (attrs & JSPROP_READONLY) {
|
||||
if ((attrs & JSPROP_READONLY) || SCOPE_IS_SEALED(scope)) {
|
||||
/* XXXbe ECMA violation: readonly proto-property stops set cold. */
|
||||
OBJ_DROP_PROPERTY(cx, pobj, (JSProperty *)sprop);
|
||||
if (!JSVERSION_IS_ECMA(cx->version)) {
|
||||
@ -2624,13 +2631,6 @@ js_SetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set scope for use below. It was locked by js_LookupProperty, and
|
||||
* we know pobj owns it (i.e., scope->object == pobj). Therefore we
|
||||
* optimize JS_UNLOCK_OBJ(cx, pobj) into JS_UNLOCK_SCOPE(cx, scope).
|
||||
*/
|
||||
scope = OBJ_SCOPE(pobj);
|
||||
|
||||
if (pobj != obj) {
|
||||
/* Don't clone a setter or shared prototype property. */
|
||||
if (attrs & (JSPROP_SETTER | JSPROP_SHARED)) {
|
||||
|
@ -50,6 +50,7 @@
|
||||
#include "jsdbgapi.h"
|
||||
#include "jslock.h"
|
||||
#include "jsnum.h"
|
||||
#include "jsopcode.h"
|
||||
#include "jsscope.h"
|
||||
#include "jsstr.h"
|
||||
|
||||
@ -840,6 +841,21 @@ CheckAncestorLine(JSScope *scope, JSBool sparse)
|
||||
#define CHECK_ANCESTOR_LINE(scope, sparse) /* nothing */
|
||||
#endif
|
||||
|
||||
static void
|
||||
ReportReadOnlyScope(JSContext *cx, JSScope *scope)
|
||||
{
|
||||
JSString *str;
|
||||
|
||||
str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK,
|
||||
OBJECT_TO_JSVAL(scope->object),
|
||||
NULL);
|
||||
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_READ_ONLY,
|
||||
str
|
||||
? JS_GetStringBytes(str)
|
||||
: LOCKED_OBJ_GET_CLASS(scope->object)->name);
|
||||
}
|
||||
|
||||
JSScopeProperty *
|
||||
js_AddScopeProperty(JSContext *cx, JSScope *scope, jsid id,
|
||||
JSPropertyOp getter, JSPropertyOp setter, uint32 slot,
|
||||
@ -851,6 +867,17 @@ js_AddScopeProperty(JSContext *cx, JSScope *scope, jsid id,
|
||||
|
||||
CHECK_ANCESTOR_LINE(scope, JS_TRUE);
|
||||
|
||||
/*
|
||||
* You can't add properties to a sealed scope. But note well that you can
|
||||
* change property attributes in a sealed scope, even though that replaces
|
||||
* a JSScopeProperty * in the scope's hash table -- but no id is added, so
|
||||
* the scope remains sealed.
|
||||
*/
|
||||
if (SCOPE_IS_SEALED(scope)) {
|
||||
ReportReadOnlyScope(cx, scope);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Normalize stub getter and setter values for faster is-stub testing in
|
||||
* the SPROP_CALL_[GS]ETTER macros.
|
||||
@ -1273,6 +1300,10 @@ js_RemoveScopeProperty(JSContext *cx, JSScope *scope, jsid id)
|
||||
|
||||
JS_ASSERT(JS_IS_SCOPE_LOCKED(scope));
|
||||
CHECK_ANCESTOR_LINE(scope, JS_TRUE);
|
||||
if (SCOPE_IS_SEALED(scope)) {
|
||||
ReportReadOnlyScope(cx, scope);
|
||||
return JS_FALSE;
|
||||
}
|
||||
METER(removes);
|
||||
|
||||
spp = js_SearchScope(scope, id, JS_FALSE);
|
||||
|
@ -66,7 +66,8 @@
|
||||
* attributes, tiny or short id, and a field telling for..in order. Note that
|
||||
* labels are not unique in the tree, but they are unique among a node's kids
|
||||
* (barring rare and benign multi-threaded race condition outcomes, see below)
|
||||
* and along any ancestor line from the tree root to a given leaf node.
|
||||
* and along any ancestor line from the tree root to a given leaf node (except
|
||||
* for the hard case of duplicate formal parameters to a function).
|
||||
*
|
||||
* Thus the root of the tree represents all empty scopes, and the first ply
|
||||
* of the tree represents all scopes containing one property, etc. Each node
|
||||
@ -223,11 +224,16 @@ struct JSScope {
|
||||
|
||||
/* Scope flags and some macros to hide them from other files than jsscope.c. */
|
||||
#define SCOPE_MIDDLE_DELETE 0x0001
|
||||
#define SCOPE_SEALED 0x0002
|
||||
|
||||
#define SCOPE_HAD_MIDDLE_DELETE(scope) ((scope)->flags & SCOPE_MIDDLE_DELETE)
|
||||
#define SCOPE_SET_MIDDLE_DELETE(scope) ((scope)->flags |= SCOPE_MIDDLE_DELETE)
|
||||
#define SCOPE_CLR_MIDDLE_DELETE(scope) ((scope)->flags &= ~SCOPE_MIDDLE_DELETE)
|
||||
|
||||
#define SCOPE_IS_SEALED(scope) ((scope)->flags & SCOPE_SEALED)
|
||||
#define SCOPE_SET_SEALED(scope) ((scope)->flags |= SCOPE_SEALED)
|
||||
#define SCOPE_CLR_SEALED(scope) ((scope)->flags &= ~SCOPE_SEALED)
|
||||
|
||||
/*
|
||||
* A little information hiding for scope->lastProp, in case it ever becomes
|
||||
* a tagged pointer again.
|
||||
|
Loading…
Reference in New Issue
Block a user