Bug 779017 - Give every JSScript a ScriptSource. r=jorendorff
authorBenjamin Peterson <benjamin@python.org>
Tue, 31 Jul 2012 19:18:22 -0700
changeset 101059 e6a683016c735546d6cb1f2d2fcc42e2087d524e
parent 101058 f6ebe752f7cd60e041b2a9390a6c9e18a7d6ccf4
child 101060 6e78bc0145ee413876a0ee1da79bc3140e155a8a
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersjorendorff
bugs779017
milestone17.0a1
Bug 779017 - Give every JSScript a ScriptSource. r=jorendorff
js/src/frontend/BytecodeCompiler.cpp
js/src/jsfun.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/vm/GlobalObject.cpp
js/src/vm/Xdr.h
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -18,20 +18,20 @@
 
 #include "frontend/TreeContext-inl.h"
 
 using namespace js;
 using namespace js::frontend;
 
 class AutoAttachToRuntime {
     JSRuntime *rt;
+    ScriptSource *ss;
   public:
-    ScriptSource *ss;
-    AutoAttachToRuntime(JSRuntime *rt)
-      : rt(rt), ss(NULL) {}
+    AutoAttachToRuntime(JSRuntime *rt, ScriptSource *ss)
+      : rt(rt), ss(ss) {}
     ~AutoAttachToRuntime() {
         // This makes the source visible to the GC. If compilation fails, and no
         // script refers to it, it will be collected.
         if (ss)
             ss->attachToRuntime(rt);
     }
 };
 
@@ -74,24 +74,24 @@ frontend::CompileScript(JSContext *cx, H
      * The scripted callerFrame can only be given for compile-and-go scripts
      * and non-zero static level requires callerFrame.
      */
     JS_ASSERT_IF(callerFrame, options.compileAndGo);
     JS_ASSERT_IF(staticLevel != 0, callerFrame);
 
     if (!CheckLength(cx, length))
         return NULL;
-    AutoAttachToRuntime attacher(cx->runtime);
+    ScriptSource *ss = cx->new_<ScriptSource>();
+    if (!ss)
+        return NULL;
+    AutoAttachToRuntime attacher(cx->runtime, ss);
     SourceCompressionToken sct(cx);
-    ScriptSource *ss = NULL;
     if (!cx->hasRunOption(JSOPTION_ONLY_CNG_SOURCE) || options.compileAndGo) {
-        ss = ScriptSource::createFromSource(cx, chars, length, false, &sct);
-        if (!ss)
+        if (!ss->setSource(cx, chars, length, false, &sct))
             return NULL;
-        attacher.ss = ss;
     }
 
     Parser parser(cx, options, chars, length, /* foldConstants = */ true);
     if (!parser.init())
         return NULL;
     parser.sct = &sct;
 
     SharedContext sc(cx, scopeChain, /* fun = */ NULL, /* funbox = */ NULL, StrictModeFromContext(cx));
@@ -239,23 +239,24 @@ frontend::CompileScript(JSContext *cx, H
 
 // Compile a JS function body, which might appear as the value of an event
 // handler attribute in an HTML <INPUT> tag, or in a Function() constructor.
 bool
 frontend::CompileFunctionBody(JSContext *cx, HandleFunction fun, CompileOptions options,
                               Bindings *bindings, const jschar *chars, size_t length)
 {
     if (!CheckLength(cx, length))
-        return false;
-    AutoAttachToRuntime attacher(cx->runtime);
-    SourceCompressionToken sct(cx);
-    ScriptSource *ss = ScriptSource::createFromSource(cx, chars, length, true, &sct);
+        return NULL;
+    ScriptSource *ss = cx->new_<ScriptSource>();
     if (!ss)
         return NULL;
-    attacher.ss = ss;
+    AutoAttachToRuntime attacher(cx->runtime, ss);
+    SourceCompressionToken sct(cx);
+    if (!ss->setSource(cx, chars, length, true, &sct))
+        return NULL;
 
     options.setCompileAndGo(false);
     Parser parser(cx, options, chars, length, /* foldConstants = */ true);
     if (!parser.init())
         return false;
     parser.sct = &sct;
 
     JS_ASSERT(fun);
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -601,18 +601,21 @@ js::FunctionToString(JSContext *cx, Hand
         if (!out.append("function "))
             return NULL;
         if (fun->atom) {
             if (!out.append(fun->atom))
                 return NULL;
         }
     }
     bool haveSource = fun->isInterpreted();
-    if (haveSource && !fun->script()->scriptSource() && !fun->script()->loadSource(cx, &haveSource))
-            return NULL;
+    if (haveSource && !fun->script()->scriptSource()->hasSourceData() &&
+        !fun->script()->loadSource(cx, &haveSource))
+    {
+        return NULL;
+    }
     if (haveSource) {
         RootedScript script(cx, fun->script());
         RootedString src(cx, fun->script()->sourceData(cx));
         if (!src)
             return NULL;
         const jschar *chars = src->getChars(cx);
         if (!chars)
             return NULL;
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -353,18 +353,18 @@ js::XDRScript(XDRState<mode> *xdr, Handl
         ContainsDynamicNameAccess,
         FunHasExtensibleScope,
         ArgumentsHasVarBinding,
         NeedsArgsObj,
         OwnFilename,
         ParentFilename,
         IsGenerator,
         IsGeneratorExp,
-        HaveSource,
         OwnSource,
+        HasSourceData,
         ExplicitUseStrict
     };
 
     uint32_t length, lineno, nslots;
     uint32_t natoms, nsrcnotes, ntrynotes, nobjects, nregexps, nconsts, nClosedArgs, nClosedVars, i;
     uint32_t prologLength, version;
     uint32_t nTypeSets = 0;
     uint32_t scriptBits = 0;
@@ -514,20 +514,20 @@ js::XDRScript(XDRState<mode> *xdr, Handl
             scriptBits |= (1 << ArgumentsHasVarBinding);
         if (script->analyzedArgsUsage() && script->needsArgsObj())
             scriptBits |= (1 << NeedsArgsObj);
         if (script->filename) {
             scriptBits |= (enclosingScript && enclosingScript->filename == script->filename)
                           ? (1 << ParentFilename)
                           : (1 << OwnFilename);
         }
-        if (script->scriptSource()) {
-            scriptBits |= (1 << HaveSource);
-            if (!enclosingScript || enclosingScript->scriptSource() != script->scriptSource())
-                scriptBits |= (1 << OwnSource);
+        if (!enclosingScript || enclosingScript->scriptSource() != script->scriptSource()) {
+            scriptBits |= (1 << OwnSource);
+            if (script->scriptSource()->hasSourceData())
+                scriptBits |= (1 << HasSourceData);
         }
         if (script->isGenerator)
             scriptBits |= (1 << IsGenerator);
         if (script->isGeneratorExp)
             scriptBits |= (1 << IsGeneratorExp);
 
         JS_ASSERT(!script->compileAndGo);
         JS_ASSERT(!script->hasSingletons);
@@ -568,18 +568,29 @@ js::XDRScript(XDRState<mode> *xdr, Handl
         JSVersion version_ = JSVersion(version & JS_BITMASK(16));
         JS_ASSERT((version_ & VersionFlags::FULL_MASK) == unsigned(version_));
 
         // principals and originPrincipals are set with xdr->initScriptPrincipals(script) below.
         // staticLevel is set below.
         CompileOptions options(cx);
         options.setVersion(version_)
                .setNoScriptRval(!!(scriptBits & (1 << NoScriptRval)));
+        ScriptSource *ss;
+        if (scriptBits & (1 << OwnSource)) {
+            ss = cx->new_<ScriptSource>();
+            if (!ss)
+                return NULL;
+        } else {
+            JS_ASSERT(enclosingScript);
+            ss = enclosingScript->scriptSource();
+        }
         script = JSScript::Create(cx, enclosingScope, !!(scriptBits & (1 << SavedCallerFun)),
-                                  options, /* staticLevel = */ 0, NULL, 0, 0);
+                                  options, /* staticLevel = */ 0, ss, 0, 0);
+        if (scriptBits & (1 << OwnSource))
+            ss->attachToRuntime(cx->runtime);
         if (!script || !JSScript::partiallyInit(cx, script,
                                                 length, nsrcnotes, natoms, nobjects,
                                                 nregexps, ntrynotes, nconsts, nClosedArgs,
                                                 nClosedVars, nTypeSets))
             return JS_FALSE;
 
         script->bindings.transfer(&bindings);
         JS_ASSERT(!script->mainOffset);
@@ -629,30 +640,20 @@ js::XDRScript(XDRState<mode> *xdr, Handl
                 return false;
         }
     } else if (scriptBits & (1 << ParentFilename)) {
         JS_ASSERT(enclosingScript);
         if (mode == XDR_DECODE)
             script->filename = enclosingScript->filename;
     }
 
-    if (scriptBits & (1 << HaveSource)) {
-        ScriptSource *ss = script->scriptSource();
-        if (scriptBits & (1 << OwnSource)) {
-            if (!ScriptSource::performXDR<mode>(xdr, &ss))
-                return false;
-        } else {
-            JS_ASSERT(enclosingScript);
-            ss = enclosingScript->scriptSource();
-        }
-        if (mode == XDR_DECODE)
-            script->setScriptSource(cx, ss);
-    } else if (mode == XDR_DECODE) {
-        script->setScriptSource(cx, NULL);
-        JS_ASSERT_IF(enclosingScript, !enclosingScript->scriptSource());
+    if (scriptBits & (1 << HasSourceData)) {
+        JS_ASSERT(scriptBits & (1 << OwnSource));
+        if (!script->scriptSource()->performXDR<mode>(xdr))
+            return false;
     }
     if (!xdr->codeUint32(&script->sourceStart))
         return false;
     if (!xdr->codeUint32(&script->sourceEnd))
         return false;
 
     if (mode == XDR_DECODE) {
         script->lineno = lineno;
@@ -1122,52 +1123,48 @@ SourceCompressorThread::abort(SourceComp
     JS_ASSERT(userTok == tok);
     stop = true;
 }
 #endif /* JS_THREADSAFE */
 
 void
 JSScript::setScriptSource(JSContext *cx, ScriptSource *ss)
 {
+    JS_ASSERT(ss);
 #ifdef JSGC_INCREMENTAL
     // During IGC, we need to barrier writing to scriptSource_.
-    if (ss && cx->runtime->gcIncrementalState != NO_INCREMENTAL && cx->runtime->gcIsFull)
+    if (cx->runtime->gcIncrementalState != NO_INCREMENTAL && cx->runtime->gcIsFull)
         ss->mark();
 #endif
     scriptSource_ = ss;
 }
 
 bool
 JSScript::loadSource(JSContext *cx, bool *worked)
 {
-    JS_ASSERT(!scriptSource_);
+    JS_ASSERT(!scriptSource_->hasSourceData());
     *worked = false;
     if (!cx->runtime->sourceHook)
         return true;
     jschar *src = NULL;
     uint32_t length;
     if (!cx->runtime->sourceHook(cx, this, &src, &length))
         return false;
     if (!src)
         return true;
-    ScriptSource *ss = ScriptSource::createFromSource(cx, src, length, false, NULL, true);
-    if (!ss) {
-        cx->free_(src);
-        return false;
-    }
-    setScriptSource(cx, ss);
-    ss->attachToRuntime(cx->runtime);
+    ScriptSource *ss = scriptSource();
+    JS_ALWAYS_TRUE(ss->setSource(cx, src, length, false, NULL, true));
     *worked = true;
     return true;
 }
 
 JSFixedString *
 JSScript::sourceData(JSContext *cx)
 {
-    JS_ASSERT(scriptSource_);
+    JS_ASSERT(scriptSource_->hasSourceData());
     return scriptSource_->substring(cx, sourceStart, sourceEnd);
 }
 
 JSFixedString *
 SourceDataCache::lookup(ScriptSource *ss)
 {
     if (!map_)
         return NULL;
@@ -1233,61 +1230,54 @@ ScriptSource::substring(JSContext *cx, u
         chars = data.source;
     }
 #else
     chars = data.source;
 #endif
     return js_NewStringCopyN(cx, chars + start, stop - start);
 }
 
-ScriptSource *
-ScriptSource::createFromSource(JSContext *cx, const jschar *src, uint32_t length,
-                               bool argumentsNotIncluded, SourceCompressionToken *tok,
-                               bool ownSource)
+bool
+ScriptSource::setSource(JSContext *cx, const jschar *src, uint32_t length,
+                        bool argumentsNotIncluded, SourceCompressionToken *tok,
+                        bool ownSource)
 {
-    ScriptSource *ss = static_cast<ScriptSource *>(cx->runtime->malloc_(sizeof(*ss)));
-    if (!ss)
-        return NULL;
+    JS_ASSERT(!hasSourceData());
     if (!ownSource) {
         const size_t nbytes = length * sizeof(jschar);
-        ss->data.compressed = static_cast<unsigned char *>(cx->runtime->malloc_(nbytes));
-        if (!ss->data.compressed) {
-            cx->free_(ss);
-            return NULL;
-        }
+        data.compressed = static_cast<unsigned char *>(cx->malloc_(nbytes));
+        if (!data.compressed)
+            return false;
     }
-    ss->next = NULL;
-    ss->length_ = length;
-    ss->compressedLength_ = 0;
-    ss->marked = ss->onRuntime_ = false;
-    ss->argumentsNotIncluded_ = argumentsNotIncluded;
-#ifdef DEBUG
-    ss->ready_ = false;
-#endif
+    length_ = length;
+    argumentsNotIncluded_ = argumentsNotIncluded;
 
     JS_ASSERT_IF(ownSource, !tok);
 
 #ifdef JS_THREADSAFE
     if (tok && !ownSource) {
-        tok->ss = ss;
+#ifdef DEBUG
+        ready_ = false;  
+#endif
+        tok->ss = this;
         tok->chars = src;
         cx->runtime->sourceCompressorThread.compress(tok);
     } else
 #endif
     {
         if (ownSource)
-            ss->data.source = const_cast<jschar *>(src);
+            data.source = const_cast<jschar *>(src);
         else
-            PodCopy(ss->data.source, src, ss->length_);
+            PodCopy(data.source, src, length_);
 #ifdef DEBUG
-        ss->ready_ = true;
+        ready_ = true;
 #endif
     }
 
-    return ss;
+    return true;
 }
 
 void
 SourceCompressionToken::ensureReady()
 {
 #ifdef JS_THREADSAFE
     cx->runtime->sourceCompressorThread.waitOnCompression(this);
 #endif
@@ -1350,79 +1340,60 @@ ScriptSource::sweep(JSRuntime *rt)
             *prev = next;
             cur->destroy(rt);
         }
     }
 }
 
 template<XDRMode mode>
 bool
-ScriptSource::performXDR(XDRState<mode> *xdr, ScriptSource **ssp)
+ScriptSource::performXDR(XDRState<mode> *xdr)
 {
-    class Cleanup {
-        JSContext *cx;
-        ScriptSource *ss;
-      public:
-        explicit Cleanup(JSContext *cx)
-            : cx(cx), ss(NULL) {}
-        ~Cleanup()
-        {
-            if (ss) {
-                if (ss->data.compressed)
-                    cx->free_(ss->data.compressed);
-                cx->free_(ss);
-            }
-        }
-        void protect(ScriptSource *source) { ss = source; }
-        void release() { ss = NULL; }
-    } cleanup(xdr->cx());
-    ScriptSource *ss = *ssp;
-    if (mode == XDR_DECODE) {
-        *ssp = static_cast<ScriptSource *>(xdr->cx()->malloc_(sizeof(ScriptSource)));
-        ss = *ssp;
-        if (!ss)
-            return false;
-        ss->marked = ss->onRuntime_ = ss->argumentsNotIncluded_ = false;
-#ifdef DEBUG
-        ss->ready_ = false;
-#endif
-        ss->data.compressed = NULL;
-        cleanup.protect(ss);
-#ifdef JSGC_INCREMENTAL
-        // See comment in ScriptSource::createFromSource.
-        if (xdr->cx()->runtime->gcIncrementalState != NO_INCREMENTAL &&
-            xdr->cx()->runtime->gcIsFull)
-            ss->marked = true;
-#endif
-    }
-    if (!xdr->codeUint32(&ss->length_))
-        return false;
-    if (!xdr->codeUint32(&ss->compressedLength_))
+    uint8_t hasSource = hasSourceData();
+    if (!xdr->codeUint8(&hasSource))
         return false;
 
-    uint8_t argumentsNotIncluded = ss->argumentsNotIncluded_;
-    if (!xdr->codeUint8(&argumentsNotIncluded))
-        return false;
-    ss->argumentsNotIncluded_ = argumentsNotIncluded;
+    if (hasSource) {
+        // Only set members when we know decoding cannot fail. This prevents the
+        // script source from being partially initialized.
+        uint32_t length = length_;
+        if (!xdr->codeUint32(&length))
+            return false;
 
-    size_t byteLen = ss->compressed() ? ss->compressedLength_ : (ss->length_ * sizeof(jschar));
-    if (mode == XDR_DECODE) {
-        ss->data.compressed = static_cast<unsigned char *>(xdr->cx()->malloc_(byteLen));
-        if (!ss->data.compressed)
+        uint32_t compressedLength = compressedLength_;
+        if (!xdr->codeUint32(&compressedLength))
+            return false;
+
+        uint8_t argumentsNotIncluded = argumentsNotIncluded_;
+        if (!xdr->codeUint8(&argumentsNotIncluded))
             return false;
+
+        size_t byteLen = compressedLength ? compressedLength : (length * sizeof(jschar));
+        if (mode == XDR_DECODE) {
+            data.compressed = static_cast<unsigned char *>(xdr->cx()->malloc_(byteLen));
+            if (!data.compressed)
+                return false;
+        }
+        if (!xdr->codeBytes(data.compressed, byteLen)) {
+            if (mode == XDR_DECODE) {
+                xdr->cx()->free_(data.compressed);
+                data.compressed = NULL;
+            }
+            return false;
+        }
+        length_ = length;
+        compressedLength_ = compressedLength;
+        argumentsNotIncluded_ = argumentsNotIncluded;
     }
-    if (!xdr->codeBytes(ss->data.compressed, byteLen))
-        return false;
-    if (mode == XDR_DECODE) {
+
 #ifdef DEBUG
-        ss->ready_ = true;
+    if (mode == XDR_DECODE)
+        ready_ = true;
 #endif
-        ss->attachToRuntime(xdr->cx()->runtime);
-        cleanup.release();
-    }
+
     return true;
 }
 
 /*
  * Shared script filename management.
  */
 
 const char *
@@ -2632,17 +2603,17 @@ JSScript::markChildren(JSTracer *trc)
 
     if (enclosingScope_)
         MarkObject(trc, &enclosingScope_, "enclosing");
 
     if (IS_GC_MARKING_TRACER(trc)) {
         if (filename)
             MarkScriptFilename(trc->runtime, filename);
 
-        if (trc->runtime->gcIsFull && scriptSource_)
+        if (trc->runtime->gcIsFull)
             scriptSource_->mark();
     }
 
     bindings.trace(trc);
 
 #ifdef JS_METHODJIT
     for (int constructing = 0; constructing <= 1; constructing++) {
         for (int barriers = 0; barriers <= 1; barriers++) {
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -992,42 +992,62 @@ struct ScriptSource
     bool marked:1;
     bool onRuntime_:1;
     bool argumentsNotIncluded_:1;
 #ifdef DEBUG
     bool ready_:1;
 #endif
 
   public:
-    static ScriptSource *createFromSource(JSContext *cx,
-                                          const jschar *src,
-                                          uint32_t length,
-                                          bool argumentsNotIncluded = false,
-                                          SourceCompressionToken *tok = NULL,
-                                          bool ownSource = false);
+    ScriptSource()
+      : next(NULL),
+        length_(0),
+        compressedLength_(0),
+        marked(false),
+        onRuntime_(false),
+        argumentsNotIncluded_(false)
+#ifdef DEBUG
+       ,ready_(true)
+#endif
+    {
+        data.source = NULL;
+    }
+    bool setSource(JSContext *cx,
+                   const jschar *src,
+                   uint32_t length,
+                   bool argumentsNotIncluded = false,
+                   SourceCompressionToken *tok = NULL,
+                   bool ownSource = false);
     void attachToRuntime(JSRuntime *rt);
     void mark() { marked = true; }
-    void destroy(JSRuntime *rt);
-    uint32_t length() const { return length_; }
     bool onRuntime() const { return onRuntime_; }
-    bool argumentsNotIncluded() const { return argumentsNotIncluded_; }
 #ifdef DEBUG
     bool ready() const { return ready_; }
 #endif
+    bool hasSourceData() const { return !!data.source; }
+    uint32_t length() const {
+        JS_ASSERT(hasSourceData());
+        return length_;
+    }
+    bool argumentsNotIncluded() const {
+        JS_ASSERT(hasSourceData());
+        return argumentsNotIncluded_;
+    }
     JSFixedString *substring(JSContext *cx, uint32_t start, uint32_t stop);
     size_t sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf);
 
     // For the GC.
     static void sweep(JSRuntime *rt);
 
     // XDR handling
     template <XDRMode mode>
-    static bool performXDR(XDRState<mode> *xdr, ScriptSource **ss);
+    bool performXDR(XDRState<mode> *xdr);
 
   private:
+    void destroy(JSRuntime *rt);
     bool compressed() { return compressedLength_ != 0; }
 };
 
 #ifdef JS_THREADSAFE
 /*
  * Background thread to compress JS source code. This happens only while parsing
  * and bytecode generation is happening in the main thread. If needed, the
  * compiler waits for compression to complete before returning.
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -233,20 +233,22 @@ GlobalObject::initFunctionAndObjectClass
         JS_ASSERT(proto == functionProto);
         functionProto->flags |= JSFUN_PROTOTYPE;
 
         const char *rawSource = "() {\n}";
         size_t sourceLen = strlen(rawSource);
         jschar *source = InflateString(cx, rawSource, &sourceLen);
         if (!source)
             return NULL;
-        ScriptSource *ss = ScriptSource::createFromSource(cx, source, sourceLen);
-        cx->free_(source);
-        if (!ss)
+        ScriptSource *ss = cx->new_<ScriptSource>();
+        if (!ss) {
+            cx->free_(source);
             return NULL;
+        }
+        JS_ALWAYS_TRUE(ss->setSource(cx, source, sourceLen, false, NULL, true));
 
         CompileOptions options(cx);
         options.setNoScriptRval(true)
                .setVersion(JSVERSION_DEFAULT);
         Rooted<JSScript*> script(cx, JSScript::Create(cx,
                                                       /* enclosingScope = */ NullPtr(),
                                                       /* savedCallerFun = */ false,
                                                       options,
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -20,17 +20,17 @@ namespace js {
  * Bytecode version number. Increment the subtrahend whenever JS bytecode
  * changes incompatibly.
  *
  * This version number is XDR'd near the front of xdr bytecode and
  * aborts deserialization if there is a mismatch between the current
  * and saved versions. If deserialization fails, the data should be
  * invalidated if possible.
  */
-static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 125);
+static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 126);
 
 class XDRBuffer {
   public:
     XDRBuffer(JSContext *cx)
       : context(cx), base(NULL), cursor(NULL), limit(NULL) { }
 
     JSContext *cx() const {
         return context;