Backed out changeset 510c42c0d472 - bug 633219

This commit is contained in:
Igor Bukanov 2011-02-17 19:39:05 +01:00
parent 604a770f16
commit 47a2ecace5
12 changed files with 468 additions and 490 deletions

View File

@ -1,63 +0,0 @@
// Test that the watch handler is not called recursively for the same object
// and property.
(function() {
var obj1 = {}, obj2 = {};
var handler_entry_count = 0;
var handler_exit_count = 0;
obj1.watch('x', handler);
obj1.watch('y', handler);
obj2.watch('x', handler);
obj1.x = 1;
assertEq(handler_entry_count, 3);
assertEq(handler_exit_count, 3);
function handler(id) {
handler_entry_count++;
assertEq(handler_exit_count, 0);
switch (true) {
case this === obj1 && id === "x":
assertEq(handler_entry_count, 1);
obj2.x = 3;
assertEq(handler_exit_count, 2);
break;
case this === obj2 && id === "x":
assertEq(handler_entry_count, 2);
obj1.y = 4;
assertEq(handler_exit_count, 1);
break;
default:
assertEq(this, obj1);
assertEq(id, "y");
assertEq(handler_entry_count, 3);
// We expect no more watch handler invocations
obj1.x = 5;
obj1.y = 6;
obj2.x = 7;
assertEq(handler_exit_count, 0);
break;
}
++handler_exit_count;
assertEq(handler_entry_count, 3);
}
})();
// Test that run-away recursion in watch handlers is properly handled.
(function() {
var obj = {};
var i = 0;
try {
handler();
throw new Error("Unreachable");
} catch(e) {
assertEq(e instanceof InternalError, true);
}
function handler() {
var prop = "a" + ++i;
obj.watch(prop, handler);
obj[prop] = 2;
}
})();

View File

@ -67,7 +67,6 @@ CPPSRCS = \
testNewObject.cpp \
testOps.cpp \
testPropCache.cpp \
testResolveRecursion.cpp \
testSameValue.cpp \
testScriptObject.cpp \
testSetProperty.cpp \

View File

@ -1,134 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sw=4 et tw=99:
*/
#include "tests.h"
/*
* Test that resolve hook recursion for the same object and property is
* prevented.
*/
BEGIN_TEST(testResolveRecursion)
{
static JSClass my_resolve_class = {
"MyResolve",
JSCLASS_NEW_RESOLVE | JSCLASS_HAS_PRIVATE,
JS_PropertyStub, // add
JS_PropertyStub, // delete
JS_PropertyStub, // get
JS_StrictPropertyStub, // set
JS_EnumerateStub,
(JSResolveOp) my_resolve,
JS_ConvertStub,
JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS
};
obj1 = JS_NewObject(cx, &my_resolve_class, NULL, NULL);
CHECK(obj1);
obj2 = JS_NewObject(cx, &my_resolve_class, NULL, NULL);
CHECK(obj2);
CHECK(JS_SetPrivate(cx, obj1, this));
CHECK(JS_SetPrivate(cx, obj2, this));
CHECK(JS_DefineProperty(cx, global, "obj1", OBJECT_TO_JSVAL(obj1), NULL, NULL, 0));
CHECK(JS_DefineProperty(cx, global, "obj2", OBJECT_TO_JSVAL(obj2), NULL, NULL, 0));
resolveEntryCount = 0;
resolveExitCount = 0;
/* Start the essence of the test via invoking the first resolve hook. */
jsval v;
EVAL("obj1.x", &v);
CHECK(v == JSVAL_FALSE);
CHECK(resolveEntryCount == 4);
CHECK(resolveExitCount == 4);
return true;
}
JSObject *obj1;
JSObject *obj2;
unsigned resolveEntryCount;
unsigned resolveExitCount;
struct AutoIncrCounters {
AutoIncrCounters(cls_testResolveRecursion *t) : t(t) {
t->resolveEntryCount++;
}
~AutoIncrCounters() {
t->resolveExitCount++;
}
cls_testResolveRecursion *t;
};
bool
doResolve(JSObject *obj, jsid id, uintN flags, JSObject **objp)
{
CHECK(resolveExitCount == 0);
AutoIncrCounters incr(this);
CHECK(obj == obj1 || obj == obj2);
CHECK(JSID_IS_STRING(id));
JSFlatString *str = JS_FlattenString(cx, JSID_TO_STRING(id));
CHECK(str);
jsval v;
if (JS_FlatStringEqualsAscii(str, "x")) {
if (obj == obj1) {
/* First resolve hook invocation. */
CHECK(resolveEntryCount == 1);
EVAL("obj2.y = true", &v);
CHECK(v == JSVAL_TRUE);
CHECK(JS_DefinePropertyById(cx, obj, id, JSVAL_FALSE, NULL, NULL, 0));
*objp = obj;
return true;
}
if (obj == obj2) {
CHECK(resolveEntryCount == 4);
*objp = NULL;
return true;
}
} else if (JS_FlatStringEqualsAscii(str, "y")) {
if (obj == obj2) {
CHECK(resolveEntryCount == 2);
CHECK(JS_DefinePropertyById(cx, obj, id, JSVAL_NULL, NULL, NULL, 0));
EVAL("obj1.x", &v);
CHECK(JSVAL_IS_VOID(v));
EVAL("obj1.y", &v);
CHECK(v == JSVAL_ZERO);
*objp = obj;
return true;
}
if (obj == obj1) {
CHECK(resolveEntryCount == 3);
EVAL("obj1.x", &v);
CHECK(JSVAL_IS_VOID(v));
EVAL("obj1.y", &v);
CHECK(JSVAL_IS_VOID(v));
EVAL("obj2.y", &v);
CHECK(JSVAL_IS_NULL(v));
EVAL("obj2.x", &v);
CHECK(JSVAL_IS_VOID(v));
EVAL("obj1.y = 0", &v);
CHECK(v == JSVAL_ZERO);
*objp = obj;
return true;
}
}
CHECK(false);
return false;
}
static JSBool
my_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags, JSObject **objp)
{
return static_cast<cls_testResolveRecursion *>(JS_GetPrivate(cx, obj))->
doResolve(obj, id, flags, objp);
}
END_TEST(testResolveRecursion)

View File

@ -1385,6 +1385,37 @@ JS_SetGlobalObject(JSContext *cx, JSObject *obj)
cx->resetCompartment();
}
class AutoResolvingEntry {
public:
AutoResolvingEntry() : entry(NULL) {}
/*
* Returns false on error. But N.B. if obj[id] was already being resolved,
* this is a no-op, and we silently treat that as success.
*/
bool start(JSContext *cx, JSObject *obj, jsid id, uint32 flag) {
JS_ASSERT(!entry);
this->cx = cx;
key.obj = obj;
key.id = id;
this->flag = flag;
bool ok = !!js_StartResolving(cx, &key, flag, &entry);
JS_ASSERT_IF(!ok, !entry);
return ok;
}
~AutoResolvingEntry() {
if (entry)
js_StopResolving(cx, &key, flag, NULL, 0);
}
private:
JSContext *cx;
JSResolvingKey key;
uint32 flag;
JSResolvingEntry *entry;
};
JSObject *
js_InitFunctionAndObjectClasses(JSContext *cx, JSObject *obj)
{
@ -1395,11 +1426,14 @@ js_InitFunctionAndObjectClasses(JSContext *cx, JSObject *obj)
if (!cx->globalObject)
JS_SetGlobalObject(cx, obj);
/* Record Function and Object in the resolving set. */
/* Record Function and Object in cx->resolvingTable. */
AutoResolvingEntry e1, e2;
JSAtom **classAtoms = cx->runtime->atomState.classAtoms;
AutoResolving resolving1(cx, obj, ATOM_TO_JSID(classAtoms[JSProto_Function]));
AutoResolving resolving2(cx, obj, ATOM_TO_JSID(classAtoms[JSProto_Object]));
if (!e1.start(cx, obj, ATOM_TO_JSID(classAtoms[JSProto_Function]), JSRESFLAG_LOOKUP) ||
!e2.start(cx, obj, ATOM_TO_JSID(classAtoms[JSProto_Object]), JSRESFLAG_LOOKUP)) {
return NULL;
}
/* Initialize the function class first so constructors can be made. */
if (!js_GetClassPrototype(cx, obj, JSProto_Function, &fun_proto))
return NULL;

View File

@ -112,6 +112,9 @@ using namespace js::gc;
static const size_t ARENA_HEADER_SIZE_HACK = 40;
static const size_t TEMP_POOL_CHUNK_SIZE = 4096 - ARENA_HEADER_SIZE_HACK;
static void
FreeContext(JSContext *cx);
#ifdef DEBUG
JS_REQUIRES_STACK bool
StackSegment::contains(const JSStackFrame *fp) const
@ -167,10 +170,9 @@ StackSpace::init()
return true;
}
StackSpace::~StackSpace()
void
StackSpace::finish()
{
if (!base)
return;
#ifdef XP_WIN
VirtualFree(base, (commitEnd - base) * sizeof(Value), MEM_DECOMMIT);
VirtualFree(base, 0, MEM_RELEASE);
@ -495,17 +497,88 @@ AllFramesIter::operator++()
bool
JSThreadData::init()
{
#ifdef DEBUG
/* The data must be already zeroed. */
for (size_t i = 0; i != sizeof(*this); ++i)
JS_ASSERT(reinterpret_cast<uint8*>(this)[i] == 0);
#endif
if (!stackSpace.init())
return false;
dtoaState = js_NewDtoaState();
if (!dtoaState) {
finish();
return false;
}
nativeStackBase = GetNativeStackBase();
#ifdef JS_TRACER
/* Set the default size for the code cache to 16MB. */
maxCodeCacheBytes = 16 * 1024 * 1024;
#endif
return stackSpace.init() && !!(dtoaState = js_NewDtoaState());
return true;
}
void
JSThreadData::finish()
{
if (dtoaState)
js_DestroyDtoaState(dtoaState);
js_FinishGSNCache(&gsnCache);
propertyCache.~PropertyCache();
stackSpace.finish();
}
void
JSThreadData::mark(JSTracer *trc)
{
stackSpace.mark(trc);
}
void
JSThreadData::purge(JSContext *cx)
{
js_PurgeGSNCache(&gsnCache);
/* FIXME: bug 506341. */
propertyCache.purge(cx);
}
#ifdef JS_THREADSAFE
static JSThread *
NewThread(void *id)
{
JS_ASSERT(js_CurrentThreadId() == id);
JSThread *thread = (JSThread *) js_calloc(sizeof(JSThread));
if (!thread)
return NULL;
JS_INIT_CLIST(&thread->contextList);
thread->id = id;
if (!thread->data.init()) {
js_free(thread);
return NULL;
}
return thread;
}
static void
DestroyThread(JSThread *thread)
{
/* The thread must have zero contexts. */
JS_ASSERT(JS_CLIST_IS_EMPTY(&thread->contextList));
/*
* The conservative GC scanner should be disabled when the thread leaves
* the last request.
*/
JS_ASSERT(!thread->data.conservativeGC.hasStackToScan());
thread->data.finish();
js_free(thread);
}
JSThread *
js_CurrentThread(JSRuntime *rt)
{
@ -531,21 +604,14 @@ js_CurrentThread(JSRuntime *rt)
thread->data.nativeStackBase = GetNativeStackBase();
} else {
JS_UNLOCK_GC(rt);
void *threadMemory = js_calloc(sizeof(JSThread));
if (!threadMemory)
thread = NewThread(id);
if (!thread)
return NULL;
thread = new (threadMemory) JSThread(id);
if (!thread->init()) {
js_delete(thread);
return NULL;
}
JS_LOCK_GC(rt);
js_WaitForGC(rt);
if (!rt->threads.relookupOrAdd(p, id, thread)) {
JS_UNLOCK_GC(rt);
js_delete(thread);
DestroyThread(thread);
return NULL;
}
@ -623,7 +689,8 @@ js_FinishThreads(JSRuntime *rt)
return;
for (JSThread::Map::Range r = rt->threads.all(); !r.empty(); r.popFront()) {
JSThread *thread = r.front().value;
js_delete(thread);
JS_ASSERT(JS_CLIST_IS_EMPTY(&thread->contextList));
DestroyThread(thread);
}
rt->threads.clear();
#else
@ -642,7 +709,8 @@ js_PurgeThreads(JSContext *cx)
if (JS_CLIST_IS_EMPTY(&thread->contextList)) {
JS_ASSERT(cx->thread != thread);
js_delete(thread);
DestroyThread(thread);
e.removeFront();
} else {
thread->data.purge(cx);
@ -687,13 +755,13 @@ js_NewContext(JSRuntime *rt, size_t stackChunkSize)
JS_ASSERT(cx->resolveFlags == 0);
if (!cx->busyArrays.init()) {
js_delete(cx);
FreeContext(cx);
return NULL;
}
#ifdef JS_THREADSAFE
if (!js_InitContextThread(cx)) {
js_delete(cx);
FreeContext(cx);
return NULL;
}
#endif
@ -1025,7 +1093,41 @@ js_DestroyContext(JSContext *cx, JSDestroyContextMode mode)
cx->dstOffsetCache.dumpStats();
#endif
JS_UNLOCK_GC(rt);
js_delete(cx);
FreeContext(cx);
}
static void
FreeContext(JSContext *cx)
{
#ifdef JS_THREADSAFE
JS_ASSERT(!cx->thread);
#endif
/* Free the stuff hanging off of cx. */
VOUCH_DOES_NOT_REQUIRE_STACK();
JS_FinishArenaPool(&cx->tempPool);
JS_FinishArenaPool(&cx->regExpPool);
if (cx->lastMessage)
js_free(cx->lastMessage);
/* Remove any argument formatters. */
JSArgumentFormatMap *map = cx->argumentFormatMap;
while (map) {
JSArgumentFormatMap *temp = map;
map = map->next;
cx->free(temp);
}
/* Destroy the resolve recursion damper. */
if (cx->resolvingTable) {
JS_DHashTableDestroy(cx->resolvingTable);
cx->resolvingTable = NULL;
}
/* Finally, free cx itself. */
cx->~JSContext();
js_free(cx);
}
JSContext *
@ -1056,21 +1158,107 @@ js_NextActiveContext(JSRuntime *rt, JSContext *cx)
#endif
}
namespace js {
bool
AutoResolving::isDuplicate() const
static JSDHashNumber
resolving_HashKey(JSDHashTable *table, const void *ptr)
{
JS_ASSERT(prev);
AutoResolving *cursor = prev;
do {
if (cursor->object == object && cursor->id == id && cursor->kind == kind)
return true;
} while (!!(cursor = cursor->prev));
return false;
}
const JSResolvingKey *key = (const JSResolvingKey *)ptr;
} /* namespace js */
return (JSDHashNumber(uintptr_t(key->obj)) >> JS_GCTHING_ALIGN) ^ JSID_BITS(key->id);
}
static JSBool
resolving_MatchEntry(JSDHashTable *table,
const JSDHashEntryHdr *hdr,
const void *ptr)
{
const JSResolvingEntry *entry = (const JSResolvingEntry *)hdr;
const JSResolvingKey *key = (const JSResolvingKey *)ptr;
return entry->key.obj == key->obj && entry->key.id == key->id;
}
static const JSDHashTableOps resolving_dhash_ops = {
JS_DHashAllocTable,
JS_DHashFreeTable,
resolving_HashKey,
resolving_MatchEntry,
JS_DHashMoveEntryStub,
JS_DHashClearEntryStub,
JS_DHashFinalizeStub,
NULL
};
JSBool
js_StartResolving(JSContext *cx, JSResolvingKey *key, uint32 flag,
JSResolvingEntry **entryp)
{
JSDHashTable *table;
JSResolvingEntry *entry;
table = cx->resolvingTable;
if (!table) {
table = JS_NewDHashTable(&resolving_dhash_ops, NULL,
sizeof(JSResolvingEntry),
JS_DHASH_MIN_SIZE);
if (!table)
goto outofmem;
cx->resolvingTable = table;
}
entry = (JSResolvingEntry *)
JS_DHashTableOperate(table, key, JS_DHASH_ADD);
if (!entry)
goto outofmem;
if (entry->flags & flag) {
/* An entry for (key, flag) exists already -- dampen recursion. */
entry = NULL;
} else {
/* Fill in key if we were the first to add entry, then set flag. */
if (!entry->key.obj)
entry->key = *key;
entry->flags |= flag;
}
*entryp = entry;
return JS_TRUE;
outofmem:
JS_ReportOutOfMemory(cx);
return JS_FALSE;
}
void
js_StopResolving(JSContext *cx, JSResolvingKey *key, uint32 flag,
JSResolvingEntry *entry, uint32 generation)
{
JSDHashTable *table;
/*
* Clear flag from entry->flags and return early if other flags remain.
* We must take care to re-lookup entry if the table has changed since
* it was found by js_StartResolving.
*/
table = cx->resolvingTable;
if (!entry || table->generation != generation) {
entry = (JSResolvingEntry *)
JS_DHashTableOperate(table, key, JS_DHASH_LOOKUP);
}
JS_ASSERT(JS_DHASH_ENTRY_IS_BUSY(&entry->hdr));
entry->flags &= ~flag;
if (entry->flags)
return;
/*
* Do a raw remove only if fewer entries were removed than would cause
* alpha to be less than .5 (alpha is at most .75). Otherwise, we just
* call JS_DHashTableOperate to re-lookup the key and remove its entry,
* compressing or shrinking the table as needed.
*/
if (table->removedCount < JS_DHASH_TABLE_SIZE(table) >> 2)
JS_DHashTableRawRemove(table, &entry->hdr);
else
JS_DHashTableOperate(table, key, JS_DHASH_REMOVE);
}
static void
ReportError(JSContext *cx, const char *message, JSErrorReport *reportp,
@ -1803,29 +1991,6 @@ JSContext::JSContext(JSRuntime *rt)
busyArrays()
{}
JSContext::~JSContext()
{
#ifdef JS_THREADSAFE
JS_ASSERT(!thread);
#endif
/* Free the stuff hanging off of cx. */
VOUCH_DOES_NOT_REQUIRE_STACK();
JS_FinishArenaPool(&tempPool);
JS_FinishArenaPool(&regExpPool);
if (lastMessage)
js_free(lastMessage);
/* Remove any argument formatters. */
JSArgumentFormatMap *map = argumentFormatMap;
while (map) {
JSArgumentFormatMap *temp = map;
map = map->next;
js_free(temp);
}
}
void
JSContext::resetCompartment()
{

View File

@ -653,21 +653,9 @@ class StackSpace
static const size_t STACK_QUOTA = (VALUES_PER_STACK_FRAME + 18) *
JS_MAX_INLINE_CALL_COUNT;
/* The constructor must be called over zeroed memory. */
StackSpace() {
JS_ASSERT(!base);
#ifdef XP_WIN
JS_ASSERT(!commitEnd);
#endif
JS_ASSERT(!end);
JS_ASSERT(!currentSegment);
JS_ASSERT(!invokeSegment);
JS_ASSERT(!invokeFrame);
JS_ASSERT(!invokeArgEnd);
}
/* Kept as a member of JSThreadData; cannot use constructor/destructor. */
bool init();
~StackSpace();
void finish();
#ifdef DEBUG
template <class T>
@ -853,55 +841,6 @@ struct JSPendingProxyOperation {
JSObject *object;
};
namespace js {
/*
* Class to detect recursive invocation of Class::resolve hooks and watch
* handlers.
*
* We optimize for the case of just few entries in the resolving set and use a
* linked list of AutoResolving instances with the explicit search, not a hash
* set, to check for duplicated keys. We assume that cases like recursive
* resolving hooks or watch handlers will be dealt with a native stack
* recursion checks long before O(N) complexity of adding a new entry to the
* list will affect performance.
*
* The linked list may contain duplicated entries as the user of th class may
* not use the alreadyStarted method, see js_InitFunctionAndObjectClasses. It
* allows to skip any checks in the destructor making the common case of no
* dups in the list faster.
*/
class AutoResolving {
public:
enum Kind {
LOOKUP,
WATCH
};
JS_ALWAYS_INLINE AutoResolving(JSContext *cx, JSObject *obj, jsid id, Kind kind = LOOKUP
JS_GUARD_OBJECT_NOTIFIER_PARAM);
~AutoResolving() {
*lastp = prev;
}
bool alreadyStarted() const {
return prev && isDuplicate();
}
private:
bool isDuplicate() const;
JSObject *const object;
jsid const id;
Kind const kind;
AutoResolving **const lastp;
AutoResolving *const prev;
JS_DECL_USE_GUARD_OBJECT_NOTIFIER
};
} /* namespace js */
struct JSThreadData {
#ifdef JS_THREADSAFE
/* The request depth for this thread. */
@ -963,46 +902,10 @@ struct JSThreadData {
js::ConservativeGCThreadData conservativeGC;
js::AutoResolving *resolvingList;
/* The constructor must be called over zeroed memory. */
JSThreadData() {
#ifdef JS_THREADSAFE
JS_ASSERT(!requestDepth);
#endif
#ifdef JS_TRACER
JS_ASSERT(!onTraceCompartment);
JS_ASSERT(!recordingCompartment);
JS_ASSERT(!profilingCompartment);
JS_ASSERT(!maxCodeCacheBytes);
#endif
JS_ASSERT(!interruptFlags);
JS_ASSERT(!waiveGCQuota);
JS_ASSERT(!dtoaState);
JS_ASSERT(!nativeStackBase);
JS_ASSERT(!pendingProxyOperation);
}
bool init();
~JSThreadData() {
if (dtoaState)
js_DestroyDtoaState(dtoaState);
js_FinishGSNCache(&gsnCache);
}
void mark(JSTracer *trc) {
stackSpace.mark(trc);
}
void purge(JSContext *cx) {
js_PurgeGSNCache(&gsnCache);
/* FIXME: bug 506341. */
propertyCache.purge(cx);
}
void finish();
void mark(JSTracer *trc);
void purge(JSContext *cx);
/* This must be called with the GC lock held. */
inline void triggerOperationCallback(JSRuntime *rt);
@ -1035,30 +938,6 @@ struct JSThread {
/* Factored out of JSThread for !JS_THREADSAFE embedding in JSRuntime. */
JSThreadData data;
/* The constructor must be called over zeroed memory. */
JSThread(void *id)
: id(id)
{
JS_INIT_CLIST(&contextList);
JS_ASSERT(!suspendCount);
JS_ASSERT(!checkRequestDepth);
}
bool init() {
return data.init();
}
~JSThread() {
/* The thread must have zero contexts. */
JS_ASSERT(JS_CLIST_IS_EMPTY(&contextList));
/*
* The conservative GC scanner should be disabled when the thread leaves
* the last request.
*/
JS_ASSERT(!data.conservativeGC.hasStackToScan());
}
};
#define JS_THREAD_DATA(cx) (&(cx)->thread->data)
@ -1178,7 +1057,7 @@ struct JSRuntime {
/*
* Compartment that triggered GC. If more than one Compatment need GC,
* gcTriggerCompartment is reset to NULL and a global GC is performed.
* gcTriggerCompartment is reset to NULL and a global GC is performed.
*/
JSCompartment *gcTriggerCompartment;
@ -1583,6 +1462,27 @@ struct JSArgumentFormatMap {
};
#endif
/*
* Key and entry types for the JSContext.resolvingTable hash table, typedef'd
* here because all consumers need to see these declarations (and not just the
* typedef names, as would be the case for an opaque pointer-to-typedef'd-type
* declaration), along with cx->resolvingTable.
*/
typedef struct JSResolvingKey {
JSObject *obj;
jsid id;
} JSResolvingKey;
typedef struct JSResolvingEntry {
JSDHashEntryHdr hdr;
JSResolvingKey key;
uint32 flags;
} JSResolvingEntry;
#define JSRESFLAG_LOOKUP 0x1 /* resolving id from lookup */
#define JSRESFLAG_WATCH 0x2 /* resolving id from watch */
#define JSRESOLVE_INFER 0xffff /* infer bits from current bytecode */
extern const JSDebugHooks js_NullDebugHooks; /* defined in jsdbgapi.cpp */
namespace js {
@ -1716,7 +1616,6 @@ typedef js::HashSet<JSObject *,
struct JSContext
{
explicit JSContext(JSRuntime *rt);
~JSContext();
/* JSRuntime contextList linkage. */
JSCList link;
@ -1738,6 +1637,14 @@ struct JSContext
/* Locale specific callbacks for string conversion. */
JSLocaleCallbacks *localeCallbacks;
/*
* cx->resolvingTable is non-null and non-empty if we are initializing
* standard classes lazily, or if we are otherwise recursing indirectly
* from js_LookupProperty through a Class.resolve hook. It is used to
* limit runaway recursion (see jsapi.c and jsobj.c).
*/
JSDHashTable *resolvingTable;
/*
* True if generating an error, to prevent runaway recursion.
* NB: generatingError packs with throwing below.
@ -1959,7 +1866,7 @@ struct JSContext
/*
* Return:
* - The override version, if there is an override version.
* - The newest scripted frame's version, if there is such a frame.
* - The newest scripted frame's version, if there is such a frame.
* - The default verion.
*
* Note: if this ever shows up in a profile, just add caching!
@ -3069,6 +2976,17 @@ js_ContextIterator(JSRuntime *rt, JSBool unlocked, JSContext **iterp);
extern JS_FRIEND_API(JSContext *)
js_NextActiveContext(JSRuntime *, JSContext *);
/*
* Class.resolve and watchpoint recursion damping machinery.
*/
extern JSBool
js_StartResolving(JSContext *cx, JSResolvingKey *key, uint32 flag,
JSResolvingEntry **entryp);
extern void
js_StopResolving(JSContext *cx, JSResolvingKey *key, uint32 flag,
JSResolvingEntry *entry, uint32 generation);
/*
* Report an exception, which is currently realized as a printf-style format
* string and its arguments.

View File

@ -1,4 +1,4 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=4 sw=4 et tw=78:
*
* ***** BEGIN LICENSE BLOCK *****
@ -523,19 +523,6 @@ class AutoNamespaceArray : protected AutoGCRooter {
JSXMLArray array;
};
JS_ALWAYS_INLINE
AutoResolving::AutoResolving(JSContext *cx, JSObject *obj, jsid id, Kind kind
JS_GUARD_OBJECT_NOTIFIER_PARAM_NO_INIT)
: object(obj),
id(id),
kind(kind),
lastp(&JS_THREAD_DATA(cx)->resolvingList),
prev(*lastp)
{
JS_GUARD_OBJECT_NOTIFIER_INIT;
*lastp = this;
}
#ifdef DEBUG
class CompartmentChecker
{

View File

@ -112,18 +112,6 @@ class HashTable : AllocPolicy
Ptr(Entry &entry) : entry(&entry) {}
public:
/*
* Any method on Ptr instantiated with the default constructor should
* only be called after initializing the Ptr with assignment operator
* from another Ptr instance.
*/
Ptr() {
#ifdef DEBUG
/* Initialize to some small invalid address. */
entry = reinterpret_cast<Entry *>(0xBAD);
#endif
}
bool found() const { return entry->isLive(); }
operator ConvertibleToBool() const { return found() ? &Ptr::nonNull : 0; }
bool operator==(const Ptr &rhs) const { JS_ASSERT(found() && rhs.found()); return entry == rhs.entry; }

View File

@ -1320,35 +1320,53 @@ static JSBool
obj_watch_handler(JSContext *cx, JSObject *obj, jsid id, jsval old,
jsval *nvp, void *closure)
{
JSObject *callable = (JSObject *) closure;
JSSecurityCallbacks *callbacks = JS_GetSecurityCallbacks(cx);
JSObject *callable;
JSSecurityCallbacks *callbacks;
JSStackFrame *caller;
JSPrincipals *subject, *watcher;
JSResolvingKey key;
JSResolvingEntry *entry;
uint32 generation;
Value argv[3];
JSBool ok;
callable = (JSObject *) closure;
callbacks = JS_GetSecurityCallbacks(cx);
if (callbacks && callbacks->findObjectPrincipals) {
/* Skip over any obj_watch_* frames between us and the real subject. */
JSStackFrame *caller = js_GetScriptedCaller(cx, NULL);
caller = js_GetScriptedCaller(cx, NULL);
if (caller) {
/*
* Only call the watch handler if the watcher is allowed to watch
* the currently executing script.
*/
JSPrincipals *watcher = callbacks->findObjectPrincipals(cx, callable);
JSPrincipals *subject = js_StackFramePrincipals(cx, caller);
watcher = callbacks->findObjectPrincipals(cx, callable);
subject = js_StackFramePrincipals(cx, caller);
if (watcher && subject && !watcher->subsume(watcher, subject)) {
/* Silently don't call the watch handler. */
return true;
return JS_TRUE;
}
}
}
/* Avoid recursion on (obj, id) already being watched. */
AutoResolving resolving(cx, obj, id, AutoResolving::WATCH);
if (resolving.alreadyStarted())
return true;
Value argv[] = { IdToValue(id), Valueify(old), Valueify(*nvp) };
return ExternalInvoke(cx, ObjectValue(*obj), ObjectOrNullValue(callable),
JS_ARRAY_LENGTH(argv), argv, Valueify(nvp));
/* Avoid recursion on (obj, id) already being watched on cx. */
key.obj = obj;
key.id = id;
if (!js_StartResolving(cx, &key, JSRESFLAG_WATCH, &entry))
return JS_FALSE;
if (!entry)
return JS_TRUE;
generation = cx->resolvingTable->generation;
argv[0] = IdToValue(id);
argv[1] = Valueify(old);
argv[2] = Valueify(*nvp);
ok = ExternalInvoke(cx, ObjectValue(*obj), ObjectOrNullValue(callable), 3, argv,
Valueify(nvp));
js_StopResolving(cx, &key, JSRESFLAG_WATCH, entry, generation);
return ok;
}
static JSBool
@ -4161,36 +4179,52 @@ JSBool
js_GetClassObject(JSContext *cx, JSObject *obj, JSProtoKey key,
JSObject **objp)
{
JSObject *cobj;
JSResolvingKey rkey;
JSResolvingEntry *rentry;
uint32 generation;
JSObjectOp init;
Value v;
obj = obj->getGlobal();
if (!obj->isGlobal()) {
*objp = NULL;
return true;
return JS_TRUE;
}
Value v = obj->getReservedSlot(key);
v = obj->getReservedSlot(key);
if (v.isObject()) {
*objp = &v.toObject();
return true;
return JS_TRUE;
}
AutoResolving resolving(cx, obj, ATOM_TO_JSID(cx->runtime->atomState.classAtoms[key]));
if (resolving.alreadyStarted()) {
rkey.obj = obj;
rkey.id = ATOM_TO_JSID(cx->runtime->atomState.classAtoms[key]);
if (!js_StartResolving(cx, &rkey, JSRESFLAG_LOOKUP, &rentry))
return JS_FALSE;
if (!rentry) {
/* Already caching key in obj -- suppress recursion. */
*objp = NULL;
return true;
}
JSObject *cobj = NULL;
if (JSObjectOp init = lazy_prototype_init[key]) {
if (!init(cx, obj))
return false;
v = obj->getReservedSlot(key);
if (v.isObject())
cobj = &v.toObject();
return JS_TRUE;
}
generation = cx->resolvingTable->generation;
JSBool ok = true;
cobj = NULL;
init = lazy_prototype_init[key];
if (init) {
if (!init(cx, obj)) {
ok = JS_FALSE;
} else {
v = obj->getReservedSlot(key);
if (v.isObject())
cobj = &v.toObject();
}
}
js_StopResolving(cx, &rkey, JSRESFLAG_LOOKUP, rentry, generation);
*objp = cobj;
return true;
return ok;
}
JSBool
@ -4800,61 +4834,116 @@ js_DefineNativeProperty(JSContext *cx, JSObject *obj, jsid id, const Value &valu
JS_SCOPE_DEPTH_METERING(JS_BASIC_STATS_ACCUM(bs, val))
/*
* Call obj's resolve hook.
* Call obj's resolve hook. obj is a native object and the caller holds its
* scope lock.
*
* cx, start, id, and flags are the parameters initially passed to the ongoing
* lookup; objp and propp are its out parameters. obj is an object along
* start's prototype chain.
*
* There are four possible outcomes:
*
* - On failure, report an error or exception, unlock obj, and return false.
*
* - If we are alrady resolving a property of *curobjp, set *recursedp = true,
* unlock obj, and return true.
*
* - If the resolve hook finds or defines the sought property, set *objp and
* *propp appropriately, set *recursedp = false, and return true with *objp's
* lock held.
*
* - Otherwise no property was resolved. Set *propp = NULL and *recursedp = false
* and return true.
*/
static JSBool
CallResolveOp(JSContext *cx, JSObject *start, JSObject *obj, jsid id, uintN flags,
JSObject **objp, JSProperty **propp)
JSObject **objp, JSProperty **propp, bool *recursedp)
{
Class *clasp = obj->getClass();
JSResolveOp resolve = clasp->resolve;
JSObject *obj2;
/*
* Avoid recursion on (obj, id) already being resolved on cx.
*
* Once we have successfully added an entry for (obj, key) to
* cx->resolvingTable, control must go through cleanup: before
* returning. But note that JS_DHASH_ADD may find an existing
* entry, in which case we bail to suppress runaway recursion.
*/
JSResolvingKey key = {obj, id};
JSResolvingEntry *entry;
if (!js_StartResolving(cx, &key, JSRESFLAG_LOOKUP, &entry))
return false;
if (!entry) {
/* Already resolving id in obj -- suppress recursion. */
*recursedp = true;
return true;
}
uint32 generation = cx->resolvingTable->generation;
*recursedp = false;
*propp = NULL;
JSBool ok;
const Shape *shape = NULL;
if (clasp->flags & JSCLASS_NEW_RESOLVE) {
JSNewResolveOp newresolve = (JSNewResolveOp)resolve;
if (flags == JSRESOLVE_INFER)
flags = js_InferFlags(cx, 0);
obj2 = (clasp->flags & JSCLASS_NEW_RESOLVE_GETS_START) ? start : NULL;
JSObject *obj2 = (clasp->flags & JSCLASS_NEW_RESOLVE_GETS_START) ? start : NULL;
{
/* Protect id and all atoms from a GC nested in resolve. */
AutoKeepAtoms keep(cx->runtime);
if (!newresolve(cx, obj, id, flags, &obj2))
return false;
ok = newresolve(cx, obj, id, flags, &obj2);
}
/*
* We trust the new style resolve hook to set obj2 to NULL when
* the id cannot be resolved. But, when obj2 is not null, we do
* not assume that id must exist and lookup the property again
* for compatibility.
*/
if (!obj2) {
*propp = NULL;
return true;
}
if (!obj2->isNative()) {
/* Whoops, newresolve handed back a foreign obj2. */
JS_ASSERT(obj2 != obj);
return obj2->lookupProperty(cx, id, objp, propp);
if (!ok)
goto cleanup;
if (obj2) {
/* Resolved: lookup id again for backward compatibility. */
if (!obj2->isNative()) {
/* Whoops, newresolve handed back a foreign obj2. */
JS_ASSERT(obj2 != obj);
ok = obj2->lookupProperty(cx, id, objp, propp);
if (!ok || *propp)
goto cleanup;
} else {
/*
* Require that obj2 not be empty now, as we do for old-style
* resolve. If it doesn't, then id was not truly resolved, and
* we'll find it in the proto chain, or miss it if obj2's proto
* is not on obj's proto chain. That last case is a "too bad!"
* case.
*/
if (!obj2->nativeEmpty())
shape = obj2->nativeLookup(id);
}
if (shape) {
JS_ASSERT(!obj2->nativeEmpty());
obj = obj2;
}
}
} else {
if (!resolve(cx, obj, id))
return false;
obj2 = obj;
}
JS_ASSERT(obj2->isNative());
if (const Shape *shape = obj2->nativeLookup(id)) {
*objp = obj2;
*propp = (JSProperty *) shape;
return true;
/*
* Old resolve always requires id re-lookup if obj is not empty after
* resolve returns.
*/
ok = resolve(cx, obj, id);
if (!ok)
goto cleanup;
JS_ASSERT(obj->isNative());
if (!obj->nativeEmpty())
shape = obj->nativeLookup(id);
}
/* The id was not resolved. */
*propp = NULL;
return true;
cleanup:
if (ok && shape) {
*objp = obj;
*propp = (JSProperty *) shape;
}
js_StopResolving(cx, &key, JSRESFLAG_LOOKUP, entry, generation);
return ok;
}
static JS_ALWAYS_INLINE int
@ -4878,12 +4967,11 @@ js_LookupPropertyWithFlagsInline(JSContext *cx, JSObject *obj, jsid id, uintN fl
/* Try obj's class resolve hook if id was not found in obj's scope. */
if (!shape && obj->getClass()->resolve != JS_ResolveStub) {
/* Avoid recursion on (obj, id) already being resolved. */
AutoResolving resolving(cx, obj, id);
if (resolving.alreadyStarted())
break;
if (!CallResolveOp(cx, start, obj, id, flags, objp, propp))
bool recursed;
if (!CallResolveOp(cx, start, obj, id, flags, objp, propp, &recursed))
return -1;
if (recursed)
break;
if (*propp) {
/* Recalculate protoIndex in case it was resolved on some other object. */
protoIndex = 0;

View File

@ -1660,8 +1660,6 @@ extern int
js_LookupPropertyWithFlags(JSContext *cx, JSObject *obj, jsid id, uintN flags,
JSObject **objp, JSProperty **propp);
/* Infer resolve flags from the current bytecode. */
#define JSRESOLVE_INFER 0xffff
/*
* We cache name lookup results only for the global object or for native

View File

@ -401,8 +401,6 @@ public:
, const JSGuardObjectNotifier& _notifier = JSGuardObjectNotifier()
#define JS_GUARD_OBJECT_NOTIFIER_PARAM0 \
const JSGuardObjectNotifier& _notifier = JSGuardObjectNotifier()
#define JS_GUARD_OBJECT_NOTIFIER_PARAM_NO_INIT \
, const JSGuardObjectNotifier& _notifier
#define JS_GUARD_OBJECT_NOTIFIER_INIT \
JS_BEGIN_MACRO _mCheckNotUsedAsTemporary.Init(_notifier); JS_END_MACRO
@ -411,7 +409,6 @@ public:
#define JS_DECL_USE_GUARD_OBJECT_NOTIFIER
#define JS_GUARD_OBJECT_NOTIFIER_PARAM
#define JS_GUARD_OBJECT_NOTIFIER_PARAM0
#define JS_GUARD_OBJECT_NOTIFIER_PARAM_NO_INIT
#define JS_GUARD_OBJECT_NOTIFIER_INIT JS_BEGIN_MACRO JS_END_MACRO
#endif /* !defined(DEBUG) */

View File

@ -62,6 +62,7 @@
#include "jsapi.h"
#include "jsdbgapi.h"
#include "jsobj.h"
#include "jsscript.h"
#include "jscntxt.h"
#include "mozilla/FunctionTimer.h"