mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 00:01:50 +00:00
519 lines
14 KiB
C
519 lines
14 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public License
|
|
* Version 1.0 (the "NPL"); you may not use this file except in
|
|
* compliance with the NPL. You may obtain a copy of the NPL at
|
|
* http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
|
* for the specific language governing rights and limitations under the
|
|
* NPL.
|
|
*
|
|
* The Initial Developer of this code under the NPL is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
|
* Reserved.
|
|
*/
|
|
|
|
/*
|
|
* JS symbol tables.
|
|
*/
|
|
#include "jsstddef.h"
|
|
#include <string.h>
|
|
#include "prtypes.h"
|
|
#include "prassert.h"
|
|
#include "jsapi.h"
|
|
#include "jsatom.h"
|
|
#include "jscntxt.h"
|
|
#include "jsinterp.h"
|
|
#include "jslock.h"
|
|
#include "jsnum.h"
|
|
#include "jsscope.h"
|
|
#include "jsstr.h"
|
|
|
|
PR_STATIC_CALLBACK(prhashcode)
|
|
js_hash_id(const void *key)
|
|
{
|
|
jsval v;
|
|
const JSAtom *atom;
|
|
|
|
v = (jsval)key;
|
|
if (JSVAL_IS_INT(v))
|
|
return JSVAL_TO_INT(v);
|
|
atom = key;
|
|
return atom->number;
|
|
}
|
|
|
|
typedef struct JSScopePrivate {
|
|
JSContext *context;
|
|
JSScope *scope;
|
|
} JSScopePrivate;
|
|
|
|
PR_STATIC_CALLBACK(void *)
|
|
js_alloc_scope_space(void *priv, size_t size)
|
|
{
|
|
return JS_malloc(((JSScopePrivate *)priv)->context, size);
|
|
}
|
|
|
|
PR_STATIC_CALLBACK(void)
|
|
js_free_scope_space(void *priv, void *item)
|
|
{
|
|
JS_free(((JSScopePrivate *)priv)->context, item);
|
|
}
|
|
|
|
PR_STATIC_CALLBACK(PRHashEntry *)
|
|
js_alloc_symbol(void *priv, const void *key)
|
|
{
|
|
JSScopePrivate *spriv;
|
|
JSContext *cx;
|
|
JSSymbol *sym;
|
|
|
|
spriv = priv;
|
|
PR_ASSERT(JS_IS_SCOPE_LOCKED(spriv->scope));
|
|
cx = spriv->context;
|
|
sym = JS_malloc(cx, sizeof(JSSymbol));
|
|
if (!sym)
|
|
return NULL;
|
|
sym->entry.key = key;
|
|
return &sym->entry;
|
|
}
|
|
|
|
PR_STATIC_CALLBACK(void)
|
|
js_free_symbol(void *priv, PRHashEntry *he, uintN flag)
|
|
{
|
|
JSScopePrivate *spriv;
|
|
JSContext *cx;
|
|
JSSymbol *sym, **sp;
|
|
JSScopeProperty *sprop;
|
|
|
|
spriv = priv;
|
|
PR_ASSERT(JS_IS_SCOPE_LOCKED(spriv->scope));
|
|
cx = spriv->context;
|
|
sym = (JSSymbol *)he;
|
|
sprop = sym->entry.value;
|
|
if (sprop) {
|
|
sym->entry.value = NULL;
|
|
sprop = js_DropScopeProperty(cx, spriv->scope, sprop);
|
|
if (sprop) {
|
|
for (sp = &sprop->symbols; *sp; sp = &(*sp)->next) {
|
|
if (*sp == sym) {
|
|
*sp = sym->next;
|
|
if (!*sp)
|
|
break;
|
|
}
|
|
}
|
|
sym->next = NULL;
|
|
}
|
|
}
|
|
|
|
if (flag == HT_FREE_ENTRY)
|
|
JS_free(cx, he);
|
|
}
|
|
|
|
static PRHashAllocOps hash_scope_alloc_ops = {
|
|
js_alloc_scope_space, js_free_scope_space,
|
|
js_alloc_symbol, js_free_symbol
|
|
};
|
|
|
|
/************************************************************************/
|
|
|
|
PR_STATIC_CALLBACK(JSSymbol *)
|
|
js_hash_scope_lookup(JSContext *cx, JSScope *scope, jsid id, prhashcode hash)
|
|
{
|
|
PRHashTable *table = scope->data;
|
|
PRHashEntry **hep;
|
|
JSSymbol *sym;
|
|
|
|
hep = PR_HashTableRawLookup(table, hash, (const void *)id);
|
|
sym = (JSSymbol *) *hep;
|
|
return sym;
|
|
}
|
|
|
|
#define SCOPE_ADD(PRIV, CLASS_SPECIFIC_CODE) \
|
|
PR_BEGIN_MACRO \
|
|
if (sym) { \
|
|
if (sym->entry.value == sprop) \
|
|
return sym; \
|
|
if (sym->entry.value) \
|
|
js_free_symbol(PRIV, &sym->entry, HT_FREE_VALUE); \
|
|
} else { \
|
|
CLASS_SPECIFIC_CODE \
|
|
sym->scope = scope; \
|
|
sym->next = NULL; \
|
|
} \
|
|
if (sprop) { \
|
|
sym->entry.value = js_HoldScopeProperty(cx, scope, sprop); \
|
|
for (sp = &sprop->symbols; *sp; sp = &(*sp)->next) \
|
|
; \
|
|
*sp = sym; \
|
|
} else { \
|
|
sym->entry.value = NULL; \
|
|
} \
|
|
PR_END_MACRO
|
|
|
|
PR_STATIC_CALLBACK(JSSymbol *)
|
|
js_hash_scope_add(JSContext *cx, JSScope *scope, jsid id, JSScopeProperty *sprop)
|
|
{
|
|
PRHashTable *table = scope->data;
|
|
const void *key;
|
|
prhashcode keyHash;
|
|
PRHashEntry **hep;
|
|
JSSymbol *sym, **sp;
|
|
JSScopePrivate *priv;
|
|
|
|
PR_ASSERT(JS_IS_SCOPE_LOCKED(scope));
|
|
priv = table->allocPriv;
|
|
priv->context = cx;
|
|
key = (const void *)id;
|
|
keyHash = js_hash_id(key);
|
|
hep = PR_HashTableRawLookup(table, keyHash, key);
|
|
sym = (JSSymbol *) *hep;
|
|
SCOPE_ADD(priv,
|
|
sym = (JSSymbol *) PR_HashTableRawAdd(table, hep, keyHash, key, NULL);
|
|
if (!sym)
|
|
return NULL;
|
|
);
|
|
return sym;
|
|
}
|
|
|
|
PR_STATIC_CALLBACK(JSBool)
|
|
js_hash_scope_remove(JSContext *cx, JSScope *scope, jsid id)
|
|
{
|
|
PRHashTable *table = scope->data;
|
|
JSScopePrivate *priv;
|
|
|
|
PR_ASSERT(JS_IS_SCOPE_LOCKED(scope));
|
|
priv = table->allocPriv;
|
|
priv->context = cx;
|
|
return PR_HashTableRemove(table, (const void *)id);
|
|
}
|
|
|
|
/* Forward declaration for use by js_hash_scope_clear(). */
|
|
extern JS_FRIEND_DATA(JSScopeOps) js_list_scope_ops;
|
|
|
|
PR_STATIC_CALLBACK(void)
|
|
js_hash_scope_clear(JSContext *cx, JSScope *scope)
|
|
{
|
|
PRHashTable *table = scope->data;
|
|
JSScopePrivate *priv;
|
|
|
|
PR_ASSERT(JS_IS_SCOPE_LOCKED(scope));
|
|
priv = table->allocPriv;
|
|
priv->context = cx;
|
|
PR_HashTableDestroy(table);
|
|
JS_free(cx, priv);
|
|
scope->ops = &js_list_scope_ops;
|
|
scope->data = NULL;
|
|
}
|
|
|
|
JSScopeOps js_hash_scope_ops = {
|
|
js_hash_scope_lookup,
|
|
js_hash_scope_add,
|
|
js_hash_scope_remove,
|
|
js_hash_scope_clear
|
|
};
|
|
|
|
/************************************************************************/
|
|
|
|
PR_STATIC_CALLBACK(JSSymbol *)
|
|
js_list_scope_lookup(JSContext *cx, JSScope *scope, jsid id, prhashcode hash)
|
|
{
|
|
JSSymbol *sym, **sp;
|
|
|
|
PR_ASSERT(JS_IS_SCOPE_LOCKED(scope));
|
|
for (sp = (JSSymbol **)&scope->data; (sym = *sp) != 0;
|
|
sp = (JSSymbol **)&sym->entry.next) {
|
|
if (sym_id(sym) == id) {
|
|
/* Move sym to the front for shorter searches. */
|
|
*sp = (JSSymbol *)sym->entry.next;
|
|
sym->entry.next = scope->data;
|
|
scope->data = sym;
|
|
return sym;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#define HASH_THRESHOLD 5
|
|
|
|
PR_STATIC_CALLBACK(JSSymbol *)
|
|
js_list_scope_add(JSContext *cx, JSScope *scope, jsid id, JSScopeProperty *sprop)
|
|
{
|
|
JSSymbol *list = scope->data;
|
|
uint32 nsyms;
|
|
JSSymbol *sym, *next, **sp;
|
|
PRHashTable *table;
|
|
PRHashEntry **hep;
|
|
JSScopePrivate priv;
|
|
|
|
PR_ASSERT(JS_IS_SCOPE_LOCKED(scope));
|
|
nsyms = 0;
|
|
for (sym = list; sym; sym = (JSSymbol *)sym->entry.next) {
|
|
if (sym_id(sym) == id)
|
|
break;
|
|
nsyms++;
|
|
}
|
|
|
|
if (nsyms >= HASH_THRESHOLD) {
|
|
JSScopePrivate *priv = JS_malloc(cx, sizeof(JSScopePrivate));
|
|
if (!priv) return NULL;
|
|
priv->context = cx;
|
|
priv->scope = scope;
|
|
table = PR_NewHashTable(nsyms, js_hash_id,
|
|
PR_CompareValues, PR_CompareValues,
|
|
&hash_scope_alloc_ops, priv);
|
|
if (table) {
|
|
for (sym = list; sym; sym = next) {
|
|
/* Save next for loop update, before it changes in lookup. */
|
|
next = (JSSymbol *)sym->entry.next;
|
|
|
|
/* Now compute missing keyHash fields. */
|
|
sym->entry.keyHash = js_hash_id(sym->entry.key);
|
|
sym->entry.next = NULL;
|
|
hep = PR_HashTableRawLookup(table,
|
|
sym->entry.keyHash,
|
|
sym->entry.key);
|
|
*hep = &sym->entry;
|
|
}
|
|
table->nentries = nsyms;
|
|
scope->ops = &js_hash_scope_ops;
|
|
scope->data = table;
|
|
return scope->ops->add(cx, scope, id, sprop);
|
|
}
|
|
}
|
|
|
|
priv.context = cx;
|
|
priv.scope = scope;
|
|
SCOPE_ADD(&priv,
|
|
sym = (JSSymbol *)js_alloc_symbol(&priv, (const void *)id);
|
|
if (!sym)
|
|
return NULL;
|
|
/* Don't set keyHash until we know we need it, above. */
|
|
sym->entry.next = scope->data;
|
|
scope->data = sym;
|
|
);
|
|
return sym;
|
|
}
|
|
|
|
PR_STATIC_CALLBACK(JSBool)
|
|
js_list_scope_remove(JSContext *cx, JSScope *scope, jsid id)
|
|
{
|
|
JSSymbol *sym, **sp;
|
|
JSScopePrivate priv;
|
|
|
|
PR_ASSERT(JS_IS_SCOPE_LOCKED(scope));
|
|
for (sp = (JSSymbol **)&scope->data; (sym = *sp) != 0;
|
|
sp = (JSSymbol **)&sym->entry.next) {
|
|
if (sym_id(sym) == id) {
|
|
*sp = (JSSymbol *)sym->entry.next;
|
|
priv.context = cx;
|
|
priv.scope = scope;
|
|
js_free_symbol(&priv, &sym->entry, HT_FREE_ENTRY);
|
|
return JS_TRUE;
|
|
}
|
|
}
|
|
return JS_FALSE;
|
|
}
|
|
|
|
PR_STATIC_CALLBACK(void)
|
|
js_list_scope_clear(JSContext *cx, JSScope *scope)
|
|
{
|
|
JSSymbol *sym;
|
|
JSScopePrivate priv;
|
|
|
|
PR_ASSERT(JS_IS_SCOPE_LOCKED(scope));
|
|
while ((sym = scope->data) != NULL) {
|
|
scope->data = sym->entry.next;
|
|
priv.context = cx;
|
|
priv.scope = scope;
|
|
js_free_symbol(&priv, &sym->entry, HT_FREE_ENTRY);
|
|
}
|
|
}
|
|
|
|
JSScopeOps JS_FRIEND_DATA(js_list_scope_ops) = {
|
|
js_list_scope_lookup,
|
|
js_list_scope_add,
|
|
js_list_scope_remove,
|
|
js_list_scope_clear
|
|
};
|
|
|
|
/************************************************************************/
|
|
|
|
JSScope *
|
|
js_GetMutableScope(JSContext *cx, JSObject *obj)
|
|
{
|
|
JSScope *scope, *newscope;
|
|
|
|
scope = (JSScope *) obj->map;
|
|
PR_ASSERT(JS_IS_SCOPE_LOCKED(scope));
|
|
if (scope->object == obj)
|
|
return scope;
|
|
newscope = js_NewScope(cx, 0, scope->map.ops, LOCKED_OBJ_GET_CLASS(obj),
|
|
obj);
|
|
if (!newscope)
|
|
return NULL;
|
|
JS_LOCK_SCOPE(cx, newscope);
|
|
obj->map = js_HoldObjectMap(cx, &newscope->map);
|
|
scope = (JSScope *) js_DropObjectMap(cx, &scope->map, obj);
|
|
JS_TRANSFER_SCOPE_LOCK(cx, scope, newscope);
|
|
return newscope;
|
|
}
|
|
|
|
JSScope *
|
|
js_MutateScope(JSContext *cx, JSObject *obj, jsid id,
|
|
JSPropertyOp getter, JSPropertyOp setter, uintN attrs,
|
|
JSScopeProperty **propp)
|
|
{
|
|
/* XXX pessimal */
|
|
*propp = NULL;
|
|
return js_GetMutableScope(cx, obj);
|
|
}
|
|
|
|
JSScope *
|
|
js_NewScope(JSContext *cx, jsrefcount nrefs, JSObjectOps *ops, JSClass *clasp,
|
|
JSObject *obj)
|
|
{
|
|
JSScope *scope;
|
|
|
|
scope = JS_malloc(cx, sizeof(JSScope));
|
|
if (!scope)
|
|
return NULL;
|
|
js_InitObjectMap(&scope->map, nrefs, ops, clasp);
|
|
scope->object = obj;
|
|
scope->props = NULL;
|
|
scope->proptail = &scope->props;
|
|
scope->ops = &js_list_scope_ops;
|
|
scope->data = NULL;
|
|
|
|
#ifdef JS_THREADSAFE
|
|
js_NewLock(&scope->lock);
|
|
scope->count = 0;
|
|
#ifdef DEBUG
|
|
scope->file[0] = scope->file[1] = scope->file[2] = scope->file[3] = NULL;
|
|
scope->line[0] = scope->line[1] = scope->line[2] = scope->line[3] = 0;
|
|
#endif
|
|
#endif
|
|
|
|
return scope;
|
|
}
|
|
|
|
void
|
|
js_DestroyScope(JSContext *cx, JSScope *scope)
|
|
{
|
|
JS_LOCK_SCOPE(cx, scope);
|
|
scope->ops->clear(cx, scope);
|
|
JS_UNLOCK_SCOPE(cx, scope);
|
|
#ifdef JS_THREADSAFE
|
|
PR_ASSERT(scope->count == 0);
|
|
js_DestroyLock(&scope->lock);
|
|
#endif
|
|
JS_free(cx, scope);
|
|
}
|
|
|
|
prhashcode
|
|
js_HashValue(jsval v)
|
|
{
|
|
return js_hash_id((const void *)v);
|
|
}
|
|
|
|
jsval
|
|
js_IdToValue(jsid id)
|
|
{
|
|
JSAtom *atom;
|
|
|
|
if (JSVAL_IS_INT(id))
|
|
return id;
|
|
atom = (JSAtom *)id;
|
|
return ATOM_KEY(atom);
|
|
}
|
|
|
|
JSScopeProperty *
|
|
js_NewScopeProperty(JSContext *cx, JSScope *scope, jsid id,
|
|
JSPropertyOp getter, JSPropertyOp setter, uintN attrs)
|
|
{
|
|
uint32 slot;
|
|
JSScopeProperty *sprop;
|
|
|
|
PR_ASSERT(JS_IS_SCOPE_LOCKED(scope));
|
|
if (!js_AllocSlot(cx, scope->object, &slot))
|
|
return NULL;
|
|
sprop = JS_malloc(cx, sizeof(JSScopeProperty));
|
|
if (!sprop) {
|
|
js_FreeSlot(cx, scope->object, slot);
|
|
return NULL;
|
|
}
|
|
sprop->nrefs = 0;
|
|
sprop->id = js_IdToValue(id);
|
|
sprop->getter = getter;
|
|
sprop->setter = setter;
|
|
sprop->slot = slot;
|
|
sprop->attrs = attrs;
|
|
sprop->spare = 0;
|
|
sprop->symbols = NULL;
|
|
sprop->next = NULL;
|
|
sprop->prevp = scope->proptail;
|
|
*scope->proptail = sprop;
|
|
scope->proptail = &sprop->next;
|
|
return sprop;
|
|
}
|
|
|
|
void
|
|
js_DestroyScopeProperty(JSContext *cx, JSScope *scope, JSScopeProperty *sprop)
|
|
{
|
|
/*
|
|
* Test whether obj was finalized before prop's last dereference.
|
|
*
|
|
* This can happen when a deleted property is held by a property iterator
|
|
* object (which points to obj, keeping obj alive so long as the property
|
|
* iterator is reachable). As soon as the GC finds the iterator to be
|
|
* unreachable, it will finalize the iterator, and may also finalize obj,
|
|
* in an unpredictable order. If obj is finalized first, its map will be
|
|
* null below, and we need only free prop.
|
|
*
|
|
* In the more typical case of a property whose last reference (from a
|
|
* symbol in obj's scope) goes away before obj is finalized, we must be
|
|
* sure to free prop's slot and unlink it from obj's property list.
|
|
*/
|
|
if (scope) {
|
|
PR_ASSERT(JS_IS_SCOPE_LOCKED(scope));
|
|
if (scope->object) {
|
|
js_FreeSlot(cx, scope->object, sprop->slot);
|
|
*sprop->prevp = sprop->next;
|
|
if (sprop->next)
|
|
sprop->next->prevp = sprop->prevp;
|
|
else
|
|
scope->proptail = sprop->prevp;
|
|
}
|
|
}
|
|
|
|
/* Purge any cached weak links to prop, then free it. */
|
|
js_FlushPropertyCacheByProp(cx, (JSProperty *)sprop);
|
|
JS_free(cx, sprop);
|
|
}
|
|
|
|
JSScopeProperty *
|
|
js_HoldScopeProperty(JSContext *cx, JSScope *scope, JSScopeProperty *sprop)
|
|
{
|
|
PR_ASSERT(JS_IS_SCOPE_LOCKED(scope));
|
|
if (sprop) {
|
|
PR_ASSERT(sprop->nrefs >= 0);
|
|
sprop->nrefs++;
|
|
}
|
|
return sprop;
|
|
}
|
|
|
|
JSScopeProperty *
|
|
js_DropScopeProperty(JSContext *cx, JSScope *scope, JSScopeProperty *sprop)
|
|
{
|
|
PR_ASSERT(JS_IS_SCOPE_LOCKED(scope));
|
|
if (sprop) {
|
|
PR_ASSERT(sprop->nrefs > 0);
|
|
if (--sprop->nrefs == 0) {
|
|
js_DestroyScopeProperty(cx, scope, sprop);
|
|
sprop = NULL;
|
|
}
|
|
}
|
|
return sprop;
|
|
}
|