Bug 438633: Give new JSScript objects lifetimes like GCThings. r=brendan

Attach script objects immediately in all JSAPI script-creating functions;
have JS_NewScriptObject simply return the already-allocated object; and
make JS_DestroyScript a no-op.

Verify that all scripts given to JSAPI script-consuming functions have
objects, or are the canonical empty script object.
This commit is contained in:
Jim Blandy 2010-08-20 13:11:05 -07:00
parent 16f4c21b3c
commit 285b03968f
5 changed files with 101 additions and 25 deletions

View File

@ -4414,6 +4414,10 @@ JS_CompileUCScriptForPrincipals(JSContext *cx, JSObject *obj, JSPrincipals *prin
uint32 tcflags = JS_OPTIONS_TO_TCFLAGS(cx) | TCF_NEED_MUTABLE_SCRIPT;
JSScript *script = Compiler::compileScript(cx, obj, NULL, principals, tcflags,
chars, length, NULL, filename, lineno);
if (script && !js_NewScriptObject(cx, script)) {
js_DestroyScript(cx, script);
script = NULL;
}
LAST_FRAME_CHECKS(cx, script);
return script;
}
@ -4514,6 +4518,10 @@ JS_CompileFile(JSContext *cx, JSObject *obj, const char *filename)
NULL, 0, fp, filename, 1);
if (fp != stdin)
fclose(fp);
if (script && !js_NewScriptObject(cx, script)) {
js_DestroyScript(cx, script);
script = NULL;
}
LAST_FRAME_CHECKS(cx, script);
return script;
}
@ -4530,6 +4538,10 @@ JS_CompileFileHandleForPrincipals(JSContext *cx, JSObject *obj, const char *file
tcflags = JS_OPTIONS_TO_TCFLAGS(cx) | TCF_NEED_MUTABLE_SCRIPT;
script = Compiler::compileScript(cx, obj, NULL, principals, tcflags,
NULL, 0, file, filename, 1);
if (script && !js_NewScriptObject(cx, script)) {
js_DestroyScript(cx, script);
script = NULL;
}
LAST_FRAME_CHECKS(cx, script);
return script;
}
@ -4543,34 +4555,29 @@ JS_CompileFileHandle(JSContext *cx, JSObject *obj, const char *filename, FILE *f
JS_PUBLIC_API(JSObject *)
JS_NewScriptObject(JSContext *cx, JSScript *script)
{
JSObject *obj;
CHECK_REQUEST(cx);
assertSameCompartment(cx, script);
if (!script)
return NewNonFunction<WithProto::Class>(cx, &js_ScriptClass, NULL, NULL);
JS_ASSERT(!script->u.object);
{
AutoScriptRooter root(cx, script);
obj = NewNonFunction<WithProto::Class>(cx, &js_ScriptClass, NULL, NULL);
if (obj) {
obj->setPrivate(script);
script->u.object = obj;
#ifdef CHECK_SCRIPT_OWNER
script->owner = NULL;
#endif
}
}
return obj;
/*
* This function should only ever be applied to JSScripts that had
* script objects allocated for them when they were created, as
* described in the comment for JSScript::u.object.
*/
JS_ASSERT(script->u.object);
return script->u.object;
}
JS_PUBLIC_API(JSObject *)
JS_GetScriptObject(JSScript *script)
{
/*
* This function should only ever be applied to JSScripts that had
* script objects allocated for them when they were created, as
* described in the comment for JSScript::u.object.
*/
JS_ASSERT(script->u.object);
return script->u.object;
}
@ -4578,8 +4585,17 @@ JS_PUBLIC_API(void)
JS_DestroyScript(JSContext *cx, JSScript *script)
{
CHECK_REQUEST(cx);
assertSameCompartment(cx, script);
js_DestroyScript(cx, script);
/*
* Originally, JSScript lifetimes were managed explicitly, and this function
* was used to free a JSScript. Now, this function does nothing, and the
* garbage collector manages JSScripts; you must root the JSScript's script
* object (obtained via JS_GetScriptObject) to keep it alive.
*
* However, since the script objects have taken over this responsibility, it
* follows that every script passed here must have a script object.
*/
JS_ASSERT(script->u.object);
}
JS_PUBLIC_API(JSFunction *)
@ -4741,6 +4757,8 @@ JS_ExecuteScript(JSContext *cx, JSObject *obj, JSScript *script, jsval *rval)
CHECK_REQUEST(cx);
assertSameCompartment(cx, obj, script);
/* This should receive only scripts handed out via the JSAPI. */
JS_ASSERT(script == JSScript::emptyScript() || script->u.object);
ok = Execute(cx, obj, script, NULL, 0, Valueify(rval));
LAST_FRAME_CHECKS(cx, ok);
return ok;
@ -4768,7 +4786,7 @@ JS_EvaluateUCScriptForPrincipals(JSContext *cx, JSObject *obj,
}
ok = Execute(cx, obj, script, NULL, 0, Valueify(rval));
LAST_FRAME_CHECKS(cx, ok);
JS_DestroyScript(cx, script);
js_DestroyScript(cx, script);
return ok;
}

View File

@ -6081,8 +6081,11 @@ JSObject::getCompartment(JSContext *cx)
return cx->runtime->defaultCompartment;
}
// Compile-time Function, Block, and RegExp objects are not parented.
if (clasp == &js_FunctionClass || clasp == &js_BlockClass || clasp == &js_RegExpClass) {
/*
* Script objects and compile-time Function, Block, RegExp objects
* are not parented.
*/
if (clasp == &js_FunctionClass || clasp == &js_BlockClass || clasp == &js_RegExpClass || clasp == &js_ScriptClass) {
// This is a bogus answer, but it'll do for now.
return cx->runtime->defaultCompartment;
}

View File

@ -1277,6 +1277,33 @@ js_TraceScript(JSTracer *trc, JSScript *script)
js_MarkScriptFilename(script->filename);
}
JSBool
js_NewScriptObject(JSContext *cx, JSScript *script)
{
AutoScriptRooter root(cx, script);
JS_ASSERT(!script->u.object);
JS_ASSERT(script != JSScript::emptyScript());
JSObject *obj = NewNonFunction<WithProto::Class>(cx, &js_ScriptClass, NULL, NULL);
if (!obj)
return JS_FALSE;
obj->setPrivate(script);
script->u.object = obj;
/*
* Clear the object's proto, to avoid entraining stuff. Once we no longer use the parent
* for security checks, then we can clear the parent, too.
*/
obj->clearProto();
#ifdef CHECK_SCRIPT_OWNER
script->owner = NULL;
#endif
return JS_TRUE;
}
typedef struct GSNCacheEntry {
JSDHashEntryHdr hdr;
jsbytecode *pc;

View File

@ -184,7 +184,23 @@ struct JSScript {
uint16 staticLevel;/* static level for display maintenance */
JSPrincipals *principals;/* principals for this script */
union {
JSObject *object; /* optional Script-class object wrapper */
/*
* A script object of class js_ScriptClass, to ensure the script is GC'd.
* - All scripts returned by JSAPI functions (JS_CompileScript,
* JS_CompileFile, etc.) have these objects.
* - Function scripts never have script objects; such scripts are owned
* by their function objects.
* - Temporary scripts created by obj_eval, JS_EvaluateScript, and
* similar functions never have these objects; such scripts are
* explicitly destroyed by the code that created them.
* Debugging API functions (JSDebugHooks::newScriptHook;
* JS_GetFunctionScript) may reveal sans-script-object Function and
* temporary scripts to clients, but clients must never call
* JS_NewScriptObject on such scripts: doing so would double-free them,
* once from the explicit call to js_DestroyScript, and once when the
* script object is garbage collected.
*/
JSObject *object;
JSScript *nextToGC; /* next to GC in rt->scriptsToGC list */
} u;
#ifdef CHECK_SCRIPT_OWNER
@ -375,6 +391,9 @@ js_DestroyScript(JSContext *cx, JSScript *script);
extern void
js_TraceScript(JSTracer *trc, JSScript *script);
extern JSBool
js_NewScriptObject(JSContext *cx, JSScript *script);
/*
* To perturb as little code as possible, we introduce a js_GetSrcNote lookup
* cache without adding an explicit cx parameter. Thus js_GetSrcNote becomes

View File

@ -671,8 +671,17 @@ JS_XDRScript(JSXDRState *xdr, JSScript **scriptp)
{
if (!js_XDRScript(xdr, scriptp, true, NULL))
return JS_FALSE;
if (xdr->mode == JSXDR_DECODE)
if (xdr->mode == JSXDR_DECODE) {
js_CallNewScriptHook(xdr->cx, *scriptp, NULL);
if (*scriptp != JSScript::emptyScript() &&
!js_NewScriptObject(xdr->cx, *scriptp)) {
js_DestroyScript(xdr->cx, *scriptp);
*scriptp = NULL;
return JS_FALSE;
}
}
return JS_TRUE;
}