Bug 1277246 - Add support for names section. r=luke
authorYury Delendik <ydelendik@mozilla.com>
Thu, 02 Jun 2016 14:39:30 -0500
changeset 341459 d02ab40ae319910659cf19114f41e7aa7ce68e4d
parent 341458 71530f6566b92269c014a067a47d0d69ff79a72c
child 341460 58137ecd12edb7ae1a57c80da811676bc66fd03c
push id1183
push userraliiev@mozilla.com
push dateMon, 05 Sep 2016 20:01:49 +0000
treeherdermozilla-release@3148731bed45 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1277246
milestone49.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 1277246 - Add support for names section. r=luke MozReview-Commit-ID: LbGMxxDdmXx
js/src/asmjs/AsmJS.cpp
js/src/asmjs/Wasm.cpp
js/src/asmjs/WasmBinary.h
js/src/asmjs/WasmModule.cpp
js/src/asmjs/WasmModule.h
js/src/jit-test/tests/wasm/binary.js
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -8564,17 +8564,17 @@ BuildConsoleMessage(ExclusiveContext* cx
         slowText.reset(JS_smprintf("; %d functions compiled slowly: ", slowFuncs.length()));
         if (!slowText)
             return nullptr;
 
         for (unsigned i = 0; i < slowFuncs.length(); i++) {
             const SlowFunction& func = slowFuncs[i];
             slowText.reset(JS_smprintf("%s%s:%u (%ums)%s",
                                        slowText.get(),
-                                       module.prettyFuncName(func.index),
+                                       module.maybePrettyFuncName(func.index),
                                        func.lineOrBytecode,
                                        func.ms,
                                        i+1 < slowFuncs.length() ? ", " : ""));
             if (!slowText)
                 return nullptr;
         }
     }
 
--- a/js/src/asmjs/Wasm.cpp
+++ b/js/src/asmjs/Wasm.cpp
@@ -14,16 +14,17 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "asmjs/Wasm.h"
 
 #include "mozilla/CheckedInt.h"
+#include "mozilla/unused.h"
 
 #include "jsprf.h"
 
 #include "asmjs/WasmBinaryIterator.h"
 #include "asmjs/WasmGenerator.h"
 #include "vm/ArrayBufferObject.h"
 #include "vm/Debugger.h"
 
@@ -33,16 +34,17 @@
 #include "vm/Debugger-inl.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
 using mozilla::CheckedInt;
 using mozilla::IsNaN;
+using mozilla::Unused;
 
 typedef Handle<WasmModuleObject*> HandleWasmModule;
 typedef MutableHandle<WasmModuleObject*> MutableHandleWasmModule;
 
 /*****************************************************************************/
 // reporting
 
 static bool
@@ -668,22 +670,43 @@ CheckTypeForJS(JSContext* cx, Decoder& d
     if (sig.ret() == ExprType::I64 && !allowI64)
         return Fail(cx, d, "cannot import/export i64 return type");
     if (IsSimdType(sig.ret()))
         return Fail(cx, d, "cannot import/export SIMD return type");
 
     return true;
 }
 
+static UniqueChars
+MaybeDecodeName(JSContext* cx, Decoder& d)
+{
+    Bytes bytes;
+    if (!d.readBytes(&bytes))
+        return nullptr;
+
+    // Rejecting a name if null or non-ascii character was found.
+    // TODO Relax the requirement and allow valid non-null valid UTF8 characters.
+    for (size_t i = 0; i < bytes.length(); i++) {
+        const uint8_t ch = bytes[i];
+        if (ch == 0 || ch >= 128)
+            return nullptr;
+    }
+
+    if (!bytes.append(0))
+        return nullptr;
+
+    return UniqueChars((char*)bytes.extractOrCopyRawBuffer());
+}
+
 struct ImportName
 {
-    Bytes module;
-    Bytes func;
+    UniqueChars module;
+    UniqueChars func;
 
-    ImportName(Bytes&& module, Bytes&& func)
+    ImportName(UniqueChars&& module, UniqueChars&& func)
       : module(Move(module)), func(Move(func))
     {}
     ImportName(ImportName&& rhs)
       : module(Move(rhs.module)), func(Move(rhs.func))
     {}
 };
 
 typedef Vector<ImportName, 0, SystemAllocPolicy> ImportNameVector;
@@ -696,26 +719,26 @@ DecodeImport(JSContext* cx, Decoder& d, 
         return false;
 
     if (!init->imports.emplaceBack(sig))
         return false;
 
     if (!CheckTypeForJS(cx, d, *sig))
         return false;
 
-    Bytes moduleName;
-    if (!d.readBytes(&moduleName))
-        return Fail(cx, d, "expected import module name");
+    UniqueChars moduleName = MaybeDecodeName(cx, d);
+    if (!moduleName)
+        return Fail(cx, d, "expected valid import module name");
 
-    if (moduleName.empty())
+    if (!strlen(moduleName.get()))
         return Fail(cx, d, "module name cannot be empty");
 
-    Bytes funcName;
-    if (!d.readBytes(&funcName))
-        return Fail(cx, d, "expected import func name");
+    UniqueChars funcName = MaybeDecodeName(cx, d);
+    if (!funcName)
+        return Fail(cx, d, "expected valid import func name");
 
     return importNames->emplaceBack(Move(moduleName), Move(funcName));
 }
 
 static bool
 DecodeImportSection(JSContext* cx, Decoder& d, ModuleGeneratorData* init, ImportNameVector* importNames)
 {
     uint32_t sectionStart, sectionSize;
@@ -795,44 +818,32 @@ DecodeMemorySection(JSContext* cx, Decod
     return true;
 }
 
 typedef HashSet<const char*, CStringHasher> CStringSet;
 
 static UniqueChars
 DecodeExportName(JSContext* cx, Decoder& d, CStringSet* dupSet)
 {
-    Bytes fieldBytes;
-    if (!d.readBytes(&fieldBytes)) {
-        Fail(cx, d, "expected export name");
+    UniqueChars exportName = MaybeDecodeName(cx, d);
+    if (!exportName) {
+        Fail(cx, d, "expected valid export name");
         return nullptr;
     }
 
-    if (memchr(fieldBytes.begin(), 0, fieldBytes.length())) {
-        Fail(cx, d, "null in export names not yet supported");
-        return nullptr;
-    }
-
-    if (!fieldBytes.append(0))
-        return nullptr;
-
-    UniqueChars fieldName((char*)fieldBytes.extractOrCopyRawBuffer());
-    if (!fieldName)
-        return nullptr;
-
-    CStringSet::AddPtr p = dupSet->lookupForAdd(fieldName.get());
+    CStringSet::AddPtr p = dupSet->lookupForAdd(exportName.get());
     if (p) {
         Fail(cx, d, "duplicate export");
         return nullptr;
     }
 
-    if (!dupSet->add(p, fieldName.get()))
+    if (!dupSet->add(p, exportName.get()))
         return nullptr;
 
-    return Move(fieldName);
+    return Move(exportName);
 }
 
 static bool
 DecodeFunctionExport(JSContext* cx, Decoder& d, ModuleGenerator& mg, CStringSet* dupSet)
 {
     uint32_t funcIndex;
     if (!d.readVarU32(&funcIndex))
         return Fail(cx, d, "expected export internal index");
@@ -1020,16 +1031,81 @@ DecodeDataSection(JSContext* cx, Decoder
 
     if (!d.finishSection(sectionStart, sectionSize))
         return Fail(cx, d, "data section byte size mismatch");
 
     return true;
 }
 
 static bool
+MaybeDecodeNameSectionBody(JSContext* cx, Decoder& d, uint32_t sectionStart,
+                           uint32_t sectionSize, CacheableCharsVector* funcNames)
+{
+    uint32_t numFuncNames;
+    if (!d.readVarU32(&numFuncNames))
+        return false;
+
+    if (numFuncNames > MaxFuncs)
+        return false;
+
+    CacheableCharsVector result;
+    if (!result.resize(numFuncNames))
+        return false;
+
+    for (uint32_t i = 0; i < numFuncNames; i++) {
+        UniqueChars funcName = MaybeDecodeName(cx, d);
+        if (!funcName)
+            return false;
+
+        result[i] = strlen(funcName.get()) ? Move(funcName) : nullptr;
+
+        // Skipping local names for a function.
+        uint32_t numLocals;
+        if (!d.readVarU32(&numLocals))
+            return false;
+
+        for (uint32_t j = 0; j < numLocals; j++) {
+            UniqueChars localName = MaybeDecodeName(cx, d);
+            if (!localName) {
+                cx->clearPendingException();
+                return false;
+            }
+
+            Unused << localName;
+        }
+    }
+
+    *funcNames = Move(result);
+    return true;
+}
+
+static bool
+DecodeNameSection(JSContext* cx, Decoder& d, CacheableCharsVector* funcNames)
+{
+    uint32_t sectionStart, sectionSize;
+    if (!d.startSection(NameSectionId, &sectionStart, &sectionSize))
+        return Fail(cx, d, "failed to start section");
+    if (sectionStart == Decoder::NotStarted)
+        return true;
+
+    if (!MaybeDecodeNameSectionBody(cx, d, sectionStart, sectionSize, funcNames)) {
+        // This section does not cause validation for the whole module to fail and
+        // is instead treated as if the section was absent.
+        d.ignoreSection(sectionStart, sectionSize);
+        return true;
+    }
+
+    if (!d.finishSection(sectionStart, sectionSize))
+        return Fail(cx, d, "names section byte size mismatch");
+
+    return true;
+}
+
+
+static bool
 DecodeModule(JSContext* cx, UniqueChars file, const uint8_t* bytes, uint32_t length,
              ImportNameVector* importNames, UniqueExportMap* exportMap,
              MutableHandle<ArrayBufferObject*> heap, MutableHandle<WasmModuleObject*> moduleObj)
 {
     Decoder d(bytes, bytes + length);
 
     uint32_t u32;
     if (!d.readFixedU32(&u32) || u32 != MagicNumber)
@@ -1066,16 +1142,18 @@ DecodeModule(JSContext* cx, UniqueChars 
 
     if (!DecodeCodeSection(cx, d, mg))
         return false;
 
     if (!DecodeDataSection(cx, d, heap))
         return false;
 
     CacheableCharsVector funcNames;
+    if (!DecodeNameSection(cx, d, &funcNames))
+        return false;
 
     while (!d.done()) {
         if (!d.skipSection())
             return Fail(cx, d, "failed to skip unknown section at end");
     }
 
     UniqueModuleData module;
     UniqueStaticLinkData staticLink;
@@ -1118,44 +1196,44 @@ CheckCompilerSupport(JSContext* cx)
 #endif
         JS_ReportError(cx, "WebAssembly is not supported on the current device.");
         return false;
     }
     return true;
 }
 
 static bool
-GetProperty(JSContext* cx, HandleObject obj, const Bytes& bytes, MutableHandleValue v)
+GetProperty(JSContext* cx, HandleObject obj, const char* chars, MutableHandleValue v)
 {
-    JSAtom* atom = AtomizeUTF8Chars(cx, (char*)bytes.begin(), bytes.length());
+    JSAtom* atom = AtomizeUTF8Chars(cx, chars, strlen(chars));
     if (!atom)
         return false;
 
     RootedId id(cx, AtomToId(atom));
     return GetProperty(cx, obj, obj, id, v);
 }
 
 static bool
 ImportFunctions(JSContext* cx, HandleObject importObj, const ImportNameVector& importNames,
                 MutableHandle<FunctionVector> imports)
 {
     if (!importNames.empty() && !importObj)
         return Fail(cx, "no import object given");
 
     for (const ImportName& name : importNames) {
         RootedValue v(cx);
-        if (!GetProperty(cx, importObj, name.module, &v))
+        if (!GetProperty(cx, importObj, name.module.get(), &v))
             return false;
 
-        if (!name.func.empty()) {
+        if (strlen(name.func.get()) > 0) {
             if (!v.isObject())
                 return Fail(cx, "import object field is not an Object");
 
             RootedObject obj(cx, &v.toObject());
-            if (!GetProperty(cx, obj, name.func, &v))
+            if (!GetProperty(cx, obj, name.func.get(), &v))
                 return false;
         }
 
         if (!IsFunctionObject(v))
             return Fail(cx, "import object field is not a Function");
 
         if (!imports.append(&v.toObject().as<JSFunction>()))
             return false;
--- a/js/src/asmjs/WasmBinary.h
+++ b/js/src/asmjs/WasmBinary.h
@@ -30,16 +30,17 @@ static const uint32_t EncodingVersion   
 static const char TypeSectionId[]        = "type";
 static const char ImportSectionId[]      = "import";
 static const char FunctionSectionId[]    = "function";
 static const char TableSectionId[]       = "table";
 static const char MemorySectionId[]      = "memory";
 static const char ExportSectionId[]      = "export";
 static const char CodeSectionId[]        = "code";
 static const char DataSectionId[]        = "data";
+static const char NameSectionId[]        = "name";
 
 enum class ValType
 {
     I32                                  = 0x01,
     I64                                  = 0x02,
     F32                                  = 0x03,
     F64                                  = 0x04,
 
@@ -792,16 +793,20 @@ class Decoder
       backup:
         cur_ = before;
         *startOffset = NotStarted;
         return true;
     }
     MOZ_MUST_USE bool finishSection(uint32_t startOffset, uint32_t size) {
         return size == (cur_ - beg_) - startOffset;
     }
+    void ignoreSection(uint32_t startOffset, uint32_t size) {
+        cur_ = (beg_ + startOffset) + size;
+        MOZ_ASSERT(cur_ <= end_);
+    }
     MOZ_MUST_USE bool skipSection() {
         uint32_t idSize;
         if (!readVarU32(&idSize) || bytesRemain() < idSize)
             return false;
         cur_ += idSize;
         uint32_t size;
         if (!readVarU32(&size) || bytesRemain() < size)
             return false;
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -1618,26 +1618,28 @@ Module::callImport(JSContext* cx, uint32
         return false;
 
     exit.code = jitExitCode;
     exit.baselineScript = script->baselineScript();
     return true;
 }
 
 const char*
-Module::prettyFuncName(uint32_t funcIndex) const
+Module::maybePrettyFuncName(uint32_t funcIndex) const
 {
+    if (funcIndex >= module_->prettyFuncNames.length())
+        return nullptr;
     return module_->prettyFuncNames[funcIndex].get();
 }
 
 const char*
 Module::getFuncName(JSContext* cx, uint32_t funcIndex, UniqueChars* owner) const
 {
-    if (!module_->prettyFuncNames.empty())
-        return prettyFuncName(funcIndex);
+    if (const char* prettyName = maybePrettyFuncName(funcIndex))
+        return prettyName;
 
     char* chars = JS_smprintf("wasm-function[%u]", funcIndex);
     if (!chars) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
     owner->reset(chars);
--- a/js/src/asmjs/WasmModule.h
+++ b/js/src/asmjs/WasmModule.h
@@ -635,17 +635,17 @@ class Module : public mozilla::LinkedLis
 
     uint8_t* interrupt() const { MOZ_ASSERT(staticallyLinked_); return interrupt_; }
     uint8_t* outOfBounds() const { MOZ_ASSERT(staticallyLinked_); return outOfBounds_; }
 
     // Every function has an associated display atom which is either the pretty
     // name given by the asm.js function name or wasm symbols or something
     // generated from the function index.
 
-    const char* prettyFuncName(uint32_t funcIndex) const;
+    const char* maybePrettyFuncName(uint32_t funcIndex) const;
     const char* getFuncName(JSContext* cx, uint32_t funcIndex, UniqueChars* owner) const;
     JSAtom* getFuncAtom(JSContext* cx, uint32_t funcIndex) const;
 
     // If debuggerObservesAsmJS was true when the module was compiled, render
     // the binary to a new source string.
 
     JSString* createText(JSContext* cx);
 
--- a/js/src/jit-test/tests/wasm/binary.js
+++ b/js/src/jit-test/tests/wasm/binary.js
@@ -15,32 +15,34 @@ const ver3 = (Wasm.experimentalVersion >
 // Section names
 const sigId                = "type";
 const importId             = "import";
 const functionSignaturesId = "function";
 const functionTableId      = "table";
 const exportTableId        = "export";
 const functionBodiesId     = "code";
 const dataSegmentsId       = "data";
+const nameId               = "name";
 
 const magicError = /failed to match magic number/;
 const versionError = /failed to match binary version/;
 const unknownSectionError = /failed to skip unknown section at end/;
 const sectionError = /failed to start section/;
 
 const VoidCode = 0;
 const I32Code = 1;
 const I64Code = 2;
 const F32Code = 3;
 const F64Code = 4;
 
 const FunctionConstructorCode = 0x40;
 
 const Block = 0x01;
 const End = 0x0f;
+const CallImport = 0x18;
 
 function toU8(array) {
     for (let b of array)
         assertEq(b < 256, true);
     return Uint8Array.from(array);
 }
 
 function varU32(u32) {
@@ -95,16 +97,21 @@ function string(name) {
     var nameBytes = name.split('').map(c => {
         var code = c.charCodeAt(0);
         assertEq(code < 128, true); // TODO
         return code
     });
     return varU32(nameBytes.length).concat(nameBytes);
 }
 
+function encodedString(name, len) {
+    var nameBytes = name.split('').map(c => c.charCodeAt(0));
+    return varU32(len === undefined ? nameBytes.length : len).concat(nameBytes);
+}
+
 function moduleWithSections(sectionArray) {
     var bytes = moduleHeaderThen();
     for (let section of sectionArray) {
         bytes.push(...string(section.name));
         bytes.push(...varU32(section.body.length));
         bytes.push(...section.body);
     }
     return toU8(bytes);
@@ -153,24 +160,50 @@ function importSection(imports) {
     for (let imp of imports) {
         body.push(...varU32(imp.sigIndex));
         body.push(...string(imp.module));
         body.push(...string(imp.func));
     }
     return { name: importId, body };
 }
 
+function exportSection(exports) {
+    var body = [];
+    body.push(...varU32(exports.length));
+    for (let exp of exports) {
+        body.push(...varU32(exp.funcIndex));
+        body.push(...string(exp.name));
+    }
+    return { name: exportTableId, body };
+}
+
 function tableSection(elems) {
     var body = [];
     body.push(...varU32(elems.length));
     for (let i of elems)
         body.push(...varU32(i));
     return { name: functionTableId, body };
 }
 
+function nameSection(elems) {
+    var body = [];
+    body.push(...varU32(elems.length));
+    for (let fn of elems) {
+        body.push(...encodedString(fn.name, fn.nameLen));
+        if (!fn.locals) {
+           body.push(...varU32(0));
+           continue;
+        }
+        body.push(...varU32(fn.locals.length));
+        for (let local of fn.locals)
+            body.push(...encodedString(local.name, local.nameLen));
+    }
+    return { name: nameId, body };
+}
+
 const v2vSig = {args:[], ret:VoidCode};
 const i2vSig = {args:[I32Code], ret:VoidCode};
 const v2vBody = funcBody({locals:[], body:[]});
 
 assertErrorMessage(() => wasmEval(moduleWithSections([ {name: sigId, body: U32MAX_LEB } ])), TypeError, /too many signatures/);
 assertErrorMessage(() => wasmEval(moduleWithSections([ {name: sigId, body: [1, 0], } ])), TypeError, /expected function form/);
 assertErrorMessage(() => wasmEval(moduleWithSections([ {name: sigId, body: [1, FunctionConstructorCode, ...U32MAX_LEB], } ])), TypeError, /too many arguments in signature/);
 
@@ -213,8 +246,40 @@ assertErrorMessage(() => wasmEval(module
 wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0,0,0]), tableSection([0,1,0,2]), bodySection([v2vBody, v2vBody, v2vBody])]));
 wasmEval(moduleWithSections([sigSection([v2vSig,i2vSig]), declSection([0,0,1]), tableSection([0,1,2]), bodySection([v2vBody, v2vBody, v2vBody])]));
 
 // Deep nesting shouldn't crash or even throw.
 var manyBlocks = [];
 for (var i = 0; i < 20000; i++)
     manyBlocks.push(Block, End);
 wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:manyBlocks})])]));
+
+// Checking stack trace.
+function runStartTraceTest(namesContent, expectedName) {
+    var sections = [
+        sigSection([v2vSig]),
+        importSection([{sigIndex:0, module:"env", func:"callback"}]),
+        declSection([0]),
+        exportSection([{funcIndex:0, name: "run"}]),
+        bodySection([funcBody({locals: [], body: [CallImport, varU32(0), varU32(0)]})])
+    ];
+    if (namesContent)
+        sections.push(nameSection(namesContent));
+    var result = "";
+    var callback = () => {
+        var prevFrameEntry = new Error().stack.split('\n')[1];
+        result = prevFrameEntry.split('@')[0];
+    };
+    wasmEval(moduleWithSections(sections), {"env": { callback }}).run();
+    assertEq(result, expectedName);
+};
+
+runStartTraceTest(null, 'wasm-function[0]');
+runStartTraceTest([{name: 'test'}], 'test');
+runStartTraceTest([{name: 'test', locals: [{name: 'var1'}, {name: 'var2'}]}], 'test');
+runStartTraceTest([{name: 'test', locals: [{name: 'var1'}, {name: 'var2'}]}], 'test');
+runStartTraceTest([{name: 'test1'}, {name: 'test2'}], 'test1');
+runStartTraceTest([], 'wasm-function[0]');
+// Notice that invalid names section content shall not fail the parsing
+runStartTraceTest([{nameLen: 100, name: 'test'}], 'wasm-function[0]'); // invalid name size
+runStartTraceTest([{name: 'test', locals: [{nameLen: 40, name: 'var1'}]}], 'wasm-function[0]'); // invalid variable name size
+runStartTraceTest([{name: ''}], 'wasm-function[0]'); // empty name
+runStartTraceTest([{name: 'te\xE0\xFF'}], 'wasm-function[0]'); // invalid UTF8 name