Bug 357169: temp root macros are type-safe now and details of AddRoot etc. implementation do not leak ouside jsgc.c. r=brendan

This commit is contained in:
igor.bukanov%gmail.com 2006-10-27 19:38:33 +00:00
parent 8c58a3686e
commit 848e7ed2e3
13 changed files with 202 additions and 153 deletions

View File

@ -1795,84 +1795,22 @@ JS_ForgetLocalRoot(JSContext *cx, void *thing)
js_ForgetLocalRoot(cx, (jsval) thing);
}
#include "jshash.h" /* Added by JSIFY */
#ifdef DEBUG
typedef struct NamedRootDumpArgs {
void (*dump)(const char *name, void *rp, void *data);
void *data;
} NamedRootDumpArgs;
JS_STATIC_DLL_CALLBACK(JSDHashOperator)
js_named_root_dumper(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number,
void *arg)
{
NamedRootDumpArgs *args = (NamedRootDumpArgs *) arg;
JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr;
if (rhe->name)
args->dump(rhe->name, rhe->root, args->data);
return JS_DHASH_NEXT;
}
JS_PUBLIC_API(void)
JS_DumpNamedRoots(JSRuntime *rt,
void (*dump)(const char *name, void *rp, void *data),
void *data)
{
NamedRootDumpArgs args;
args.dump = dump;
args.data = data;
JS_DHashTableEnumerate(&rt->gcRootsHash, js_named_root_dumper, &args);
return js_DumpNamedRoots(rt, dump, data);
}
#endif /* DEBUG */
typedef struct GCRootMapArgs {
JSGCRootMapFun map;
void *data;
} GCRootMapArgs;
JS_STATIC_DLL_CALLBACK(JSDHashOperator)
js_gcroot_mapper(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number,
void *arg)
{
GCRootMapArgs *args = (GCRootMapArgs *) arg;
JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr;
intN mapflags;
JSDHashOperator op;
mapflags = args->map(rhe->root, rhe->name, args->data);
#if JS_MAP_GCROOT_NEXT == JS_DHASH_NEXT && \
JS_MAP_GCROOT_STOP == JS_DHASH_STOP && \
JS_MAP_GCROOT_REMOVE == JS_DHASH_REMOVE
op = (JSDHashOperator)mapflags;
#else
op = JS_DHASH_NEXT;
if (mapflags & JS_MAP_GCROOT_STOP)
op |= JS_DHASH_STOP;
if (mapflags & JS_MAP_GCROOT_REMOVE)
op |= JS_DHASH_REMOVE;
#endif
return op;
}
JS_PUBLIC_API(uint32)
JS_MapGCRoots(JSRuntime *rt, JSGCRootMapFun map, void *data)
{
GCRootMapArgs args;
uint32 rv;
args.map = map;
args.data = data;
JS_LOCK_GC(rt);
rv = JS_DHashTableEnumerate(&rt->gcRootsHash, js_gcroot_mapper, &args);
JS_UNLOCK_GC(rt);
return rv;
return JS_MapGCRoots(rt, map, data);
}
JS_PUBLIC_API(JSBool)
@ -2218,7 +2156,7 @@ JS_InitClass(JSContext *cx, JSObject *obj, JSObject *parent_proto,
return NULL;
/* After this point, control must exit via label bad or out. */
JS_PUSH_SINGLE_TEMP_ROOT(cx, proto, &tvr);
JS_PUSH_TEMP_ROOT_OBJECT(cx, proto, &tvr);
if (!constructor) {
/*

View File

@ -423,7 +423,7 @@ array_length_setter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
return JS_FALSE;
/* Protect iter against GC in OBJ_DELETE_PROPERTY. */
JS_PUSH_SINGLE_TEMP_ROOT(cx, iter, &tvr);
JS_PUSH_TEMP_ROOT_OBJECT(cx, iter, &tvr);
gap = oldlen - newlen;
for (;;) {
ok = JS_NextProperty(cx, iter, &id2);

View File

@ -465,6 +465,8 @@ typedef void
typedef union JSTempValueUnion {
jsval value;
JSObject *object;
JSString *string;
void *gcthing;
JSTempValueMarker marker;
jsval *array;
} JSTempValueUnion;
@ -479,24 +481,27 @@ JS_STATIC_ASSERT(sizeof(JSTempValueUnion) == sizeof(JSObject *));
/*
* Context-linked stack of temporary GC roots.
*
* If count is -1, then u.value contains the single value to root.
* If count is -1, then u.value contains the single value or GC-thing to root.
* If count is -2, then u.marker holds a mark hook that is executed to mark
* the values.
* If count >= 0, then u.array points to a stack-allocated vector of jsvals.
*
* To root a single GC-thing pointer, which need not be tagged and stored as a
* jsval, use JS_PUSH_SINGLE_TEMP_ROOT. The (jsval)(val) cast works because a
* GC-thing is aligned on a 0 mod 8 boundary, and object has the 0 jsval tag.
* So any GC-thing may be tagged as if it were an object and untagged, if it's
* then used only as an opaque pointer until discriminated by other means than
* tag bits (this is how the GC mark function uses its |thing| parameter -- it
* consults GC-thing flags stored separately from the thing to decide the type
* of thing).
* jsval, use JS_PUSH_TEMP_ROOT_GCTHING. The macro reinterprets an arbitrary
* GC-thing as jsval. It works because a GC-thing is aligned on a 0 mod 8
* boundary, and object has the 0 jsval tag. So any GC-thing may be tagged as
* if it were an object and untagged, if it's then used only as an opaque
* pointer until discriminated by other means than tag bits (this is how the
* GC mark function uses its |thing| parameter -- it consults GC-thing flags
* stored separately from the thing to decide the type of thing).
*
* Alternatively, if a single pointer to rooted JSObject * is required, use
* JS_PUSH_TEMP_ROOT_OBJECT(cx, NULL, &tvr). Then &tvr.u.object gives the
* necessary pointer, which puns tvr.u.value safely because object tag bits
* are all zeroes.
* JS_PUSH_TEMP_ROOT_OBJECT and JS_PUSH_TEMP_ROOT_STRING are type-safe
* alternatives to JS_PUSH_TEMP_ROOT_GCTHING for JSObject and JSString. They
* also provide a simple way to get a single pointer to rooted JSObject or
* JSString via JS_PUSH_TEMP_ROOT_(OBJECT|STRTING)(cx, NULL, &tvr). Then
* &tvr.u.object or tvr.u.string gives the necessary pointer, which puns
* tvr.u.value safely because JSObject * and JSString * are GC-things and, as
* such, their tag bits are all zeroes.
*
* If you need to protect a result value that flows out of a C function across
* several layers of other functions, use the js_LeaveLocalRootScopeWithResult
@ -517,31 +522,46 @@ struct JSTempValueRooter {
#define JS_PUSH_SINGLE_TEMP_ROOT(cx,val,tvr) \
JS_BEGIN_MACRO \
JS_PUSH_TEMP_ROOT_COMMON(cx, tvr); \
(tvr)->count = -1; \
(tvr)->u.value = (jsval)(val); \
(tvr)->u.value = val; \
JS_PUSH_TEMP_ROOT_COMMON(cx, tvr); \
JS_END_MACRO
#define JS_PUSH_TEMP_ROOT(cx,cnt,arr,tvr) \
JS_BEGIN_MACRO \
JS_PUSH_TEMP_ROOT_COMMON(cx, tvr); \
JS_ASSERT((ptrdiff_t)(cnt) >= 0); \
(tvr)->count = (ptrdiff_t)(cnt); \
(tvr)->u.array = (arr); \
JS_PUSH_TEMP_ROOT_COMMON(cx, tvr); \
JS_END_MACRO
#define JS_PUSH_TEMP_ROOT_MARKER(cx,marker_,tvr) \
JS_BEGIN_MACRO \
JS_PUSH_TEMP_ROOT_COMMON(cx, tvr); \
(tvr)->count = -2; \
(tvr)->u.marker = (marker_); \
JS_PUSH_TEMP_ROOT_COMMON(cx, tvr); \
JS_END_MACRO
#define JS_PUSH_TEMP_ROOT_OBJECT(cx,obj,tvr) \
JS_BEGIN_MACRO \
JS_PUSH_TEMP_ROOT_COMMON(cx, tvr); \
(tvr)->count = -1; \
(tvr)->u.object = (obj); \
JS_PUSH_TEMP_ROOT_COMMON(cx, tvr); \
JS_END_MACRO
#define JS_PUSH_TEMP_ROOT_STRING(cx,str,tvr) \
JS_BEGIN_MACRO \
(tvr)->count = -1; \
(tvr)->u.string = (str); \
JS_PUSH_TEMP_ROOT_COMMON(cx, tvr); \
JS_END_MACRO
#define JS_PUSH_TEMP_ROOT_GCTHING(cx,thing,tvr) \
JS_BEGIN_MACRO \
JS_ASSERT(JSVAL_IS_OBJECT((jsval)thing)); \
(tvr)->count = -1; \
(tvr)->u.gcthing = (thing); \
JS_PUSH_TEMP_ROOT_COMMON(cx, tvr); \
JS_END_MACRO
#define JS_POP_TEMP_ROOT(cx,tvr) \

View File

@ -1253,9 +1253,10 @@ out:
JSBool
js_ReportUncaughtException(JSContext *cx)
{
jsval exn, *vp;
jsval exn;
JSObject *exnObject;
void *mark;
jsval vp[5];
JSTempValueRooter tvr;
JSErrorReport *reportp, report;
JSString *str;
const char *bytes;
@ -1275,18 +1276,11 @@ js_ReportUncaughtException(JSContext *cx)
*/
if (JSVAL_IS_PRIMITIVE(exn)) {
exnObject = NULL;
vp = NULL;
#ifdef __GNUC__ /* suppress bogus gcc warnings */
mark = NULL;
#endif
} else {
exnObject = JSVAL_TO_OBJECT(exn);
vp = js_AllocStack(cx, 5, &mark);
if (!vp) {
ok = JS_FALSE;
goto out;
}
vp[0] = exn;
memset(vp + 1, 0, sizeof vp - sizeof vp[0]);
JS_PUSH_TEMP_ROOT(cx, JS_ARRAY_LENGTH(vp), vp, &tvr);
}
JS_ClearPendingException(cx);
@ -1297,7 +1291,7 @@ js_ReportUncaughtException(JSContext *cx)
if (!str) {
bytes = "unknown (can't convert to string)";
} else {
if (vp)
if (exnObject)
vp[1] = STRING_TO_JSVAL(str);
bytes = js_GetStringBytes(cx->runtime, str);
}
@ -1349,6 +1343,6 @@ js_ReportUncaughtException(JSContext *cx)
out:
if (exnObject)
js_FreeStack(cx, mark);
JS_POP_TEMP_ROOT(cx, &tvr);
return ok;
}

View File

@ -1260,7 +1260,7 @@ fun_xdrObject(JSXDRState *xdr, JSObject **objp)
}
/* From here on, control flow must flow through label out. */
JS_PUSH_SINGLE_TEMP_ROOT(cx, fun->object, &tvr);
JS_PUSH_TEMP_ROOT_OBJECT(cx, fun->object, &tvr);
ok = JS_TRUE;
if (!JS_XDRUint32(xdr, &nullAtom))
@ -2299,7 +2299,7 @@ js_ReportIsNotFunction(JSContext *cx, jsval *vp, uintN flags)
*vp,
NULL);
if (str) {
JS_PUSH_SINGLE_TEMP_ROOT(cx, str, &tvr);
JS_PUSH_TEMP_ROOT_STRING(cx, str, &tvr);
bytes = JS_GetStringBytes(str);
if (flags & JSV2F_ITERATOR) {
source = js_ValueToPrintableSource(cx, *vp);

View File

@ -591,6 +591,13 @@ js_ChangeExternalStringFinalizer(JSStringFinalizeOp oldop,
return -1;
}
/* This is compatible with JSDHashEntryStub. */
typedef struct JSGCRootHashEntry {
JSDHashEntryHdr hdr;
void *root;
const char *name;
} JSGCRootHashEntry;
/* Initial size of the gcRootsHash table (SWAG, small enough to amortize). */
#define GC_ROOTS_SIZE 256
#define GC_FINALIZE_LEN 1024
@ -705,19 +712,8 @@ js_DumpGCStats(JSRuntime *rt, FILE *fp)
#endif
#ifdef DEBUG
JS_STATIC_DLL_CALLBACK(JSDHashOperator)
js_root_printer(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 i, void *arg)
{
uint32 *leakedroots = (uint32 *)arg;
JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr;
(*leakedroots)++;
fprintf(stderr,
"JS engine warning: leaking GC root \'%s\' at %p\n",
rhe->name ? (char *)rhe->name : "", rhe->root);
return JS_DHASH_NEXT;
}
static void
CheckLeakedRoots(JSRuntime *rt);
#endif
void
@ -740,27 +736,8 @@ js_FinishGC(JSRuntime *rt)
if (rt->gcRootsHash.ops) {
#ifdef DEBUG
uint32 leakedroots = 0;
/* Warn (but don't assert) debug builds of any remaining roots. */
JS_DHashTableEnumerate(&rt->gcRootsHash, js_root_printer,
&leakedroots);
if (leakedroots > 0) {
if (leakedroots == 1) {
fprintf(stderr,
"JS engine warning: 1 GC root remains after destroying the JSRuntime.\n"
" This root may point to freed memory. Objects reachable\n"
" through it have not been finalized.\n");
} else {
fprintf(stderr,
"JS engine warning: %lu GC roots remain after destroying the JSRuntime.\n"
" These roots may point to freed memory. Objects reachable\n"
" through them have not been finalized.\n",
(unsigned long) leakedroots);
}
}
CheckLeakedRoots(rt);
#endif
JS_DHashTableFinish(&rt->gcRootsHash);
rt->gcRootsHash.ops = NULL;
}
@ -842,6 +819,122 @@ js_RemoveRoot(JSRuntime *rt, void *rp)
return JS_TRUE;
}
#ifdef DEBUG
JS_STATIC_DLL_CALLBACK(JSDHashOperator)
js_root_printer(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 i, void *arg)
{
uint32 *leakedroots = (uint32 *)arg;
JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr;
(*leakedroots)++;
fprintf(stderr,
"JS engine warning: leaking GC root \'%s\' at %p\n",
rhe->name ? (char *)rhe->name : "", rhe->root);
return JS_DHASH_NEXT;
}
static void
CheckLeakedRoots(JSRuntime *rt)
{
uint32 leakedroots = 0;
/* Warn (but don't assert) debug builds of any remaining roots. */
JS_DHashTableEnumerate(&rt->gcRootsHash, js_root_printer,
&leakedroots);
if (leakedroots > 0) {
if (leakedroots == 1) {
fprintf(stderr,
"JS engine warning: 1 GC root remains after destroying the JSRuntime.\n"
" This root may point to freed memory. Objects reachable\n"
" through it have not been finalized.\n");
} else {
fprintf(stderr,
"JS engine warning: %lu GC roots remain after destroying the JSRuntime.\n"
" These roots may point to freed memory. Objects reachable\n"
" through them have not been finalized.\n",
(unsigned long) leakedroots);
}
}
}
typedef struct NamedRootDumpArgs {
void (*dump)(const char *name, void *rp, void *data);
void *data;
} NamedRootDumpArgs;
JS_STATIC_DLL_CALLBACK(JSDHashOperator)
js_named_root_dumper(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number,
void *arg)
{
NamedRootDumpArgs *args = (NamedRootDumpArgs *) arg;
JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr;
if (rhe->name)
args->dump(rhe->name, rhe->root, args->data);
return JS_DHASH_NEXT;
}
void
js_DumpNamedRoots(JSRuntime *rt,
void (*dump)(const char *name, void *rp, void *data),
void *data)
{
NamedRootDumpArgs args;
args.dump = dump;
args.data = data;
JS_DHashTableEnumerate(&rt->gcRootsHash, js_named_root_dumper, &args);
}
#endif /* DEBUG */
typedef struct GCRootMapArgs {
JSGCRootMapFun map;
void *data;
} GCRootMapArgs;
JS_STATIC_DLL_CALLBACK(JSDHashOperator)
js_gcroot_mapper(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number,
void *arg)
{
GCRootMapArgs *args = (GCRootMapArgs *) arg;
JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr;
intN mapflags;
JSDHashOperator op;
mapflags = args->map(rhe->root, rhe->name, args->data);
#if JS_MAP_GCROOT_NEXT == JS_DHASH_NEXT && \
JS_MAP_GCROOT_STOP == JS_DHASH_STOP && \
JS_MAP_GCROOT_REMOVE == JS_DHASH_REMOVE
op = (JSDHashOperator)mapflags;
#else
op = JS_DHASH_NEXT;
if (mapflags & JS_MAP_GCROOT_STOP)
op |= JS_DHASH_STOP;
if (mapflags & JS_MAP_GCROOT_REMOVE)
op |= JS_DHASH_REMOVE;
#endif
return op;
}
uint32
js_MapGCRoots(JSRuntime *rt, JSGCRootMapFun map, void *data)
{
GCRootMapArgs args;
uint32 rv;
args.map = map;
args.data = data;
JS_LOCK_GC(rt);
rv = JS_DHashTableEnumerate(&rt->gcRootsHash, js_gcroot_mapper, &args);
JS_UNLOCK_GC(rt);
return rv;
}
JSBool
js_RegisterCloseableIterator(JSContext *cx, JSObject *obj)
{
@ -1523,6 +1616,13 @@ js_LockGCThing(JSContext *cx, void *thing)
#define GC_THING_IS_DEEP(t,o) (GC_TYPE_IS_DEEP(t) || IS_DEEP_STRING(t, o))
/* This is compatible with JSDHashEntryStub. */
typedef struct JSGCLockHashEntry {
JSDHashEntryHdr hdr;
const JSGCThing *thing;
uint32 count;
} JSGCLockHashEntry;
JSBool
js_LockGCThingRT(JSRuntime *rt, void *thing)
{

View File

@ -88,19 +88,6 @@ js_GetGCThingFlags(void *thing);
JSRuntime*
js_GetGCStringRuntime(JSString *str);
/* These are compatible with JSDHashEntryStub. */
struct JSGCRootHashEntry {
JSDHashEntryHdr hdr;
void *root;
const char *name;
};
struct JSGCLockHashEntry {
JSDHashEntryHdr hdr;
const JSGCThing *thing;
uint32 count;
};
#if 1
/*
* Since we're forcing a GC from JS_GC anyway, don't bother wasting cycles
@ -131,6 +118,16 @@ js_AddRootRT(JSRuntime *rt, void *rp, const char *name);
extern JSBool
js_RemoveRoot(JSRuntime *rt, void *rp);
#ifdef DEBUG
extern void
js_DumpNamedRoots(JSRuntime *rt,
void (*dump)(const char *name, void *rp, void *data),
void *data);
#endif
extern uint32
js_MapGCRoots(JSRuntime *rt, JSGCRootMapFun map, void *data);
/* Table of pointers with count valid members. */
typedef struct JSPtrTable {
size_t count;

View File

@ -526,7 +526,7 @@ js_GetScopeChain(JSContext *cx, JSStackFrame *fp)
obj = cursor;
if (!parent)
break;
JS_PUSH_SINGLE_TEMP_ROOT(cx, obj, &tvr);
JS_PUSH_TEMP_ROOT_OBJECT(cx, obj, &tvr);
} else {
/*
* Avoid OBJ_SET_PARENT overhead as clonedChild cannot escape to

View File

@ -2348,7 +2348,7 @@ js_NewObject(JSContext *cx, JSClass *clasp, JSObject *proto, JSObject *parent)
* GC calling JS_ClearNewbornRoots. There's also the possibilty of things
* happening under the objectHook call-out further below.
*/
JS_PUSH_SINGLE_TEMP_ROOT(cx, obj, &tvr);
JS_PUSH_TEMP_ROOT_OBJECT(cx, obj, &tvr);
/*
* Share proto's map only if it has the same JSObjectOps, and only if

View File

@ -91,8 +91,6 @@ typedef uint32 jsatomid;
typedef struct JSArgumentFormatMap JSArgumentFormatMap;
typedef struct JSCodeGenerator JSCodeGenerator;
typedef struct JSDependentString JSDependentString;
typedef struct JSGCLockHashEntry JSGCLockHashEntry;
typedef struct JSGCRootHashEntry JSGCRootHashEntry;
typedef struct JSGCThing JSGCThing;
typedef struct JSGenerator JSGenerator;
typedef struct JSParseNode JSParseNode;

View File

@ -4149,7 +4149,7 @@ js_NewRegExpObject(JSContext *cx, JSTokenStream *ts,
re = js_NewRegExp(cx, ts, str, flags, JS_FALSE);
if (!re)
return NULL;
JS_PUSH_SINGLE_TEMP_ROOT(cx, str, &tvr);
JS_PUSH_TEMP_ROOT_STRING(cx, str, &tvr);
obj = js_NewObject(cx, &js_RegExpClass, NULL, NULL);
if (!obj || !JS_SetPrivate(cx, obj, re)) {
js_DestroyRegExp(cx, re);

View File

@ -531,6 +531,7 @@ ReportCompileErrorNumber(JSContext *cx, void *handle, uintN flags,
uintN errorNumber, JSErrorReport *report,
JSBool charArgs, va_list ap)
{
JSTempValueRooter linetvr;
JSString *linestr = NULL;
JSTokenStream *ts = NULL;
JSCodeGenerator *cg = NULL;
@ -553,7 +554,7 @@ ReportCompileErrorNumber(JSContext *cx, void *handle, uintN flags,
return JS_FALSE;
}
js_AddRoot(cx, &linestr, "error line buffer");
JS_PUSH_TEMP_ROOT_STRING(cx, NULL, &linetvr);
switch (flags & JSREPORT_HANDLE) {
case JSREPORT_TS:
@ -588,6 +589,7 @@ ReportCompileErrorNumber(JSContext *cx, void *handle, uintN flags,
ts->linebuf.base,
jschar),
0);
linetvr.u.string = linestr;
report->linebuf = linestr
? JS_GetStringBytes(linestr)
: NULL;
@ -693,7 +695,7 @@ ReportCompileErrorNumber(JSContext *cx, void *handle, uintN flags,
if (report->ucmessage)
JS_free(cx, (void *)report->ucmessage);
js_RemoveRoot(cx->runtime, &linestr);
JS_POP_TEMP_ROOT(cx, &linetvr);
if (ts && !JSREPORT_IS_WARNING(flags)) {
/* Set the error flag to suppress spurious reports. */

View File

@ -3101,7 +3101,7 @@ ToAttributeName(JSContext *cx, jsval v)
if (!qn)
return NULL;
JS_PUSH_SINGLE_TEMP_ROOT(cx, qn, &tvr);
JS_PUSH_TEMP_ROOT_GCTHING(cx, qn, &tvr);
obj = js_GetAttributeNameObject(cx, qn);
JS_POP_TEMP_ROOT(cx, &tvr);
if (!obj)
@ -7630,7 +7630,7 @@ js_NewXMLObject(JSContext *cx, JSXMLClass xml_class)
xml = js_NewXML(cx, xml_class);
if (!xml)
return NULL;
JS_PUSH_SINGLE_TEMP_ROOT(cx, xml, &tvr);
JS_PUSH_TEMP_ROOT_GCTHING(cx, xml, &tvr);
obj = js_GetXMLObject(cx, xml);
JS_POP_TEMP_ROOT(cx, &tvr);
return obj;