Bug 393368: new API to limit heap consumption by stack-like data structures used by compiler, decompiler and interpreter.

This commit is contained in:
igor@mir2.org 2007-08-27 15:21:55 -07:00
Родитель 4ded1d172a
Коммит 6cc27f8fb6
11 изменённых файлов: 119 добавлений и 30 удалений

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

@ -98,8 +98,10 @@ size_t gStackChunkSize = 8192;
/* Assume that we can not use more than 5e5 bytes of C stack by default. */
static size_t gMaxStackSize = 500000;
static jsuword gStackBase;
static size_t gScriptStackQuota = JS_DEFAULT_SCRIPT_STACK_QUOTA;
int gExitCode = 0;
JSBool gQuitting = JS_FALSE;
FILE *gErrFile = NULL;
@ -1750,6 +1752,21 @@ StringsAreUTF8(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
return JS_TRUE;
}
static JSBool
StackQuota(JSContext *cx, uintN argc, jsval *vp)
{
uint32 n;
if (argc == 0)
return JS_NewNumberValue(cx, (double) gScriptStackQuota, vp);
if (!JS_ValueToECMAUint32(cx, JS_ARGV(cx, vp)[0], &n))
return JS_FALSE;
gScriptStackQuota = n;
JS_SetScriptStackQuota(cx, gScriptStackQuota);
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
static const char* badUTF8 = "...\xC0...";
static const char* bigUTF8 = "...\xFB\xBF\xBF\xBF\xBF...";
static const jschar badSurrogate[] = { 'A', 'B', 'C', 0xDEEE, 'D', 'E', 0 };
@ -2222,6 +2239,7 @@ static JSFunctionSpec shell_functions[] = {
JS_FS("untrap", Untrap, 2,0,0),
JS_FS("line2pc", LineToPC, 0,0,0),
JS_FS("pc2line", PCToLine, 0,0,0),
JS_FN("stackQuota", StackQuota, 0,0,0,0),
JS_FS("stringsAreUTF8", StringsAreUTF8, 0,0,0),
JS_FS("testUTF8", TestUTF8, 1,0,0),
JS_FS("throwError", ThrowError, 0,0,0),
@ -2269,6 +2287,7 @@ static char *shell_help_messages[] = {
"untrap(fun[, pc]) Remove a trap",
"line2pc([fun,] line) Map line number to PC",
"pc2line(fun[, pc]) Map PC to line number",
"stackQuota([number]) Query/set script stack quota",
"stringsAreUTF8() Check if strings are UTF-8 encoded",
"testUTF8(mode) Perform UTF-8 tests (modes are 1 to 4)",
"throwError() Throw an error from JS_ReportError",
@ -3122,6 +3141,17 @@ snarf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
#endif /* NARCISSUS */
JSBool
ContextCallback(JSContext *cx, uintN contextOp)
{
if (contextOp == JSCONTEXT_NEW) {
JS_SetErrorReporter(cx, my_ErrorReporter);
JS_SetVersion(cx, JSVERSION_LATEST);
JS_SetScriptStackQuota(cx, gScriptStackQuota);
}
return JS_TRUE;
}
int
main(int argc, char **argv, char **envp)
{
@ -3157,12 +3187,11 @@ main(int argc, char **argv, char **envp)
rt = JS_NewRuntime(64L * 1024L * 1024L);
if (!rt)
return 1;
JS_SetContextCallback(rt, ContextCallback);
cx = JS_NewContext(rt, gStackChunkSize);
if (!cx)
return 1;
JS_SetErrorReporter(cx, my_ErrorReporter);
JS_SetVersion(cx, JSVERSION_LATEST);
#ifdef JS_THREADSAFE
JS_BeginRequest(cx);

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

@ -2540,6 +2540,12 @@ JS_SetThreadStackLimit(JSContext *cx, jsuword limitAddr)
cx->stackLimit = limitAddr;
}
JS_PUBLIC_API(void)
JS_SetScriptStackQuota(JSContext *cx, size_t quota)
{
cx->scriptStackQuota = quota;
}
/************************************************************************/
JS_PUBLIC_API(void)
@ -4334,8 +4340,10 @@ CompileTokenStream(JSContext *cx, JSObject *obj, JSTokenStream *ts,
JSScript *script;
eof = JS_FALSE;
JS_INIT_ARENA_POOL(&codePool, "code", 1024, sizeof(jsbytecode));
JS_INIT_ARENA_POOL(&notePool, "note", 1024, sizeof(jssrcnote));
JS_INIT_ARENA_POOL(&codePool, "code", 1024, sizeof(jsbytecode),
&cx->scriptStackQuota);
JS_INIT_ARENA_POOL(&notePool, "note", 1024, sizeof(jssrcnote),
&cx->scriptStackQuota);
js_InitParseContext(cx, &pc);
JS_ASSERT(!ts->parseContext);
ts->parseContext = &pc;

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

@ -1162,6 +1162,21 @@ JS_GetExternalStringGCType(JSRuntime *rt, JSString *str);
extern JS_PUBLIC_API(void)
JS_SetThreadStackLimit(JSContext *cx, jsuword limitAddr);
/*
* Set the quota on the number of bytes that stack-like data structures can
* use when the runtime compiles and executes scripts. These structures
* consume heap space, so JS_SetThreadStackLimit does not bound their size.
* The default quota is 32MB which is quite generous.
*
* The function must be called before any script compilation or execution API
* calls, i.e. either immediately after JS_NewContext or from JSCONTEXT_NEW
* context callback.
*/
extern JS_PUBLIC_API(void)
JS_SetScriptStackQuota(JSContext *cx, size_t quota);
#define JS_DEFAULT_SCRIPT_STACK_QUOTA ((size_t) 0x2000000)
/************************************************************************/
/*

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

@ -62,7 +62,7 @@ static JSArenaStats *arena_stats_list;
JS_PUBLIC_API(void)
JS_INIT_NAMED_ARENA_POOL(JSArenaPool *pool, const char *name, size_t size,
size_t align)
size_t align, size_t *quotap)
{
if (align == 0)
align = JS_ARENA_DEFAULT_ALIGN;
@ -72,6 +72,7 @@ JS_INIT_NAMED_ARENA_POOL(JSArenaPool *pool, const char *name, size_t size,
JS_ARENA_ALIGN(pool, &pool->first + 1);
pool->current = &pool->first;
pool->arenasize = size;
pool->quotap = quotap;
#ifdef JS_ARENAMETER
memset(&pool->stats, 0, sizeof pool->stats);
pool->stats.name = strdup(name);
@ -156,9 +157,19 @@ JS_ArenaAllocate(JSArenaPool *pool, size_t nb)
gross = hdrsz + JS_MAX(nb, pool->arenasize);
if (gross < nb)
return NULL;
b = (JSArena *) malloc(gross);
if (!b)
return NULL;
if (pool->quotap) {
if (gross > *pool->quotap)
return NULL;
b = (JSArena *) malloc(gross);
if (!b)
return NULL;
*pool->quotap -= gross;
} else {
b = (JSArena *) malloc(gross);
if (!b)
return NULL;
}
b->next = NULL;
b->limit = (jsuword)b + gross;
JS_COUNT_ARENA(pool,++);
@ -189,7 +200,7 @@ JS_PUBLIC_API(void *)
JS_ArenaRealloc(JSArenaPool *pool, void *p, size_t size, size_t incr)
{
JSArena **ap, *a, *b;
jsuword boff, aoff, extra, hdrsz, gross;
jsuword boff, aoff, extra, hdrsz, gross, growth;
/*
* Use the oversized-single-allocation header to avoid searching for ap.
@ -212,9 +223,19 @@ JS_ArenaRealloc(JSArenaPool *pool, void *p, size_t size, size_t incr)
hdrsz = sizeof *a + extra + pool->mask; /* header and alignment slop */
gross = hdrsz + aoff;
JS_ASSERT(gross > aoff);
a = (JSArena *) realloc(a, gross);
if (!a)
return NULL;
if (pool->quotap) {
growth = gross - (a->limit - (jsuword) a);
if (growth > *pool->quotap)
return NULL;
a = (JSArena *) realloc(a, gross);
if (!a)
return NULL;
*pool->quotap -= growth;
} else {
a = (JSArena *) realloc(a, gross);
if (!a)
return NULL;
}
#ifdef JS_ARENAMETER
pool->stats.nreallocs++;
#endif
@ -290,6 +311,8 @@ FreeArenaList(JSArenaPool *pool, JSArena *head)
do {
*ap = a->next;
if (pool->quotap)
*pool->quotap += a->limit - (jsuword) a;
JS_CLEAR_ARENA(a);
JS_COUNT_ARENA(pool,--);
free(a);
@ -374,6 +397,8 @@ JS_ArenaFreeAllocation(JSArenaPool *pool, void *p, size_t size)
JS_ASSERT(GET_HEADER(pool, b) == &a->next);
SET_HEADER(pool, b, ap);
}
if (pool->quotap)
*pool->quotap += a->limit - (jsuword) a;
JS_CLEAR_ARENA(a);
JS_COUNT_ARENA(pool,--);
free(a);

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

@ -88,17 +88,19 @@ struct JSArenaPool {
JSArena *current; /* arena from which to allocate space */
size_t arenasize; /* net exact size of a new arena */
jsuword mask; /* alignment mask (power-of-2 - 1) */
size_t *quotap; /* pointer to the quota on pool allocation
size or null if pool is unlimited */
#ifdef JS_ARENAMETER
JSArenaStats stats;
#endif
};
#ifdef JS_ARENAMETER
#define JS_INIT_NAMED_ARENA_POOL(pool, name, size, align) \
JS_InitArenaPool(pool, name, size, align)
#define JS_INIT_NAMED_ARENA_POOL(pool, name, size, align, quotap) \
JS_InitArenaPool(pool, name, size, align, quotap)
#else
#define JS_INIT_NAMED_ARENA_POOL(pool, name, size, align) \
JS_InitArenaPool(pool, size, align)
#define JS_INIT_NAMED_ARENA_POOL(pool, name, size, align, quotap) \
JS_InitArenaPool(pool, size, align, quotap)
#endif
/*
@ -110,14 +112,15 @@ struct JSArenaPool {
#define JS_ARENA_ALIGN(pool, n) (((jsuword)(n) + JS_ARENA_CONST_ALIGN_MASK) \
& ~(jsuword)JS_ARENA_CONST_ALIGN_MASK)
#define JS_INIT_ARENA_POOL(pool, name, size) \
JS_INIT_NAMED_ARENA_POOL(pool, name, size, JS_ARENA_CONST_ALIGN_MASK + 1)
#define JS_INIT_ARENA_POOL(pool, name, size, quotap) \
JS_INIT_NAMED_ARENA_POOL(pool, name, size, JS_ARENA_CONST_ALIGN_MASK + 1, \
quotap)
#else
#define JS_ARENA_ALIGN(pool, n) (((jsuword)(n) + (pool)->mask) & ~(pool)->mask)
#define JS_INIT_ARENA_POOL(pool, name, size, align) \
JS_INIT_NAMED_ARENA_POOL(pool, name, size, align)
#define JS_INIT_ARENA_POOL(pool, name, size, align, quotap) \
JS_INIT_NAMED_ARENA_POOL(pool, name, size, align, quotap)
#endif
@ -235,7 +238,7 @@ struct JSArenaPool {
*/
extern JS_PUBLIC_API(void)
JS_INIT_NAMED_ARENA_POOL(JSArenaPool *pool, const char *name, size_t size,
size_t align);
size_t align, size_t *quotap);
/*
* Free the arenas in pool. The user may continue to allocate from pool

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

@ -229,6 +229,7 @@ js_NewContext(JSRuntime *rt, size_t stackChunkSize)
#if JS_STACK_GROWTH_DIRECTION > 0
cx->stackLimit = (jsuword)-1;
#endif
cx->scriptStackQuota = JS_DEFAULT_SCRIPT_STACK_QUOTA;
#ifdef JS_THREADSAFE
JS_INIT_CLIST(&cx->threadLinks);
js_SetContextThread(cx);
@ -259,8 +260,10 @@ js_NewContext(JSRuntime *rt, size_t stackChunkSize)
* done by js_DestroyContext).
*/
cx->version = JSVERSION_DEFAULT;
JS_INIT_ARENA_POOL(&cx->stackPool, "stack", stackChunkSize, sizeof(jsval));
JS_INIT_ARENA_POOL(&cx->tempPool, "temp", 1024, sizeof(jsdouble));
JS_INIT_ARENA_POOL(&cx->stackPool, "stack", stackChunkSize, sizeof(jsval),
&cx->scriptStackQuota);
JS_INIT_ARENA_POOL(&cx->tempPool, "temp", 1024, sizeof(jsdouble),
&cx->scriptStackQuota);
if (!js_InitRegExpStatics(cx, &cx->regExpStatics)) {
js_DestroyContext(cx, JSDCM_NEW_FAILED);

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

@ -689,9 +689,12 @@ struct JSContext {
JSPackedBool throwing; /* is there a pending exception? */
jsval exception; /* most-recently-thrown exception */
/* Limit pointer for checking stack consumption during recursion. */
/* Limit pointer for checking native stack consumption during recursion. */
jsuword stackLimit;
/* Quota on the size of arenas used to compile and execute scripts. */
size_t scriptStackQuota;
/* Data shared by threads in an address space. */
JSRuntime *runtime;

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

@ -673,7 +673,7 @@ JS_NEW_PRINTER(JSContext *cx, const char *name, uintN indent, JSBool pretty)
if (!jp)
return NULL;
INIT_SPRINTER(cx, &jp->sprinter, &jp->pool, 0);
JS_INIT_ARENA_POOL(&jp->pool, name, 256, 1);
JS_INIT_ARENA_POOL(&jp->pool, name, 256, 1, &cx->scriptStackQuota);
jp->indent = indent & ~JS_IN_GROUP_CONTEXT;
jp->pretty = pretty;
jp->grouped = (indent & JS_IN_GROUP_CONTEXT) != 0;

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

@ -879,8 +879,10 @@ js_CompileFunctionBody(JSContext *cx, JSTokenStream *ts, JSFunction *fun)
JSObject *funobj;
JSParseNode *pn;
JS_INIT_ARENA_POOL(&codePool, "code", 1024, sizeof(jsbytecode));
JS_INIT_ARENA_POOL(&notePool, "note", 1024, sizeof(jssrcnote));
JS_INIT_ARENA_POOL(&codePool, "code", 1024, sizeof(jsbytecode),
&cx->scriptStackQuota);
JS_INIT_ARENA_POOL(&notePool, "note", 1024, sizeof(jssrcnote),
&cx->scriptStackQuota);
js_InitParseContext(cx, &pc);
JS_ASSERT(!ts->parseContext);
ts->parseContext = &pc;

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

@ -3382,7 +3382,8 @@ js_ExecuteRegExp(JSContext *cx, JSRegExp *re, JSString *str, size_t *indexp,
gData.start = start;
gData.skipped = 0;
JS_INIT_ARENA_POOL(&gData.pool, "RegExpPool", 8096, 4);
JS_INIT_ARENA_POOL(&gData.pool, "RegExpPool", 8096, 4,
&cx->scriptStackQuota);
x = InitMatch(cx, &gData, re, length);
if (!x) {

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

@ -1950,7 +1950,7 @@ js_InitPropertyTree(JSRuntime *rt)
return JS_FALSE;
}
JS_INIT_ARENA_POOL(&rt->propertyArenaPool, "properties",
256 * sizeof(JSScopeProperty), sizeof(void *));
256 * sizeof(JSScopeProperty), sizeof(void *), NULL);
return JS_TRUE;
}