Bug 1277246 - Add support for names section. r?luke draft
authorYury Delendik <ydelendik@mozilla.com>
Wed, 01 Jun 2016 15:17:24 -0500
changeset 374102 aa57cf8bd96a816eee306dc390ec70acc991b881
parent 373501 25321494921c824703a605127fb1f99b1faf5910
child 522548 c7316113519be4124541752c6801961668b1c320
push id19926
push userydelendik@mozilla.com
push dateWed, 01 Jun 2016 20:17:47 +0000
reviewersluke
bugs1277246
milestone49.0a1
Bug 1277246 - Add support for names section. r?luke MozReview-Commit-ID: D7nuKPRAcpk
js/src/asmjs/Wasm.cpp
js/src/asmjs/WasmBinary.h
js/src/asmjs/WasmModule.cpp
--- 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
@@ -1019,16 +1021,107 @@ DecodeDataSection(JSContext* cx, Decoder
     }
 
     if (!d.finishSection(sectionStart, sectionSize))
         return Fail(cx, d, "data section byte size mismatch");
 
     return true;
 }
 
+// Rejecting names that are empty or contain non-ASCII characters.
+// TODO Extend validation to include valid UTF-8 characters.
+static bool
+IsJSFriendlyName(JSContext* cx, const uint8_t* name, uint32_t nameLength)
+{
+    if (nameLength == 0)
+        return false;
+
+    for (uint32_t i = 0; i < nameLength; i++) {
+        uint8_t ch = name[i];
+        if (ch == 0 || ch >= 128)
+            return false; // non-ASCII character was found.
+    }
+
+    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; // failed to read number of function names
+
+    if (numFuncNames > MaxFuncs)
+        return false; // too many function names
+
+    CacheableCharsVector result;
+    if (!result.resize(numFuncNames))
+        return false;
+
+    for (uint32_t i = 0; i < numFuncNames; i++) {
+        uint32_t funcNameNumBytes;
+        if (!d.readVarU32(&funcNameNumBytes))
+            return false; // expected function name length
+
+        const uint8_t* funcName;
+        if (!d.hasSectionBytesRemain(sectionStart, sectionSize, funcNameNumBytes) ||
+            !d.readBytesRaw(funcNameNumBytes, &funcName))
+            return false; // function name shorter than declared
+
+        if (IsJSFriendlyName(cx, funcName, funcNameNumBytes))
+            result[i] = DuplicateString((const char*)funcName, funcNameNumBytes);
+
+        // Skipping local names.
+        uint32_t numLocals;
+        if (!d.readVarU32(&numLocals))
+            return false; // expected number of locals
+
+        for (uint32_t j = 0; j < numLocals; j++) {
+            uint32_t localNameNumBytes;
+            if (!d.readVarU32(&localNameNumBytes))
+                return false; // expected local name length
+
+            const uint8_t* localName;
+            if (!d.hasSectionBytesRemain(sectionStart, sectionSize, localNameNumBytes) ||
+                !d.readBytesRaw(localNameNumBytes, &localName))
+                return false; // local name shorter than declared
+
+            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;
@@ -1066,16 +1159,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;
--- 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,26 @@ 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 hasSectionBytesRemain(uint32_t sectionStart, uint32_t sectionSize,
+                                            uint32_t remain) {
+        MOZ_ASSERT(currentOffset() >= sectionStart);
+        return currentOffset() - sectionStart <= sectionSize &&
+               remain <= sectionSize - (currentOffset() - sectionStart);
+    }
     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
@@ -1620,24 +1620,27 @@ Module::callImport(JSContext* cx, uint32
     exit.code = jitExitCode;
     exit.baselineScript = script->baselineScript();
     return true;
 }
 
 const char*
 Module::prettyFuncName(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);
+    const char* prettyName = prettyFuncName(funcIndex);
+    if (prettyName)
+        return prettyName;
 
     char* chars = JS_smprintf("wasm-function[%u]", funcIndex);
     if (!chars) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
     owner->reset(chars);