Bug 1277246 - Add support for names section. r?luke draft
authorYury Delendik <ydelendik@mozilla.com>
Thu, 02 Jun 2016 14:39:30 -0500
changeset 374690 69337aeb55616baf8d677872f078b9630a784d61
parent 374456 92e0c73391e71a400e2c6674bca5ca70804ab081
child 522673 6ef2d34d747a8ffa969ac25f2309622ef528d1b0
push id20065
push userydelendik@mozilla.com
push dateThu, 02 Jun 2016 19:39:53 +0000
reviewersluke
bugs1277246
milestone49.0a1
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