Bug 341896: GC finalizes the states of native iterators before finalizing the rest of object. It avoids using expensive close hooks. r=brendan

This commit is contained in:
igor.bukanov%gmail.com 2006-06-29 06:51:33 +00:00
Родитель 2e17692115
Коммит 83062e5eb3
6 изменённых файлов: 269 добавлений и 123 удалений

Просмотреть файл

@ -147,6 +147,14 @@ extern JS_PUBLIC_API(JSIntn) JS_FloorLog2(JSUint32 i);
JS_END_MACRO
#endif
/*
* Internal function.
* Compute the log of the least power of 2 greater than or equal to n.
* This is a version of JS_CeilingLog2 that operates on jsuword with
* CPU-dependant size.
*/
#define JS_CEILING_LOG2W(n) ((n) <= 1 ? 0 : 1 + JS_FLOOR_LOG2W((n) - 1))
/*
* Internal function.
* Compute the log of the greatest power of 2 less than or equal to n.

Просмотреть файл

@ -172,7 +172,13 @@ struct JSRuntime {
* Table for tracking objects of extended classes that have non-null close
* hooks, and need the GC to perform two-phase finalization.
*/
JSGCCloseTable gcCloseTable;
JSPtrTable gcCloseTable;
/*
* Table for tracking iterators to ensure that we close iterator's state
* before finalizing the iterable object.
*/
JSPtrTable gcIteratorTable;
#ifdef JS_GCMETER
JSGCStats gcStats;

Просмотреть файл

@ -64,6 +64,7 @@
#include "jsfun.h"
#include "jsgc.h"
#include "jsinterp.h"
#include "jsiter.h"
#include "jslock.h"
#include "jsnum.h"
#include "jsobj.h"
@ -237,6 +238,149 @@ JS_STATIC_ASSERT(sizeof(JSGCThing) >= sizeof(jsdouble));
JS_STATIC_ASSERT(GC_FLAGS_SIZE >= GC_PAGE_SIZE);
JS_STATIC_ASSERT(sizeof(JSStackHeader) >= 2 * sizeof(jsval));
/*
* JSPtrTable capacity growth descriptor. The table grows by powers of two
* starting from capacity JSPtrTableInfo.minCapacity, but switching to linear
* growth when capacity reaches JSPtrTableInfo.linearGrowthThreshold.
*/
typedef struct JSPtrTableInfo {
uint16 minCapacity;
uint16 linearGrowthThreshold;
} JSPtrTableInfo;
#define GC_CLOSE_TABLE_MIN 4
#define GC_CLOSE_TABLE_LINEAR 1024
#define GC_ITERATOR_TABLE_MIN 4
#define GC_ITERATOR_TABLE_LINEAR 1024
static const JSPtrTableInfo closeTableInfo = {
GC_CLOSE_TABLE_MIN,
GC_CLOSE_TABLE_LINEAR
};
static const JSPtrTableInfo iteratorTableInfo = {
GC_ITERATOR_TABLE_MIN,
GC_ITERATOR_TABLE_LINEAR
};
/* Calculate table capacity based on the current value of JSPtrTable.count. */
static size_t
PtrTableCapacity(size_t count, const JSPtrTableInfo *info)
{
size_t linear, log, capacity;
linear = info->linearGrowthThreshold;
JS_ASSERT(info->minCapacity <= linear);
if (count == 0) {
capacity = 0;
} else if (count < linear) {
log = JS_CEILING_LOG2W(count);
JS_ASSERT(log != JS_BITS_PER_WORD);
capacity = (size_t)1 << log;
if (capacity < info->minCapacity)
capacity = info->minCapacity;
} else {
capacity = JS_ROUNDUP(count, linear);
}
JS_ASSERT(capacity >= count);
return capacity;
}
static void
FreePtrTable(JSPtrTable *table, const JSPtrTableInfo *info)
{
if (table->array) {
JS_ASSERT(table->count > 0);
free(table->array);
table->array = NULL;
table->count = 0;
}
JS_ASSERT(table->count == 0);
}
static JSBool
AddToPtrTable(JSContext *cx, JSPtrTable *table, const JSPtrTableInfo *info,
void *ptr)
{
size_t count, capacity;
void **array;
count = table->count;
capacity = PtrTableCapacity(count, info);
if (count == capacity) {
if (capacity < info->minCapacity) {
JS_ASSERT(capacity == 0);
JS_ASSERT(!table->array);
capacity = info->minCapacity;
} else {
/*
* Simplify the overflow detection assuming pointer is bigger
* than byte.
*/
JS_STATIC_ASSERT(2 <= sizeof table->array[0]);
capacity = (capacity < info->linearGrowthThreshold)
? 2 * capacity
: capacity + info->linearGrowthThreshold;
if (capacity > (size_t)-1 / sizeof table->array[0])
goto bad;
}
array = (void **) realloc(table->array,
capacity * sizeof table->array[0]);
if (!array)
goto bad;
#ifdef DEBUG
memset(array + count, JS_FREE_PATTERN,
(capacity - count) * sizeof table->array[0]);
#endif
table->array = array;
}
table->array[count] = ptr;
table->count = count + 1;
return JS_TRUE;
bad:
JS_ReportOutOfMemory(cx);
return JS_FALSE;
}
static void
ShrinkPtrTable(JSPtrTable *table, const JSPtrTableInfo *info,
size_t newCount)
{
size_t oldCapacity, capacity;
void **array;
JS_ASSERT(newCount <= table->count);
if (newCount == table->count)
return;
oldCapacity = PtrTableCapacity(table->count, info);
table->count = newCount;
capacity = PtrTableCapacity(newCount, info);
if (oldCapacity != capacity) {
array = table->array;
JS_ASSERT(array);
if (capacity == 0) {
free(array);
table->array = NULL;
return;
}
array = (void **) realloc(array, capacity * sizeof array[0]);
if (array)
table->array = array;
}
#ifdef DEBUG
memset(table->array + newCount, JS_FREE_PATTERN,
(capacity - newCount) * sizeof table->array[0]);
#endif
}
#ifdef JS_GCMETER
# define METER(x) x
#else
@ -343,11 +487,8 @@ FinishGCArenaLists(JSRuntime *rt)
arenaList->freeList = NULL;
}
if (rt->gcCloseTable.array) {
free(rt->gcCloseTable.array);
rt->gcCloseTable.array = NULL;
rt->gcCloseTable.count = 0;
}
FreePtrTable(&rt->gcCloseTable, &closeTableInfo);
FreePtrTable(&rt->gcIteratorTable, &iteratorTableInfo);
}
uint8 *
@ -698,12 +839,49 @@ js_RemoveRoot(JSRuntime *rt, void *rp)
return JS_TRUE;
}
JSBool
js_RegisterCloseableIterator(JSContext *cx, JSObject *obj)
{
JSRuntime *rt;
JSBool ok;
rt = cx->runtime;
JS_ASSERT(!rt->gcRunning || rt->gcClosePhase);
JS_LOCK_GC(rt);
ok = AddToPtrTable(cx, &rt->gcIteratorTable, &iteratorTableInfo, obj);
JS_UNLOCK_GC(rt);
return ok;
}
static void
CloseIteratorStates(JSContext *cx)
{
JSRuntime *rt;
size_t count, newCount, i;
void **array;
JSObject *obj;
rt = cx->runtime;
count = rt->gcIteratorTable.count;
array = rt->gcIteratorTable.array;
newCount = 0;
for (i = 0; i != count; ++i) {
obj = (JSObject *)array[i];
if (js_IsAboutToBeFinalized(cx, obj))
js_CloseIteratorState(cx, obj);
else
array[newCount++] = obj;
}
ShrinkPtrTable(&rt->gcIteratorTable, &iteratorTableInfo, newCount);
}
JSBool
js_AddObjectToCloseTable(JSContext *cx, JSObject *obj)
{
JSRuntime *rt;
JSObject **array;
uint32 count, length;
JSBool ok;
/*
* Return early without doing anything if shutting down, to prevent a bad
@ -725,30 +903,9 @@ js_AddObjectToCloseTable(JSContext *cx, JSObject *obj)
}
JS_LOCK_GC(rt);
array = rt->gcCloseTable.array;
count = rt->gcCloseTable.count;
length = GC_CLOSE_TABLE_LENGTH(count);
if (!array || count == length) {
length = (length < GC_CLOSE_TABLE_LINEAR)
? 2 * length
: length + GC_CLOSE_TABLE_LINEAR;
array = (JSObject **) realloc(array, length * sizeof(JSObject *));
if (!array) {
JS_UNLOCK_GC(rt);
JS_ReportOutOfMemory(cx);
return JS_FALSE;
}
#ifdef DEBUG
memset(array + count, 0, (length - count) * sizeof(JSObject *));
#endif
rt->gcCloseTable.array = array;
}
array[count++] = obj;
rt->gcCloseTable.count = count;
ok = AddToPtrTable(cx, &rt->gcCloseTable, &closeTableInfo, obj);
JS_UNLOCK_GC(rt);
return JS_TRUE;
return ok;
}
/*
@ -757,10 +914,10 @@ js_AddObjectToCloseTable(JSContext *cx, JSObject *obj)
* with indexes from the [startIndex, startIndex + count) range.
*/
typedef struct JSObjectsToClose {
uint32 count;
uint32 startIndex;
size_t count;
size_t startIndex;
#ifdef DEBUG
JSGCCloseTable tableSnapshot;
JSPtrTable tableSnapshot;
#endif
} JSObjectsToClose;
@ -771,8 +928,9 @@ static void
FindAndMarkObjectsToClose(JSContext *cx, JSObjectsToClose *toClose)
{
JSRuntime *rt;
JSObject **array, *obj;
uint32 count, index, pivot;
void **array;
JSObject *obj;
size_t count, index, pivot;
/*
* Find unmarked objects reachable from rt->gcCloseTable and set them
@ -783,7 +941,7 @@ FindAndMarkObjectsToClose(JSContext *cx, JSObjectsToClose *toClose)
count = pivot = rt->gcCloseTable.count;
index = 0;
while (index != pivot) {
obj = array[index];
obj = (JSObject *)array[index];
if (*js_GetGCThingFlags(obj) & GCF_MARK) {
++index;
} else {
@ -827,16 +985,16 @@ ExecuteCloseHooks(JSContext *cx, const JSObjectsToClose *toClose)
JSRuntime *rt;
JSStackFrame *fp;
uint32 index, endIndex;
JSObject *obj, **array;
JSObject *obj;
JSExtendedClass *xclasp;
uint32 added, length;
void **array;
rt = cx->runtime;
JS_ASSERT(toClose->count > 0);
/* Close table manupulations are not allowed during the marking phase. */
JS_ASSERT(toClose->tableSnapshot.array == rt->gcCloseTable.array);
JS_ASSERT(toClose->tableSnapshot.count == rt->gcCloseTable.count);
JS_ASSERT(memcmp(&toClose->tableSnapshot, &rt->gcCloseTable,
sizeof toClose->tableSnapshot) == 0);
/*
* Execute the close hooks. Temporarily set aside cx->fp here to prevent
@ -853,7 +1011,7 @@ ExecuteCloseHooks(JSContext *cx, const JSObjectsToClose *toClose)
* Reload rt->gcCloseTable.array because close hooks may create
* new objects that have close hooks and reallocate the table.
*/
obj = rt->gcCloseTable.array[index];
obj = (JSObject *)rt->gcCloseTable.array[index];
xclasp = (JSExtendedClass *) LOCKED_OBJ_GET_CLASS(obj);
JS_ASSERT(xclasp->base.flags & JSCLASS_IS_EXTENDED);
xclasp->close(cx, obj);
@ -867,36 +1025,10 @@ ExecuteCloseHooks(JSContext *cx, const JSObjectsToClose *toClose)
* processed, and update the table's count.
*/
array = rt->gcCloseTable.array;
added = rt->gcCloseTable.count - endIndex;
memmove(array + toClose->startIndex, array + endIndex,
added * sizeof array[0]);
rt->gcCloseTable.count -= toClose->count;
/*
* Check whether we need to shrink the table now. The previous
* value, which is possibly the maximum, of the table count is
* (endIndex + added). If as many or more objects were added during
* the above close loop as were closed, we won't shrink.
*
* Note that we might not shrink even if more objects were closed
* than were added, since we may not have closed enough to justify
* a shrink by power of two or linear growth increment. So use
* straightforward code here, computing current and previous
* lengths and comparing.
*/
length = GC_CLOSE_TABLE_LENGTH(rt->gcCloseTable.count);
endIndex += added;
if (length < GC_CLOSE_TABLE_LENGTH(endIndex)) {
JS_ASSERT(toClose->count > added);
array = (JSObject **) realloc(array, length * sizeof array[0]);
if (array) {
rt->gcCloseTable.array = array;
#ifdef DEBUG
memset(array + rt->gcCloseTable.count, JS_FREE_PATTERN,
(length - rt->gcCloseTable.count) * sizeof array[0]);
#endif
}
}
(rt->gcCloseTable.count - endIndex) * sizeof array[0]);
ShrinkPtrTable(&rt->gcCloseTable, &closeTableInfo,
rt->gcCloseTable.count - toClose->count);
}
#if defined(DEBUG_brendan) || defined(DEBUG_timeless)
@ -2485,18 +2617,23 @@ restart:
}
JS_ASSERT(rt->gcUnscannedBagSize == 0);
/* Finalize iterator states before the objects they iterate over. */
CloseIteratorStates(cx);
/*
* Sweep phase.
*
* Finalize as we sweep, outside of rt->gcLock but with rt->gcRunning set
* so that any attempt to allocate a GC-thing from a finalizer will fail,
* rather than nest badly and leave the unmarked newborn to be swept.
*
* Finalize smaller objects before larger, to guarantee finalization of
* GC-allocated obj->slots after obj. See FreeSlots in jsobj.c.
*/
js_SweepAtomState(&rt->atomState);
js_SweepScopeProperties(rt);
/*
* Finalize smaller objects before larger, to guarantee finalization of
* GC-allocated obj->slots after obj. See FreeSlots in jsobj.c.
*/
for (i = 0; i < GC_NUM_FREELISTS; i++) {
arenaList = &rt->gcArenaList[i];
nbytes = GC_FREELIST_NBYTES(i);

Просмотреть файл

@ -131,32 +131,18 @@ js_AddRootRT(JSRuntime *rt, void *rp, const char *name);
extern JSBool
js_RemoveRoot(JSRuntime *rt, void *rp);
/*
* Table for tracking objects of extended classes that have non-null close
* hooks, and need the GC to perform two-phase finalization. The array grows
* by powers of two starting from length GC_CLOSE_TABLE_MIN, but switching to
* linear growth when length reaches GC_CLOSE_TABLE_LINEAR. The count member
* counts valid slots in array, so the current allocated length is given by
* GC_CLOSE_TABLE_LENGTH(count).
*/
typedef struct JSGCCloseTable {
JSObject **array;
uint32 count;
} JSGCCloseTable;
/* Table of pointers with count valid members. */
typedef struct JSPtrTable {
size_t count;
void **array;
} JSPtrTable;
extern JSBool
js_RegisterCloseableIterator(JSContext *cx, JSObject *obj);
extern JSBool
js_AddObjectToCloseTable(JSContext *cx, JSObject *obj);
#define GC_CLOSE_TABLE_MIN_LOG2 3
#define GC_CLOSE_TABLE_MIN JS_BIT(GC_CLOSE_TABLE_MIN_LOG2)
#define GC_CLOSE_TABLE_LINEAR_LOG2 10
#define GC_CLOSE_TABLE_LINEAR JS_BIT(GC_CLOSE_TABLE_LINEAR_LOG2)
#define GC_CLOSE_TABLE_LENGTH(count) \
(((count) < GC_CLOSE_TABLE_LINEAR) \
? JS_MAX(JS_BIT(JS_CeilingLog2(count)), GC_CLOSE_TABLE_MIN) \
: JS_ROUNDUP(count, GC_CLOSE_TABLE_LINEAR))
/*
* The private JSGCThing struct, which describes a gcFreeList element.
*/

Просмотреть файл

@ -76,23 +76,31 @@ extern const char js_throw_str[]; /* from jsscan.h */
#error JS_INITIAL_NSLOTS must be greater than JSSLOT_ITER_FLAGS.
#endif
static void
iterator_close(JSContext *cx, JSObject *obj)
/*
* Shared code to close iterator's state either through an explicit call or
* when GC detects that the iterator is no longer reachable.
*/
void
js_CloseIteratorState(JSContext *cx, JSObject *iterobj)
{
jsval *slots;
jsval state, parent;
JSObject *iterable;
JS_ASSERT(JS_InstanceOf(cx, iterobj, &js_IteratorClass, NULL));
slots = iterobj->slots;
/* Avoid double work if js_CloseNativeIterator was called on obj. */
state = obj->slots[JSSLOT_ITER_STATE];
if (JSVAL_IS_VOID(state))
state = slots[JSSLOT_ITER_STATE];
if (JSVAL_IS_NULL(state))
return;
/* Protect against failure to fully initialize obj. */
parent = obj->slots[JSSLOT_PARENT];
if (!JSVAL_IS_NULL(state) && !JSVAL_IS_PRIMITIVE(parent)) {
parent = slots[JSSLOT_PARENT];
if (!JSVAL_IS_PRIMITIVE(parent)) {
iterable = JSVAL_TO_OBJECT(parent);
#if JS_HAS_XML_SUPPORT
if ((JSVAL_TO_INT(obj->slots[JSSLOT_ITER_FLAGS]) & JSITER_FOREACH) &&
if ((JSVAL_TO_INT(slots[JSSLOT_ITER_FLAGS]) & JSITER_FOREACH) &&
OBJECT_IS_XML(cx, iterable)) {
((JSXMLObjectOps *) iterable->map->ops)->
enumerateValues(cx, iterable, JSENUMERATE_DESTROY, &state,
@ -101,18 +109,16 @@ iterator_close(JSContext *cx, JSObject *obj)
#endif
OBJ_ENUMERATE(cx, iterable, JSENUMERATE_DESTROY, &state, NULL);
}
slots[JSSLOT_ITER_STATE] = JSVAL_NULL;
}
JSExtendedClass js_IteratorClass = {
{ "Iterator",
JSCLASS_IS_EXTENDED |
JSClass js_IteratorClass = {
"Iterator",
JSCLASS_HAS_RESERVED_SLOTS(2) | /* slots for state and flags */
JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator),
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS },
NULL, NULL, NULL, iterator_close,
JSCLASS_NO_RESERVED_MEMBERS
JSCLASS_NO_OPTIONAL_MEMBERS
};
#if JS_HAS_GENERATORS
@ -217,7 +223,7 @@ iterator_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
JSBool foreach, ok;
jsid id;
if (!JS_InstanceOf(cx, obj, &js_IteratorClass.base, argv))
if (!JS_InstanceOf(cx, obj, &js_IteratorClass, argv))
return JS_FALSE;
iterable = OBJ_GET_PARENT(cx, obj);
@ -290,7 +296,7 @@ js_NewNativeIterator(JSContext *cx, JSObject *obj, uintN flags, jsval *vp)
* scope chain to lookup the iterator's constructor. Since we use the
* parent slot to keep track of the iterable, we must fix it up later.
*/
iterobj = js_NewObject(cx, &js_IteratorClass.base, NULL, NULL);
iterobj = js_NewObject(cx, &js_IteratorClass, NULL, NULL);
if (!iterobj)
return JS_FALSE;
@ -301,6 +307,8 @@ js_NewNativeIterator(JSContext *cx, JSObject *obj, uintN flags, jsval *vp)
iterobj->slots[JSSLOT_PARENT] = OBJECT_TO_JSVAL(obj);
iterobj->slots[JSSLOT_ITER_STATE] = JSVAL_NULL;
iterobj->slots[JSSLOT_ITER_FLAGS] = INT_TO_JSVAL(flags);
if (!js_RegisterCloseableIterator(cx, iterobj))
return JS_FALSE;
ok =
#if JS_HAS_XML_SUPPORT
@ -320,7 +328,7 @@ js_NewNativeIterator(JSContext *cx, JSObject *obj, uintN flags, jsval *vp)
uintN
js_GetNativeIteratorFlags(JSContext *cx, JSObject *iterobj)
{
if (OBJ_GET_CLASS(cx, iterobj) != &js_IteratorClass.base)
if (OBJ_GET_CLASS(cx, iterobj) != &js_IteratorClass)
return 0;
return JSVAL_TO_INT(OBJ_GET_SLOT(cx, iterobj, JSSLOT_ITER_FLAGS));
}
@ -330,7 +338,7 @@ js_CloseNativeIterator(JSContext *cx, JSObject *iterobj)
{
uintN flags;
if (!JS_InstanceOf(cx, iterobj, &js_IteratorClass.base, NULL))
if (!JS_InstanceOf(cx, iterobj, &js_IteratorClass, NULL))
return;
/*
@ -351,9 +359,7 @@ js_CloseNativeIterator(JSContext *cx, JSObject *iterobj)
if (iterobj == cx->cachedIterObj)
cx->cachedIterObj = NULL;
/* Close iterobj, then mark its ITER_STATE slot to flag it as dead. */
iterator_close(cx, iterobj);
iterobj->slots[JSSLOT_ITER_STATE] = JSVAL_VOID;
js_CloseIteratorState(cx, iterobj);
}
JSBool
@ -362,7 +368,7 @@ js_DefaultIterator(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
{
JSBool keyonly;
if (OBJ_GET_CLASS(cx, obj) == &js_IteratorClass.base) {
if (OBJ_GET_CLASS(cx, obj) == &js_IteratorClass) {
*rval = OBJECT_TO_JSVAL(obj);
return JS_TRUE;
}
@ -407,7 +413,7 @@ js_ValueToIterator(JSContext *cx, jsval v, uintN flags)
if (JSVAL_IS_PRIMITIVE(rval))
goto bad_iterator;
iterobj = JSVAL_TO_OBJECT(rval);
JS_ASSERT(OBJ_GET_CLASS(cx, iterobj) == &js_IteratorClass.base);
JS_ASSERT(OBJ_GET_CLASS(cx, iterobj) == &js_IteratorClass);
iterobj->slots[JSSLOT_ITER_FLAGS] |= INT_TO_JSVAL(JSITER_HIDDEN);
goto out;
}
@ -427,7 +433,7 @@ js_ValueToIterator(JSContext *cx, jsval v, uintN flags)
* code -- the js_CloseNativeIteration early-finalization optimization
* based on it will break badly if script can reach iterobj.
*/
if (OBJ_GET_CLASS(cx, iterobj) == &js_IteratorClass.base &&
if (OBJ_GET_CLASS(cx, iterobj) == &js_IteratorClass &&
VALUE_IS_FUNCTION(cx, fval)) {
fun = (JSFunction *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(fval));
if (!FUN_INTERPRETED(fun) && fun->u.n.native == js_DefaultIterator)
@ -467,7 +473,7 @@ js_CallIteratorNext(JSContext *cx, JSObject *iterobj, uintN flags,
/* Fastest path for repeated call from for-in loop bytecode. */
if (iterobj == cx->cachedIterObj) {
JS_ASSERT(OBJ_GET_CLASS(cx, iterobj) == &js_IteratorClass.base);
JS_ASSERT(OBJ_GET_CLASS(cx, iterobj) == &js_IteratorClass);
JS_ASSERT(flags & JSITER_HIDDEN);
if (!iterator_next(cx, iterobj, 0, NULL, rval) ||
!CheckKeyValueReturn(cx, idp, rval)) {
@ -484,7 +490,7 @@ js_CallIteratorNext(JSContext *cx, JSObject *iterobj, uintN flags,
scope = OBJ_SCOPE(obj);
sprop = NULL;
while (LOCKED_OBJ_GET_CLASS(obj) == &js_IteratorClass.base) {
while (LOCKED_OBJ_GET_CLASS(obj) == &js_IteratorClass) {
obj = scope->object;
sprop = SCOPE_GET_PROPERTY(scope, id);
if (sprop)
@ -873,7 +879,7 @@ js_InitIteratorClasses(JSContext *cx, JSObject *obj)
#if JS_HAS_GENERATORS
/* Expose Iterator and initialize the generator internals if configured. */
proto = JS_InitClass(cx, obj, NULL, &js_IteratorClass.base, Iterator, 2,
proto = JS_InitClass(cx, obj, NULL, &js_IteratorClass, Iterator, 2,
NULL, iterator_methods, NULL, NULL);
if (!proto)
return NULL;

Просмотреть файл

@ -58,6 +58,9 @@ js_GetNativeIteratorFlags(JSContext *cx, JSObject *iterobj);
extern void
js_CloseNativeIterator(JSContext *cx, JSObject *iterobj);
extern void
js_CloseIteratorState(JSContext *cx, JSObject *iterobj);
extern JSBool
js_DefaultIterator(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
jsval *rval);
@ -93,7 +96,7 @@ extern JSObject *
js_NewGenerator(JSContext *cx, JSStackFrame *fp);
extern JSExtendedClass js_GeneratorClass;
extern JSExtendedClass js_IteratorClass;
extern JSClass js_IteratorClass;
extern JSClass js_StopIterationClass;
extern JSClass js_GeneratorExitClass;