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:
brendan%mozilla.org 2003-03-14 05:24:58 +00:00
parent d53c9da12d
commit 25b813e602
8 changed files with 182 additions and 11 deletions

View File

@ -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
};

View File

@ -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")

View File

@ -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)

View File

@ -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);

View File

@ -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_); \

View File

@ -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)) {

View File

@ -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);

View File

@ -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.