📄 jsscope.c
字号:
#ifdef DEBUG_notbrendan
#define CHECK_ANCESTOR_LINE(scope, sparse) \
JS_BEGIN_MACRO \
if ((scope)->table) CheckAncestorLine(scope, sparse); \
JS_END_MACRO
static void
CheckAncestorLine(JSScope *scope, JSBool sparse)
{
uint32 size;
JSScopeProperty **spp, **start, **end, *ancestorLine, *sprop, *aprop;
uint32 entryCount, ancestorCount;
ancestorLine = SCOPE_LAST_PROP(scope);
if (ancestorLine)
JS_ASSERT(SCOPE_HAS_PROPERTY(scope, ancestorLine));
entryCount = 0;
size = SCOPE_CAPACITY(scope);
start = scope->table;
for (spp = start, end = start + size; spp < end; spp++) {
sprop = SPROP_FETCH(spp);
if (sprop) {
entryCount++;
for (aprop = ancestorLine; aprop; aprop = aprop->parent) {
if (aprop == sprop)
break;
}
JS_ASSERT(aprop);
}
}
JS_ASSERT(entryCount == scope->entryCount);
ancestorCount = 0;
for (sprop = ancestorLine; sprop; sprop = sprop->parent) {
if (SCOPE_HAD_MIDDLE_DELETE(scope) &&
!SCOPE_HAS_PROPERTY(scope, sprop)) {
JS_ASSERT(sparse || (sprop->flags & SPROP_IS_DUPLICATE));
continue;
}
ancestorCount++;
}
JS_ASSERT(ancestorCount == scope->entryCount);
}
#else
#define CHECK_ANCESTOR_LINE(scope, sparse) /* nothing */
#endif
static void
ReportReadOnlyScope(JSContext *cx, JSScope *scope)
{
JSString *str;
str = js_ValueToString(cx, OBJECT_TO_JSVAL(scope->object));
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,
uintN attrs, uintN flags, intN shortid)
{
JSScopeProperty **spp, *sprop, *overwriting, **spvec, **spp2, child;
uint32 size, splen, i;
int change;
JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, scope));
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.
*/
if (getter == JS_PropertyStub)
getter = NULL;
if (setter == JS_PropertyStub)
setter = NULL;
/*
* Search for id in order to claim its entry, allocating a property tree
* node if one doesn't already exist for our parameters.
*/
spp = js_SearchScope(scope, id, JS_TRUE);
sprop = overwriting = SPROP_FETCH(spp);
if (!sprop) {
/* Check whether we need to grow, if the load factor is >= .75. */
size = SCOPE_CAPACITY(scope);
if (scope->entryCount + scope->removedCount >= size - (size >> 2)) {
if (scope->removedCount >= size >> 2) {
METER(compresses);
change = 0;
} else {
METER(grows);
change = 1;
}
if (!ChangeScope(cx, scope, change) &&
scope->entryCount + scope->removedCount == size - 1) {
METER(addFailures);
return NULL;
}
spp = js_SearchScope(scope, id, JS_TRUE);
JS_ASSERT(!SPROP_FETCH(spp));
}
} else {
/* Property exists: js_SearchScope must have returned a valid entry. */
JS_ASSERT(!SPROP_IS_REMOVED(*spp));
/*
* If all property members match, this is a redundant add and we can
* return early. If the caller wants to allocate a slot, but doesn't
* care which slot, copy sprop->slot into slot so we can match sprop,
* if all other members match.
*/
if (!(attrs & JSPROP_SHARED) &&
slot == SPROP_INVALID_SLOT &&
SPROP_HAS_VALID_SLOT(sprop, scope)) {
slot = sprop->slot;
}
if (SPROP_MATCH_PARAMS_AFTER_ID(sprop, getter, setter, slot, attrs,
flags, shortid)) {
METER(redundantAdds);
return sprop;
}
/*
* Duplicate formal parameters require us to leave the old property
* on the ancestor line, so the decompiler can find it, even though
* its entry in scope->table is overwritten to point at a new property
* descending from the old one. The SPROP_IS_DUPLICATE flag helps us
* cope with the consequent disparity between ancestor line height and
* scope->entryCount.
*/
if (flags & SPROP_IS_DUPLICATE) {
sprop->flags |= SPROP_IS_DUPLICATE;
} else {
/*
* If we are clearing sprop to force an existing property to be
* overwritten (apart from a duplicate formal parameter), we must
* unlink it from the ancestor line at scope->lastProp, lazily if
* sprop is not lastProp. And we must remove the entry at *spp,
* precisely so the lazy "middle delete" fixup code further below
* won't find sprop in scope->table, in spite of sprop being on
* the ancestor line.
*
* When we finally succeed in finding or creating a new sprop
* and storing its pointer at *spp, we'll use the |overwriting|
* local saved when we first looked up id to decide whether we're
* indeed creating a new entry, or merely overwriting an existing
* property.
*/
if (sprop == SCOPE_LAST_PROP(scope)) {
do {
SCOPE_REMOVE_LAST_PROP(scope);
if (!SCOPE_HAD_MIDDLE_DELETE(scope))
break;
sprop = SCOPE_LAST_PROP(scope);
} while (sprop && !SCOPE_HAS_PROPERTY(scope, sprop));
} else if (!SCOPE_HAD_MIDDLE_DELETE(scope)) {
/*
* If we have no hash table yet, we need one now. The middle
* delete code is simple-minded that way!
*/
if (!scope->table) {
if (!CreateScopeTable(scope)) {
JS_ReportOutOfMemory(cx);
return NULL;
}
spp = js_SearchScope(scope, id, JS_TRUE);
sprop = overwriting = SPROP_FETCH(spp);
}
SCOPE_SET_MIDDLE_DELETE(scope);
}
}
/*
* If we fail later on trying to find or create a new sprop, we will
* goto fail_overwrite and restore *spp from |overwriting|. Note that
* we don't bother to keep scope->removedCount in sync, because we'll
* fix up *spp and scope->entryCount shortly, no matter how control
* flow returns from this function.
*/
if (scope->table)
SPROP_STORE_PRESERVING_COLLISION(spp, NULL);
scope->entryCount--;
CHECK_ANCESTOR_LINE(scope, JS_TRUE);
sprop = NULL;
}
if (!sprop) {
/*
* If properties were deleted from the middle of the list starting at
* scope->lastProp, we may need to fork the property tree and squeeze
* all deleted properties out of scope's ancestor line. Otherwise we
* risk adding a node with the same id as a "middle" node, violating
* the rule that properties along an ancestor line have distinct ids
* (unless flagged SPROP_IS_DUPLICATE).
*/
if (SCOPE_HAD_MIDDLE_DELETE(scope)) {
JS_ASSERT(scope->table);
CHECK_ANCESTOR_LINE(scope, JS_TRUE);
splen = scope->entryCount;
if (splen == 0) {
JS_ASSERT(scope->lastProp == NULL);
} else {
/*
* Enumerate live entries in scope->table using a temporary
* vector, by walking the (possibly sparse, due to deletions)
* ancestor line from scope->lastProp.
*/
spvec = (JSScopeProperty **)
JS_malloc(cx, SCOPE_TABLE_NBYTES(splen));
if (!spvec)
goto fail_overwrite;
i = splen;
sprop = SCOPE_LAST_PROP(scope);
JS_ASSERT(sprop);
do {
/*
* NB: test SCOPE_GET_PROPERTY, not SCOPE_HAS_PROPERTY --
* the latter insists that sprop->id maps to sprop, while
* the former simply tests whether sprop->id is bound in
* scope. We must allow for duplicate formal parameters
* along the ancestor line, and fork them as needed.
*/
if (!SCOPE_GET_PROPERTY(scope, sprop->id))
continue;
JS_ASSERT(sprop != overwriting);
if (i == 0) {
/*
* If our original splen estimate, scope->entryCount,
* is less than the ancestor line height, there must
* be duplicate formal parameters in this (function
* object) scope. Count remaining ancestors in order
* to realloc spvec.
*/
JSScopeProperty *tmp = sprop;
do {
if (SCOPE_GET_PROPERTY(scope, tmp->id))
i++;
} while ((tmp = tmp->parent) != NULL);
spp2 = (JSScopeProperty **)
JS_realloc(cx, spvec, SCOPE_TABLE_NBYTES(splen+i));
if (!spp2) {
JS_free(cx, spvec);
goto fail_overwrite;
}
spvec = spp2;
memmove(spvec + i, spvec, SCOPE_TABLE_NBYTES(splen));
splen += i;
}
spvec[--i] = sprop;
} while ((sprop = sprop->parent) != NULL);
JS_ASSERT(i == 0);
/*
* Now loop forward through spvec, forking the property tree
* whenever we see a "parent gap" due to deletions from scope.
* NB: sprop is null on first entry to the loop body.
*/
do {
if (spvec[i]->parent == sprop) {
sprop = spvec[i];
} else {
sprop = GetPropertyTreeChild(cx, sprop, spvec[i]);
if (!sprop) {
JS_free(cx, spvec);
goto fail_overwrite;
}
spp2 = js_SearchScope(scope, sprop->id, JS_FALSE);
JS_ASSERT(SPROP_FETCH(spp2) == spvec[i]);
SPROP_STORE_PRESERVING_COLLISION(spp2, sprop);
}
} while (++i < splen);
JS_free(cx, spvec);
/*
* Now sprop points to the last property in scope, where the
* ancestor line from sprop to the root is dense w.r.t. scope:
* it contains no nodes not mapped by scope->table, apart from
* any stinking ECMA-mandated duplicate formal parameters.
*/
scope->lastProp = sprop;
CHECK_ANCESTOR_LINE(scope, JS_FALSE);
JS_RUNTIME_METER(cx->runtime, middleDeleteFixups);
}
SCOPE_CLR_MIDDLE_DELETE(scope);
}
/*
* Aliases share another property's slot, passed in the |slot| param.
* Shared properties have no slot. Unshared properties that do not
* alias another property's slot get one here, but may lose it due to
* a JS_ClearScope call.
*/
if (!(flags & SPROP_IS_ALIAS)) {
if (attrs & JSPROP_SHARED) {
slot = SPROP_INVALID_SLOT;
} else {
/*
* We may have set slot from a nearly-matching sprop, above.
* If so, we're overwriting that nearly-matching sprop, so we
* can reuse its slot -- we don't need to allocate a new one.
* Callers should therefore pass SPROP_INVALID_SLOT for all
* non-alias, unshared property adds.
*/
if (slot != SPROP_INVALID_SLOT)
JS_ASSERT(overwriting);
else if (!js_AllocSlot(cx, scope->object, &slot))
goto fail_overwrite;
}
}
/*
* Check for a watchpoint on a deleted property; if one exists, change
* setter to js_watch_set.
* XXXbe this could get expensive with lots of watchpoints...
*/
if (!JS_CLIST_IS_EMPTY(&cx->runtime->watchPointList) &&
js_FindWatchPoint(cx->runtime, scope, id)) {
setter = js_WrapWatchedSetter(cx, id, attrs, setter);
if (!setter)
goto fail_overwrite;
}
/* Find or create a property tree node labeled by our arguments. */
child.id = id;
child.getter = getter;
child.setter = setter;
child.slot = slot;
child.attrs = attrs;
child.flags = flags;
child.shortid = shortid;
sprop = GetPropertyTreeChild(cx, scope->lastProp, &child);
if (!sprop)
goto fail_overwrite;
/* Store the tree node pointer in the table entry for id. */
if (scope->table)
SPROP_STORE_PRESERVING_COLLISION(spp, sprop);
scope->entryCount++;
scope->lastProp = sprop;
CHECK_ANCESTOR_LINE(scope, JS_FALSE);
if (!overwriting) {
JS_RUNTIME_METER(cx->runtime, liveScopeProps);
JS_RUNTIME_METER(cx->runtime, totalScopeProps);
}
/*
* If we reach the hashing threshold, try to allocate scope->table.
* If we can't (a rare event, preceded by swapping to death on most
* modern OSes), stick with linear search rather than whining about
* this little set-back. Therefore we must test !scope->table and
* scope->entryCount >= SCOPE_HASH_THRESHOLD, not merely whether the
* entry count just reached the threshold.
*/
if (!scope->table && scope->entryCount >= SCOPE_HASH_THRESHOLD)
(void) CreateScopeTable(scope);
}
METER(adds);
return sprop;
fail_overwrite:
if (overwriting) {
/*
* We may or may not have forked overwriting out of scope's ancestor
* line, so we must check (the alternative is to set a flag above, but
* that hurts the common, non-error case). If we did fork overwriting
* out, we'll add it back at scope->lastProp. This means enumeration
* order can change due to a failure to overwrite an id.
* XXXbe very minor incompatibility
*/
for (sprop = SCOPE_LAST_PROP(scope); ; sprop = sprop->parent) {
if (!sprop) {
sprop = SCOPE_LAST_PROP(scope);
if (overwriting->parent == sprop) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -