From 9f4d6f4be2d145b6248ebeb78bf71a36482a09d5 Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Wed, 29 Sep 2010 19:42:22 -0700 Subject: [PATCH] Make functions per compartment, and deep copy instead of clone them if needed (584789, r=mrbkap). --- js/src/jsapi-tests/Makefile.in | 1 + js/src/jsapi-tests/testCloneScript.cpp | 50 ++++++++++++++++ js/src/jsapi.cpp | 1 + js/src/jscntxtinlines.h | 7 ++- js/src/jsfun.cpp | 49 +++++++++++++--- js/src/jsscript.cpp | 79 +++++++++++++++++++++++++- js/src/jsscript.h | 5 ++ 7 files changed, 181 insertions(+), 11 deletions(-) create mode 100644 js/src/jsapi-tests/testCloneScript.cpp diff --git a/js/src/jsapi-tests/Makefile.in b/js/src/jsapi-tests/Makefile.in index 9ce4138f65b3..014162a7fa29 100644 --- a/js/src/jsapi-tests/Makefile.in +++ b/js/src/jsapi-tests/Makefile.in @@ -50,6 +50,7 @@ CPPSRCS = \ tests.cpp \ selfTest.cpp \ testClassGetter.cpp \ + testCloneScript.cpp \ testConservativeGC.cpp \ testContexts.cpp \ testDebugger.cpp \ diff --git a/js/src/jsapi-tests/testCloneScript.cpp b/js/src/jsapi-tests/testCloneScript.cpp new file mode 100644 index 000000000000..690642a5adc4 --- /dev/null +++ b/js/src/jsapi-tests/testCloneScript.cpp @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sw=4 et tw=99: + * + * Test script cloning. + */ + +#include "tests.h" +#include "jsapi.h" + +BEGIN_TEST(test_cloneScript) +{ + JSObject *A, *B; + + CHECK(A = createGlobal()); + CHECK(B = createGlobal()); + + const char *source = + "var i = 0;\n" + "var sum = 0;\n" + "while (i < 10) {\n" + " sum += i;\n" + " ++i;\n" + "}\n" + "(sum);\n"; + + JSObject *obj; + + // compile for A + { + JSAutoEnterCompartment a; + if (!a.enter(cx, A)) + return false; + + JSFunction *fun; + CHECK(fun = JS_CompileFunction(cx, A, "f", 0, NULL, source, strlen(source), __FILE__, 1)); + CHECK(obj = JS_GetFunctionObject(fun)); + } + + // clone into B + { + JSAutoEnterCompartment b; + if (!b.enter(cx, B)) + return false; + + CHECK(JS_CloneFunctionObject(cx, obj, B)); + } + + return true; +} +END_TEST(test_cloneScript) diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 828c6929b7e3..2afd3c590af1 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4516,6 +4516,7 @@ JS_NewScriptObject(JSContext *cx, JSScript *script) * described in the comment for JSScript::u.object. */ JS_ASSERT(script->u.object); + JS_ASSERT(script != JSScript::emptyScript()); return script->u.object; } diff --git a/js/src/jscntxtinlines.h b/js/src/jscntxtinlines.h index c5134c05387e..ce0f177c3096 100644 --- a/js/src/jscntxtinlines.h +++ b/js/src/jscntxtinlines.h @@ -567,8 +567,11 @@ class CompartmentChecker } void check(JSScript *script) { - if (script && script->u.object) - check(script->u.object); + if (script && script != JSScript::emptyScript()) { + check(script->compartment); + if (script->u.object) + check(script->u.object); + } } void check(JSString *) { /* nothing for now */ } diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 0ff818461ada..ca34fd7664f2 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -2935,14 +2935,47 @@ js_CloneFunctionObject(JSContext *cx, JSFunction *fun, JSObject *parent, JS_ASSERT(parent); JS_ASSERT(proto); - /* - * The cloned function object does not need the extra JSFunction members - * beyond JSObject as it points to fun via the private slot. - */ - JSObject *clone = NewNativeClassInstance(cx, &js_FunctionClass, proto, parent); - if (!clone) - return NULL; - clone->setPrivate(fun); + JSObject *clone; + if (cx->compartment == fun->compartment()) { + /* + * The cloned function object does not need the extra JSFunction members + * beyond JSObject as it points to fun via the private slot. + */ + clone = NewNativeClassInstance(cx, &js_FunctionClass, proto, parent); + if (!clone) + return NULL; + clone->setPrivate(fun); + } else { + /* + * Across compartments we have to deep copy JSFunction and clone the + * script (for interpreted functions). + */ + clone = NewFunction(cx, parent); + if (!clone) + return NULL; + JSFunction *cfun = (JSFunction *) clone; + cfun->nargs = fun->nargs; + cfun->flags = fun->flags; + cfun->u = fun->getFunctionPrivate()->u; + cfun->atom = fun->atom; + clone->setPrivate(cfun); + if (cfun->isInterpreted()) { + JSScript *script = cfun->u.i.script; + JS_ASSERT(script); + JS_ASSERT(script->compartment == fun->compartment()); + JS_ASSERT(script->compartment != cx->compartment); + cfun->u.i.script = js_CloneScript(cx, script); + if (!cfun->u.i.script) + return NULL; + + if (cfun->u.i.script != JSScript::emptyScript()) { +#ifdef CHECK_SCRIPT_OWNER + cfun->u.i.script->owner = NULL; +#endif + js_CallNewScriptHook(cx, cfun->u.i.script, cfun); + } + } + } return clone; } diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 9ca7bf3fc796..8aacfd534bfd 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -86,7 +86,7 @@ static const jsbytecode emptyScriptCode[] = {JSOP_STOP, SRC_NULL}; false, /* debugMode */ #endif const_cast(emptyScriptCode), - {0, NULL}, NULL, 0, 0, 0, + {0, NULL}, NULL, NULL, 0, 0, 0, 0, /* nClosedArgs */ 0, /* nClosedVars */ NULL, {NULL}, @@ -1065,6 +1065,7 @@ JSScript::NewScript(JSContext *cx, uint32 length, uint32 nsrcnotes, uint32 natom nsrcnotes * sizeof(jssrcnote) == (uint8 *)script + size); + script->compartment = cx->compartment; #ifdef CHECK_SCRIPT_OWNER script->owner = cx->thread; #endif @@ -1641,6 +1642,82 @@ js_GetScriptLineExtent(JSScript *script) return 1 + lineno - script->lineno; } +class DisablePrincipalsTranscoding { + JSSecurityCallbacks *callbacks; + JSPrincipalsTranscoder temp; + + public: + DisablePrincipalsTranscoding(JSContext *cx) { + callbacks = JS_GetRuntimeSecurityCallbacks(cx->runtime); + if (callbacks) { + temp = callbacks->principalsTranscoder; + callbacks->principalsTranscoder = NULL; + } + } + + ~DisablePrincipalsTranscoding() { + if (callbacks) + callbacks->principalsTranscoder = temp; + } +}; + +JSScript * +js_CloneScript(JSContext *cx, JSScript *script) +{ + // don't clone the empty script + if (script == JSScript::emptyScript()) + return script; + + JS_ASSERT(cx->compartment != script->compartment); + JS_ASSERT(script->compartment); + + // serialize script + JSXDRState *w = JS_XDRNewMem(cx, JSXDR_ENCODE); + if (!w) + return NULL; + + // we don't want gecko to transcribe our principals for us + DisablePrincipalsTranscoding disable(cx); + + if (!JS_XDRScript(w, &script)) { + JS_XDRDestroy(w); + return NULL; + } + + uint32 nbytes; + void *p = JS_XDRMemGetData(w, &nbytes); + if (!p) { + JS_XDRDestroy(w); + return NULL; + } + + // de-serialize script + JSXDRState *r = JS_XDRNewMem(cx, JSXDR_DECODE); + if (!r) { + JS_XDRDestroy(w); + return NULL; + } + + // Hand p off from w to r. Don't want them to share the data + // mem, lest they both try to free it in JS_XDRDestroy + JS_XDRMemSetData(r, p, nbytes); + JS_XDRMemSetData(w, NULL, 0); + + // We can't use the public API because it makes a script object. + if (!js_XDRScript(r, &script, true, NULL)) + return NULL; + + JS_XDRDestroy(r); + JS_XDRDestroy(w); + + // set the proper principals for the script + script->principals = script->compartment->principals; + if (script->principals) + JSPRINCIPALS_HOLD(cx, script->principals); + + return script; +} + #ifdef JS_METHODJIT bool JSScript::isValidJitCode(void *jcode) diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 88321350d88a..8ea4a34fbfb1 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -250,6 +250,7 @@ struct JSScript { jsbytecode *main; /* main entry point, after predef'ing prolog */ JSAtomMap atomMap; /* maps immediate index to literal struct */ + JSCompartment *compartment; /* compartment the script was compiled for */ const char *filename; /* source filename or null */ uint32 lineno; /* base line number of script */ uint16 nslots; /* vars plus maximum stack depth */ @@ -277,6 +278,7 @@ struct JSScript { JSObject *object; JSScript *nextToGC; /* next to GC in rt->scriptsToGC list */ } u; + #ifdef CHECK_SCRIPT_OWNER JSThread *owner; /* for thread-safe life-cycle assertions */ #endif @@ -548,6 +550,9 @@ js_GetOpcode(JSContext *cx, JSScript *script, jsbytecode *pc) return op; } +extern JSScript * +js_CloneScript(JSContext *cx, JSScript *script); + /* * If magic is non-null, js_XDRScript succeeds on magic number mismatch but * returns false in *magic; it reflects a match via a true *magic out param.