Bug 996881: Inherit 'use strict' directive when calling toSource/toString for asm.js modules; r=luke
authorBenjamin Bouvier <benj@benj.me>
Thu, 17 Apr 2014 14:06:50 +0200
changeset 197632 a93e17835a75a43751d5ed9ad66370c34bd3a73b
parent 197631 953430fb3c691df3c534e3601b3301a6f94ad5d5
child 197633 7dd1a0534c380cb52fc36847719904a874959ee4
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs996881
milestone31.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 996881: Inherit 'use strict' directive when calling toSource/toString for asm.js modules; r=luke
js/src/jit-test/tests/asm.js/testSource.js
js/src/jit/AsmJS.cpp
js/src/jit/AsmJSLink.cpp
js/src/jit/AsmJSModule.cpp
js/src/jit/AsmJSModule.h
js/src/jsfun.cpp
js/src/jsfun.h
--- a/js/src/jit-test/tests/asm.js/testSource.js
+++ b/js/src/jit-test/tests/asm.js/testSource.js
@@ -232,16 +232,43 @@ if (isAsmJSCompilationAvailable() && isC
     var m = new Function('glob', 'ffi', 'heap', bodyOnly);
     assertEq(isAsmJSModuleLoadedFromCache(m), true);
     assertEq(m.toString(), "function anonymous(glob, ffi, heap) {\n" + bodyOnly + "\n}");
     assertEq(m.toSource(), "(function anonymous(glob, ffi, heap) {\n" + bodyOnly + "\n})");
 }
 
 })();
 
+/* Implicit "use strict" context */
+(function() {
+
+var funcHeader =  'function (glob, ffi, heap) {',
+    funcBody = '\n"use asm";\n\
+    function g() {}\n\
+    return g;\n\n'
+    funcFooter = '}',
+    funcSource = funcHeader + funcBody + funcFooter
+    useStrict = '\n"use strict";\n';
+
+var f4 = eval("\"use strict\";\n(" + funcSource + ")");
+
+var expectedToString = funcHeader + useStrict + funcBody + funcFooter
+var expectedToSource = '(' + expectedToString + ')'
+
+assertEq(f4.toString(), expectedToString);
+assertEq(f4.toSource(), expectedToSource);
+
+if (isAsmJSCompilationAvailable() && isCachingEnabled()) {
+    var f5 = eval("\"use strict\";\n(" + funcSource + ")");
+    assertEq(isAsmJSModuleLoadedFromCache(f5), true);
+    assertEq(f5.toString(), expectedToString);
+    assertEq(f5.toSource(), expectedToSource);
+}
+})();
+
 /* Functions */
 (function() {
 
 var noSrc = "function noArgument() {\n\
     return 42;\n\
 }"
 var oneSrc = "function oneArgument(x) {\n\
     x = x | 0;\n\
--- a/js/src/jit/AsmJS.cpp
+++ b/js/src/jit/AsmJS.cpp
@@ -1138,17 +1138,23 @@ class MOZ_STACK_CLASS ModuleCompiler
             !addStandardLibraryMathName("SQRT1_2", M_SQRT1_2) ||
             !addStandardLibraryMathName("SQRT2", M_SQRT2))
         {
             return false;
         }
 
         uint32_t funcStart = parser_.pc->maybeFunction->pn_body->pn_pos.begin;
         uint32_t offsetToEndOfUseAsm = parser_.tokenStream.currentToken().pos.end;
-        module_ = cx_->new_<AsmJSModule>(parser_.ss, funcStart, offsetToEndOfUseAsm);
+
+        // "use strict" should be added to the source if we are in an implicit
+        // strict context, see also comment above addUseStrict in
+        // js::FunctionToString.
+        bool strict = parser_.pc->sc->strict && !parser_.pc->sc->hasExplicitUseStrict();
+
+        module_ = cx_->new_<AsmJSModule>(parser_.ss, funcStart, offsetToEndOfUseAsm, strict);
         if (!module_)
             return false;
 
         return true;
     }
 
     bool failOffset(uint32_t offset, const char *str) {
         JS_ASSERT(!errorString_);
@@ -1509,17 +1515,16 @@ class MOZ_STACK_CLASS ModuleCompiler
                                slowFuns ? slowFuns.get() : ""));
 #endif
     }
 
     bool finish(ScopedJSDeletePtr<AsmJSModule> *module)
     {
         module_->initFuncEnd(parser_.tokenStream.currentToken().pos.end,
                              parser_.tokenStream.peekTokenPos().end);
-
         masm_.finish();
         if (masm_.oom())
             return false;
 
 #if defined(JS_CODEGEN_ARM)
         // Now that compilation has finished, we need to update offsets to
         // reflect actual offsets (an ARM distinction).
         for (unsigned i = 0; i < module_->numHeapAccesses(); i++) {
--- a/js/src/jit/AsmJSLink.cpp
+++ b/js/src/jit/AsmJSLink.cpp
@@ -795,18 +795,41 @@ js::AsmJSModuleToString(JSContext *cx, H
         if (!out.append(") {\n"))
             return nullptr;
     }
 
     Rooted<JSFlatString*> src(cx, source->substring(cx, begin, end));
     if (!src)
         return nullptr;
 
-    if (!out.append(src->chars(), src->length()))
-        return nullptr;
+    if (module.strict()) {
+        // We need to add "use strict" in the body right after the opening
+        // brace.
+        size_t bodyStart = 0, bodyEnd;
+
+        // No need to test for functions created with the Function ctor as
+        // these doesn't implicitly inherit the "use strict" context. Strict mode is
+        // enabled for functions created with the Function ctor only if they begin with
+        // the "use strict" directive, but these functions won't validate as asm.js
+        // modules.
+
+        ConstTwoByteChars chars(src->chars(), src->length());
+        if (!FindBody(cx, fun, chars, src->length(), &bodyStart, &bodyEnd))
+            return nullptr;
+
+        if (!out.append(chars, bodyStart) ||
+            !out.append("\n\"use strict\";\n") ||
+            !out.append(chars + bodyStart, src->length() - bodyStart))
+        {
+            return nullptr;
+        }
+    } else {
+        if (!out.append(src->chars(), src->length()))
+            return nullptr;
+    }
 
     if (funCtor && !out.append("\n}"))
         return nullptr;
 
     if (addParenToLambda && fun->isLambda() && !out.append(")"))
         return nullptr;
 
     return out.finishString();
--- a/js/src/jit/AsmJSModule.cpp
+++ b/js/src/jit/AsmJSModule.cpp
@@ -323,32 +323,34 @@ AsmJSModule::staticallyLink(ExclusiveCon
     // Initialize global data segment
 
     for (size_t i = 0; i < exits_.length(); i++) {
         exitIndexToGlobalDatum(i).exit = interpExitTrampoline(exits_[i]);
         exitIndexToGlobalDatum(i).fun = nullptr;
     }
 }
 
-AsmJSModule::AsmJSModule(ScriptSource *scriptSource, uint32_t funcStart, uint32_t offsetToEndOfUseAsm)
+AsmJSModule::AsmJSModule(ScriptSource *scriptSource, uint32_t funcStart,
+                         uint32_t offsetToEndOfUseAsm, bool strict)
   : globalArgumentName_(nullptr),
     importArgumentName_(nullptr),
     bufferArgumentName_(nullptr),
     code_(nullptr),
     interruptExit_(nullptr),
     dynamicallyLinked_(false),
     loadedFromCache_(false),
     funcStart_(funcStart),
     offsetToEndOfUseAsm_(offsetToEndOfUseAsm),
     scriptSource_(scriptSource),
     codeIsProtected_(false)
 {
     mozilla::PodZero(&pod);
     scriptSource_->incref();
     pod.minHeapLength_ = AsmJSAllocationGranularity;
+    pod.strict_ = strict;
 }
 
 AsmJSModule::~AsmJSModule()
 {
     scriptSource_->decref();
 
     if (code_) {
         for (unsigned i = 0; i < numExits(); i++) {
@@ -871,17 +873,17 @@ class AutoUnprotectCodeForClone
     }
 };
 
 bool
 AsmJSModule::clone(JSContext *cx, ScopedJSDeletePtr<AsmJSModule> *moduleOut) const
 {
     AutoUnprotectCodeForClone cloneGuard(cx, *this);
 
-    *moduleOut = cx->new_<AsmJSModule>(scriptSource_, funcStart_, offsetToEndOfUseAsm_);
+    *moduleOut = cx->new_<AsmJSModule>(scriptSource_, funcStart_, offsetToEndOfUseAsm_, pod.strict_);
     if (!*moduleOut)
         return false;
 
     AsmJSModule &out = **moduleOut;
 
     // Mirror the order of serialize/deserialize in cloning:
 
     out.pod = pod;
@@ -1308,18 +1310,19 @@ js::LookupAsmJSModuleInCache(ExclusiveCo
 
     ModuleCharsForLookup moduleChars;
     cursor = moduleChars.deserialize(cx, cursor);
     if (!moduleChars.match(parser))
         return true;
 
     uint32_t funcStart = parser.pc->maybeFunction->pn_body->pn_pos.begin;
     uint32_t offsetToEndOfUseAsm = parser.tokenStream.currentToken().pos.end;
+    bool strict = parser.pc->sc->strict && !parser.pc->sc->hasExplicitUseStrict();
     ScopedJSDeletePtr<AsmJSModule> module(
-        cx->new_<AsmJSModule>(parser.ss, funcStart, offsetToEndOfUseAsm));
+        cx->new_<AsmJSModule>(parser.ss, funcStart, offsetToEndOfUseAsm, strict));
     if (!module)
         return false;
     cursor = module->deserialize(cx, cursor);
     if (!cursor)
         return false;
 
     bool atEnd = cursor == entry.memory + entry.serializedSize;
     MOZ_ASSERT(atEnd, "Corrupt cache file");
--- a/js/src/jit/AsmJSModule.h
+++ b/js/src/jit/AsmJSModule.h
@@ -426,16 +426,17 @@ class AsmJSModule
 #endif
 #if defined(JS_ION_PERF)
     ProfiledBlocksFunctionVector          perfProfiledBlocksFunctions_;
 #endif
 
     struct Pod {
         uint32_t                          funcLength_;
         uint32_t                          funcLengthWithRightBrace_;
+        bool                              strict_;
         uint32_t                          numGlobalVars_;
         uint32_t                          numFFIs_;
         size_t                            funcPtrTableAndExitBytes_;
         bool                              hasArrayView_;
         size_t                            functionBytes_; // just the function bodies, no stubs
         size_t                            codeBytes_;     // function bodies and stubs
         size_t                            totalBytes_;    // function bodies, stubs, and global data
         uint32_t                          minHeapLength_;
@@ -459,17 +460,18 @@ class AsmJSModule
 
     FunctionCountsVector                  functionCounts_;
 
     // This field is accessed concurrently when requesting an interrupt.
     // Access must be synchronized via the runtime's interrupt lock.
     mutable bool                          codeIsProtected_;
 
   public:
-    explicit AsmJSModule(ScriptSource *scriptSource, uint32_t functStart, uint32_t offsetToEndOfUseAsm);
+    explicit AsmJSModule(ScriptSource *scriptSource, uint32_t functStart,
+                         uint32_t offsetToEndOfUseAsm, bool strict);
     ~AsmJSModule();
 
     void trace(JSTracer *trc) {
         for (unsigned i = 0; i < globals_.length(); i++)
             globals_[i].trace(trc);
         for (unsigned i = 0; i < exports_.length(); i++)
             exports_[i].trace(trc);
         for (unsigned i = 0; i < exits_.length(); i++) {
@@ -519,16 +521,19 @@ class AsmJSModule
         pod.funcLengthWithRightBrace_ = endAfterCurly - funcStart_;
     }
     uint32_t funcEndBeforeCurly() const {
         return funcStart_ + pod.funcLength_;
     }
     uint32_t funcEndAfterCurly() const {
         return funcStart_ + pod.funcLengthWithRightBrace_;
     }
+    bool strict() const {
+        return pod.strict_;
+    }
 
     bool addGlobalVarInit(const Value &v, AsmJSCoercion coercion, uint32_t *globalIndex) {
         JS_ASSERT(pod.funcPtrTableAndExitBytes_ == 0);
         if (pod.numGlobalVars_ == UINT32_MAX)
             return false;
         Global g(Global::Variable, nullptr);
         g.pod.u.var.initKind_ = Global::InitConstant;
         g.pod.u.var.init.constant_ = v;
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -621,24 +621,28 @@ const Class JSFunction::class_ = {
     fun_hasInstance,
     nullptr,                 /* construct   */
     fun_trace
 };
 
 const Class* const js::FunctionClassPtr = &JSFunction::class_;
 
 /* Find the body of a function (not including braces). */
-static bool
-FindBody(JSContext *cx, HandleFunction fun, ConstTwoByteChars chars, size_t length,
+bool
+js::FindBody(JSContext *cx, HandleFunction fun, ConstTwoByteChars chars, size_t length,
          size_t *bodyStart, size_t *bodyEnd)
 {
     // We don't need principals, since those are only used for error reporting.
     CompileOptions options(cx);
-    options.setFileAndLine("internal-findBody", 0)
-           .setVersion(fun->nonLazyScript()->getVersion());
+    options.setFileAndLine("internal-findBody", 0);
+
+    // For asm.js modules, there's no script.
+    if (fun->hasScript())
+        options.setVersion(fun->nonLazyScript()->getVersion());
+
     AutoKeepAtoms keepAtoms(cx->perThreadData);
     TokenStream ts(cx, options, chars.get(), length, nullptr);
     int nest = 0;
     bool onward = true;
     // Skip arguments list.
     do {
         switch (ts.getToken()) {
           case TOK_NAME:
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -549,16 +549,21 @@ class FunctionExtended : public JSFuncti
     HeapValue extendedSlots[NUM_EXTENDED_SLOTS];
 };
 
 extern JSFunction *
 CloneFunctionObject(JSContext *cx, HandleFunction fun, HandleObject parent,
                     gc::AllocKind kind = JSFunction::FinalizeKind,
                     NewObjectKind newKindArg = GenericObject);
 
+
+extern bool
+FindBody(JSContext *cx, HandleFunction fun, ConstTwoByteChars chars, size_t length,
+         size_t *bodyStart, size_t *bodyEnd);
+
 } // namespace js
 
 inline js::FunctionExtended *
 JSFunction::toExtended()
 {
     JS_ASSERT(isExtended());
     return static_cast<js::FunctionExtended *>(this);
 }