Bug 761723 - Add a runtime hook to retrieve source that wasn't saved. r=luke
authorBenjamin Peterson <benjamin@python.org>
Fri, 20 Jul 2012 20:19:17 +0200
changeset 99943 1abd39543f58a66ac3b0b55dcd39d52748f4f45b
parent 99942 3bacbbca7d87f9cf44c50584d53834513d55a2bb
child 99944 166ee51a633f6ea71ddad259aa2746bafe3ba0f1
push id12263
push userjandemooij@gmail.com
push dateFri, 20 Jul 2012 18:26:06 +0000
treeherdermozilla-inbound@f9b341d6babd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs761723
milestone17.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 761723 - Add a runtime hook to retrieve source that wasn't saved. r=luke
js/src/jsapi.cpp
js/src/jscntxt.h
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
js/src/jsfun.cpp
js/src/jsscript.cpp
js/src/jsscript.h
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -796,16 +796,17 @@ JSRuntime::JSRuntime()
     gcGrayRootsTraceOp(NULL),
     gcGrayRootsData(NULL),
     autoGCRooters(NULL),
     scriptAndCountsVector(NULL),
     NaNValue(UndefinedValue()),
     negativeInfinityValue(UndefinedValue()),
     positiveInfinityValue(UndefinedValue()),
     emptyString(NULL),
+    sourceHook(NULL),
     debugMode(false),
     spsProfiler(thisFromCtor()),
     profilingScripts(false),
     alwaysPreserveCode(false),
     hadOutOfMemory(false),
     debugScopes(NULL),
     data(NULL),
     gcLock(NULL),
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -711,16 +711,18 @@ struct JSRuntime : js::RuntimeFriendFiel
 
     /* List of active contexts sharing this runtime. */
     JSCList             contextList;
 
     bool hasContexts() const {
         return !JS_CLIST_IS_EMPTY(&contextList);
     }
 
+    JS_SourceHook       sourceHook;
+
     /* Per runtime debug hooks -- see jsprvtd.h and jsdbgapi.h. */
     JSDebugHooks        debugHooks;
 
     /* If true, new compartments are initially in debug mode. */
     bool                debugMode;
 
     /* SPS profiling metadata */
     js::SPSProfiler     spsProfiler;
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -18,16 +18,22 @@
 #include "builtin/TestingFunctions.h"
 
 #include "jsobjinlines.h"
 
 using namespace js;
 using namespace JS;
 
 JS_FRIEND_API(void)
+JS_SetSourceHook(JSRuntime *rt, JS_SourceHook hook)
+{
+    rt->sourceHook = hook;
+}
+
+JS_FRIEND_API(void)
 JS_SetGrayGCRootsTracer(JSRuntime *rt, JSTraceDataOp traceOp, void *data)
 {
     rt->gcGrayRootsTraceOp = traceOp;
     rt->gcGrayRootsData = data;
 }
 
 JS_FRIEND_API(JSString *)
 JS_GetAnonymousString(JSRuntime *rt)
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -170,16 +170,21 @@ extern JS_FRIEND_API(bool)
 JS_DefineFunctionsWithHelp(JSContext *cx, JSObject *obj, const JSFunctionSpecWithHelp *fs);
 
 #endif
 
 JS_END_EXTERN_C
 
 #ifdef __cplusplus
 
+typedef bool (* JS_SourceHook)(JSContext *cx, JSScript *script, char **src, uint32_t *length);
+
+extern JS_FRIEND_API(void)
+JS_SetSourceHook(JSRuntime *rt, JS_SourceHook hook);
+
 namespace js {
 
 struct RuntimeFriendFields {
     /*
      * If non-zero, we were been asked to call the operation callback as soon
      * as possible.
      */
     volatile int32_t    interrupt;
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -585,17 +585,20 @@ JSFunction::toString(JSContext *cx, bool
         }
         if (!out.append("function "))
             return NULL;
         if (atom) {
             if (!out.append(atom))
                 return NULL;
         }
     }
-    if (isInterpreted() && script()->source) {
+    bool haveSource = isInterpreted();
+    if (haveSource && !script()->source && !script()->loadSource(cx, &haveSource))
+            return NULL;
+    if (haveSource) {
         RootedString src(cx, script()->sourceData(cx));
         if (!src)
             return NULL;
         const jschar *chars = src->getChars(cx);
         if (!chars)
             return NULL;
         bool exprBody = flags & JSFUN_EXPR_CLOSURE;
 
@@ -681,16 +684,23 @@ JSFunction::toString(JSContext *cx, bool
         if (bodyOnly) {
             // Slap a semicolon on the end of functions with an expression body.
             if (exprBody && !out.append(";"))
                 return NULL;
         } else if (!lambdaParen && (flags & JSFUN_LAMBDA)) {
             if (!out.append(")"))
                 return NULL;
         }
+    } else if (isInterpreted()) {
+        if ((!bodyOnly && !out.append("() {\n    ")) ||
+            !out.append("[sourceless code]") ||
+            (!bodyOnly && !out.append("\n}")))
+            return NULL;
+        if (!lambdaParen && (flags & JSFUN_LAMBDA) && (!out.append(")")))
+            return NULL;
     } else {
         JS_ASSERT(!(flags & JSFUN_EXPR_CLOSURE));
         if ((!bodyOnly && !out.append("() {\n    ")) ||
             !out.append("[native code]") ||
             (!bodyOnly && !out.append("\n}")))
             return NULL;
     }
     return out.finishString();
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -1065,16 +1065,45 @@ SourceCompressorThread::waitOnCompressio
     JS_ASSERT(tok->ss->ready());
     tok->ss = NULL;
     tok->chars = NULL;
     tok = NULL;
     PR_Unlock(lock);
 }
 #endif /* JS_THREADSAFE */
 
+bool
+JSScript::loadSource(JSContext *cx, bool *worked)
+{
+    JS_ASSERT(!source);
+    *worked = false;
+    if (!cx->runtime->sourceHook)
+        return true;
+    char *src = NULL;
+    uint32_t length;
+    if (!cx->runtime->sourceHook(cx, this, &src, &length))
+        return false;
+    if (!src)
+        return true;
+    size_t newLength = length;
+    jschar *usrc = InflateString(cx, src, &newLength);
+    cx->free_(src);
+    if (!usrc)
+        return false;
+    ScriptSource *ss = ScriptSource::createFromSource(cx, usrc, length, false, NULL, true);
+    if (!ss) {
+        cx->free_(usrc);
+        return false;
+    }
+    source = ss;
+    ss->attachToRuntime(cx->runtime);
+    *worked = true;
+    return true;
+}
+
 JSFixedString *
 JSScript::sourceData(JSContext *cx)
 {
     JS_ASSERT(source);
     return source->substring(cx, sourceStart, sourceEnd);
 }
 
 JSFixedString *
@@ -1142,77 +1171,82 @@ ScriptSource::substring(JSContext *cx, u
     } else {
         chars = data.source;
     }
     return js_NewStringCopyN(cx, chars + start, stop - start);
 }
 
 ScriptSource *
 ScriptSource::createFromSource(JSContext *cx, const jschar *src, uint32_t length,
-                               bool argumentsNotIncluded, SourceCompressionToken *tok)
+                               bool argumentsNotIncluded, SourceCompressionToken *tok,
+                               bool ownSource)
 {
     ScriptSource *ss = static_cast<ScriptSource *>(cx->malloc_(sizeof(*ss)));
     if (!ss)
         return NULL;
     const size_t memlen = length * sizeof(jschar);
     ss->data.compressed = static_cast<unsigned char *>(cx->malloc_(memlen));
     if (!ss->data.compressed) {
         cx->free_(ss);
         return NULL;
     }
     ss->next = NULL;
     ss->length_ = length;
+    ss->compressedLength = 0;
     ss->marked = ss->onRuntime_ = false;
     ss->argumentsNotIncluded_ = argumentsNotIncluded;
 #ifdef DEBUG
     ss->ready_ = false;
 #endif
 
 #ifdef JSGC_INCREMENTAL
     /*
      * During the IGC we need to ensure that source is marked whenever it is
      * accessed even if the name was already in the table. At this point old
      * scripts pointing to the source may no longer be reachable.
      */
     if (cx->runtime->gcIncrementalState == MARK && cx->runtime->gcIsFull)
         ss->marked = true;
 #endif
 
+    JS_ASSERT_IF(ownSource, !tok);
+
 #ifdef JS_THREADSAFE
     if (tok) {
         tok->ss = ss;
         tok->chars = src;
         cx->runtime->sourceCompressorThread.compress(tok);
     } else
 #endif
-        ss->considerCompressing(cx->runtime, src);
+        ss->considerCompressing(cx->runtime, src, ownSource);
 
 
     return ss;
 }
 
 void
-ScriptSource::considerCompressing(JSRuntime *rt, const jschar *src)
+ScriptSource::considerCompressing(JSRuntime *rt, const jschar *src, bool ownSource)
 {
     JS_ASSERT(!ready());
     const size_t memlen = length_ * sizeof(jschar);
     const size_t COMPRESS_THRESHOLD = 512;
 
     size_t compressedLen;
-    if (memlen >= COMPRESS_THRESHOLD &&
+    if (ownSource) {
+        data.source = const_cast<jschar *>(src);
+    } else if (memlen >= COMPRESS_THRESHOLD &&
         TryCompressString(reinterpret_cast<const unsigned char *>(src), memlen,
                           data.compressed, &compressedLen))
     {
         JS_ASSERT(compressedLen < memlen);
         compressedLength = compressedLen;
         void *mem = rt->realloc_(data.compressed, compressedLength);
         data.compressed = static_cast<unsigned char *>(mem);
         JS_ASSERT(data.compressed);
     } else {
-        compressedLength = 0;
         PodCopy(data.source, src, length_);
     }
 #ifdef DEBUG    
     ready_ = true;
 #endif
 }
 
 void
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -615,16 +615,18 @@ struct JSScript : public js::gc::Cell
      * Original compiled function for the script, if it has a function.
      * NULL for global and eval scripts.
      */
     JSFunction *function() const { return function_; }
     void setFunction(JSFunction *fun);
 
     JSFixedString *sourceData(JSContext *cx);
 
+    bool loadSource(JSContext *cx, bool *worked);
+
     /* Return whether this script was compiled for 'eval' */
     bool isForEval() { return isCachedEval || isActiveEval; }
 
 #ifdef DEBUG
     unsigned id();
 #else
     unsigned id() { return 0; }
 #endif
@@ -973,17 +975,18 @@ struct ScriptSource
     bool ready_:1;
 #endif
 
   public:
     static ScriptSource *createFromSource(JSContext *cx,
                                           const jschar *src,
                                           uint32_t length,
                                           bool argumentsNotIncluded = false,
-                                          SourceCompressionToken *tok = NULL);
+                                          SourceCompressionToken *tok = NULL,
+                                          bool ownSource = false);
     void attachToRuntime(JSRuntime *rt);
     void mark() { JS_ASSERT(ready_); JS_ASSERT(onRuntime_); 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_; }
@@ -995,17 +998,17 @@ struct ScriptSource
     static void sweep(JSRuntime *rt);
 
     // XDR handling
     template <XDRMode mode>
     static bool performXDR(XDRState<mode> *xdr, ScriptSource **ss);
 
   private:
     bool compressed() { return !!compressedLength; }
-    void considerCompressing(JSRuntime *rt, const jschar *src);
+    void considerCompressing(JSRuntime *rt, const jschar *src, bool ownSource = false);
 };
 
 #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.
  *