Bug 1355263 - Generate better source URL for the wasm module. r?luke draft
authorYury Delendik <ydelendik@mozilla.com>
Mon, 10 Apr 2017 19:08:07 -0500
changeset 567231 4b0b64db0f224814f0fe72aead71ad2a80345cba
parent 567163 62b649c6b314f756f21cb95f2b0d491e2664e944
child 625559 a900d8deeff1432a3bcf7511218b36c1c8a56bce
push id55479
push userydelendik@mozilla.com
push dateMon, 24 Apr 2017 14:35:51 +0000
reviewersluke
bugs1355263
milestone55.0a1
Bug 1355263 - Generate better source URL for the wasm module. r?luke The URLs will have the following format: wasm: [<uri-econded-filename-of-host> ":"] <64-bit-hash> MozReview-Commit-ID: 3diYgRWhki1
devtools/server/tests/unit/test_frameactor_wasm-01.js
js/src/jit-test/tests/debug/wasm-12.js
js/src/jsstr.cpp
js/src/jsstr.h
js/src/vm/Debugger.cpp
js/src/wasm/WasmCode.cpp
js/src/wasm/WasmCode.h
js/src/wasm/WasmGenerator.cpp
js/src/wasm/WasmGenerator.h
--- a/devtools/server/tests/unit/test_frameactor_wasm-01.js
+++ b/devtools/server/tests/unit/test_frameactor_wasm-01.js
@@ -42,17 +42,17 @@ function test_pause_frame() {
 
       let wasmFrame = frameResponse.frames[1];
       do_check_eq(wasmFrame.type, "wasmcall");
       do_check_eq(wasmFrame.this, undefined);
 
       let location = wasmFrame.where;
       do_check_eq(location.line > 0, true);
       do_check_eq(location.column > 0, true);
-      do_check_eq(location.source.url.endsWith(" > wasm"), true);
+      do_check_eq(/^wasm:(?:[^:]*:)*?[0-9a-f]{16}$/.test(location.source.url), true);
 
       finishClient(gClient);
     });
   });
 
   /* eslint-disable comma-spacing, max-len */
   gDebuggee.eval("(" + function () {
     // WebAssembly bytecode was generated by running:
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-12.js
@@ -0,0 +1,26 @@
+// Tests that wasm module scripts have special URLs.
+
+if (!wasmIsSupported())
+  quit();
+
+var g = newGlobal();
+g.eval(`
+function initWasm(s) { return new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(s))); }
+o = initWasm('(module (func) (export "" 0))');
+o = initWasm('(module (func) (func) (export "" 1))');
+`);
+
+function isWasm(script) { return script.format === "wasm"; }
+
+function isValidWasmURL(url) {
+   // The URLs will have the following format:
+   //   wasm: [<uri-econded-filename-of-host> ":"] <64-bit-hash>
+   return /^wasm:(?:[^:]*:)*?[0-9a-f]{16}$/.test(url);
+}
+
+var dbg = new Debugger(g);
+var foundScripts = dbg.findScripts().filter(isWasm);
+assertEq(foundScripts.length, 2);
+assertEq(isValidWasmURL(foundScripts[0].source.url), true);
+assertEq(isValidWasmURL(foundScripts[1].source.url), true);
+assertEq(foundScripts[0].source.url != foundScripts[1].source.url, true);
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -4238,16 +4238,29 @@ str_encodeURI_Component(JSContext* cx, u
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedLinearString str(cx, ArgToRootedString(cx, args, 0));
     if (!str)
         return false;
 
     return Encode(cx, str, js_isUriUnescaped, nullptr, args.rval());
 }
 
+bool
+js::EncodeURI(JSContext* cx, StringBuffer& sb, const char* chars, size_t length)
+{
+    EncodeResult result = Encode(sb, chars, length, js_isUriUnescaped, js_isUriReservedPlusPound);
+    if (result == EncodeResult::Encode_Failure)
+        return false;
+    if (result == EncodeResult::Encode_BadUri) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_URI);
+        return false;
+    }
+    return true;
+}
+
 /*
  * Convert one UCS-4 char and write it into a UTF-8 buffer, which must be at
  * least 4 bytes long.  Return the number of UTF-8 bytes of data written.
  */
 uint32_t
 js::OneUcs4ToUtf8Char(uint8_t* utf8Buffer, uint32_t ucs4Char)
 {
     MOZ_ASSERT(ucs4Char <= unicode::NonBMPMax);
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -464,16 +464,19 @@ inline bool
 FileEscapedString(FILE* fp, const char* chars, size_t length, uint32_t quote)
 {
     Fprinter out(fp);
     bool res = EscapedStringPrinter(out, chars, length, quote);
     out.finish();
     return res;
 }
 
+bool
+EncodeURI(JSContext* cx, StringBuffer& sb, const char* chars, size_t length);
+
 JSObject*
 str_split_string(JSContext* cx, HandleObjectGroup group, HandleString str, HandleString sep,
                  uint32_t limit);
 
 JSString *
 str_flat_replace_string(JSContext *cx, HandleString string, HandleString pattern,
                         HandleString replacement);
 
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -7280,25 +7280,19 @@ class DebuggerSourceGetURLMatcher
         MOZ_ASSERT(ss);
         if (ss->filename()) {
             JSString* str = NewStringCopyZ<CanGC>(cx_, ss->filename());
             return Some(str);
         }
         return Nothing();
     }
     ReturnType match(Handle<WasmInstanceObject*> wasmInstance) {
-        // TODOshu: Until wasm modules have real URLs, append "> wasm" to the
-        // end to prevent them from being blacklisted by devtools by having
-        // the same value as a source mapped URL.
-        char* buf = JS_smprintf("%s > wasm", wasmInstance->instance().metadata().filename.get());
-        if (!buf)
-            return Nothing();
-        JSString* str = NewStringCopyZ<CanGC>(cx_, buf);
-        JS_smprintf_free(buf);
-        return Some(str);
+        if (JSString* str = wasmInstance->instance().code().debugDisplayURL(cx_))
+            return Some(str);
+        return Nothing();
     }
 };
 
 static bool
 DebuggerSource_getURL(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get url)", args, obj, referent);
 
--- a/js/src/wasm/WasmCode.cpp
+++ b/js/src/wasm/WasmCode.cpp
@@ -358,17 +358,18 @@ Metadata::serializedSize() const
            SerializedVectorSize(sigIds) +
            SerializedPodVectorSize(globals) +
            SerializedPodVectorSize(tables) +
            SerializedPodVectorSize(memoryAccesses) +
            SerializedPodVectorSize(codeRanges) +
            SerializedPodVectorSize(callSites) +
            SerializedPodVectorSize(funcNames) +
            SerializedPodVectorSize(customSections) +
-           filename.serializedSize();
+           filename.serializedSize() +
+           sizeof(hash);
 }
 
 uint8_t*
 Metadata::serialize(uint8_t* cursor) const
 {
     MOZ_ASSERT(!debugEnabled && debugTrapFarJumpOffsets.empty() &&
                debugFuncArgTypes.empty() && debugFuncReturnTypes.empty() &&
                debugFuncToCodeRange.empty());
@@ -379,16 +380,17 @@ Metadata::serialize(uint8_t* cursor) con
     cursor = SerializePodVector(cursor, globals);
     cursor = SerializePodVector(cursor, tables);
     cursor = SerializePodVector(cursor, memoryAccesses);
     cursor = SerializePodVector(cursor, codeRanges);
     cursor = SerializePodVector(cursor, callSites);
     cursor = SerializePodVector(cursor, funcNames);
     cursor = SerializePodVector(cursor, customSections);
     cursor = filename.serialize(cursor);
+    cursor = WriteBytes(cursor, hash, sizeof(hash));
     return cursor;
 }
 
 /* static */ const uint8_t*
 Metadata::deserialize(const uint8_t* cursor)
 {
     (cursor = ReadBytes(cursor, &pod(), sizeof(pod()))) &&
     (cursor = DeserializeVector(cursor, &funcImports)) &&
@@ -396,17 +398,18 @@ Metadata::deserialize(const uint8_t* cur
     (cursor = DeserializeVector(cursor, &sigIds)) &&
     (cursor = DeserializePodVector(cursor, &globals)) &&
     (cursor = DeserializePodVector(cursor, &tables)) &&
     (cursor = DeserializePodVector(cursor, &memoryAccesses)) &&
     (cursor = DeserializePodVector(cursor, &codeRanges)) &&
     (cursor = DeserializePodVector(cursor, &callSites)) &&
     (cursor = DeserializePodVector(cursor, &funcNames)) &&
     (cursor = DeserializePodVector(cursor, &customSections)) &&
-    (cursor = filename.deserialize(cursor));
+    (cursor = filename.deserialize(cursor)) &&
+    (cursor = ReadBytes(cursor, hash, sizeof(hash)));
     debugEnabled = false;
     debugTrapFarJumpOffsets.clear();
     debugFuncToCodeRange.clear();
     debugFuncArgTypes.clear();
     debugFuncReturnTypes.clear();
     return cursor;
 }
 
@@ -1060,16 +1063,51 @@ Code::debugGetLocalTypes(uint32_t funcIn
 
 ExprType
 Code::debugGetResultType(uint32_t funcIndex)
 {
     MOZ_ASSERT(metadata_->debugEnabled);
     return metadata_->debugFuncReturnTypes[funcIndex];
 }
 
+JSString*
+Code::debugDisplayURL(JSContext* cx) const
+{
+    // Build wasm module URL from following parts:
+    // - "wasm:" as protocol;
+    // - URI encoded filename from metadata (if can be encoded), plus ":";
+    // - 64-bit hash of the module bytes (as hex dump).
+    js::StringBuffer result(cx);
+    if (!result.append("wasm:"))
+        return nullptr;
+    if (const char* filename = metadata_->filename.get()) {
+        js::StringBuffer filenamePrefix(cx);
+        // EncodeURI returns false due to invalid chars or OOM -- fail only
+        // during OOM.
+        if (!EncodeURI(cx, filenamePrefix, filename, strlen(filename))) {
+            if (!cx->isExceptionPending())
+                return nullptr;
+            cx->clearPendingException(); // ignore invalid URI
+        } else if (!result.append(filenamePrefix.finishString()) || !result.append(":")) {
+            return nullptr;
+        }
+    }
+
+    const ModuleHash& hash = metadata().hash;
+    for (size_t i = 0; i < sizeof(ModuleHash); i++) {
+        char digit1 = hash[i] / 16, digit2 = hash[i] % 16;
+        if (!result.append((char)(digit1 < 10 ? digit1 + '0' : digit1 + 'a' - 10)))
+            return nullptr;
+        if (!result.append((char)(digit2 < 10 ? digit2 + '0' : digit2 + 'a' - 10)))
+            return nullptr;
+    }
+    return result.finishString();
+
+}
+
 void
 Code::addSizeOfMisc(MallocSizeOf mallocSizeOf,
                     Metadata::SeenSet* seenMetadata,
                     ShareableBytes::SeenSet* seenBytes,
                     size_t* code,
                     size_t* data) const
 {
     *code += segment_->length();
--- a/js/src/wasm/WasmCode.h
+++ b/js/src/wasm/WasmCode.h
@@ -287,16 +287,18 @@ struct MetadataCacheablePod
 
     explicit MetadataCacheablePod(ModuleKind kind)
       : kind(kind),
         memoryUsage(MemoryUsage::None),
         minMemoryLength(0)
     {}
 };
 
+typedef uint8_t ModuleHash[8];
+
 struct Metadata : ShareableBase<Metadata>, MetadataCacheablePod
 {
     explicit Metadata(ModuleKind kind = ModuleKind::Wasm) : MetadataCacheablePod(kind) {}
     virtual ~Metadata() {}
 
     MetadataCacheablePod& pod() { return *this; }
     const MetadataCacheablePod& pod() const { return *this; }
 
@@ -306,16 +308,17 @@ struct Metadata : ShareableBase<Metadata
     GlobalDescVector      globals;
     TableDescVector       tables;
     MemoryAccessVector    memoryAccesses;
     CodeRangeVector       codeRanges;
     CallSiteVector        callSites;
     NameInBytecodeVector  funcNames;
     CustomSectionVector   customSections;
     CacheableChars        filename;
+    ModuleHash            hash;
 
     // Debug-enabled code is not serialized.
     bool                  debugEnabled;
     Uint32Vector          debugTrapFarJumpOffsets;
     Uint32Vector          debugFuncToCodeRange;
     FuncArgTypesVector    debugFuncArgTypes;
     FuncReturnTypesVector debugFuncReturnTypes;
 
@@ -477,16 +480,20 @@ class Code
     bool incrementStepModeCount(JSContext* cx, uint32_t funcIndex);
     bool decrementStepModeCount(JSContext* cx, uint32_t funcIndex);
 
     // Stack inspection helpers.
 
     bool debugGetLocalTypes(uint32_t funcIndex, ValTypeVector* locals, size_t* argsLength);
     ExprType debugGetResultType(uint32_t funcIndex);
 
+    // Debug URL helpers.
+
+    JSString* debugDisplayURL(JSContext* cx) const;
+
     // about:memory reporting:
 
     void addSizeOfMisc(MallocSizeOf mallocSizeOf,
                        Metadata::SeenSet* seenMetadata,
                        ShareableBytes::SeenSet* seenBytes,
                        size_t* code,
                        size_t* data) const;
 
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -15,16 +15,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "wasm/WasmGenerator.h"
 
 #include "mozilla/CheckedInt.h"
 #include "mozilla/EnumeratedRange.h"
+#include "mozilla/SHA1.h"
 
 #include <algorithm>
 
 #include "wasm/WasmBaselineCompile.h"
 #include "wasm/WasmCompile.h"
 #include "wasm/WasmIonCompile.h"
 #include "wasm/WasmStubs.h"
 
@@ -1092,16 +1093,29 @@ ModuleGenerator::initSigTableElems(uint3
     InitExpr offset(Val(uint32_t(0)));
     if (!env_->elemSegments.emplaceBack(tableIndex, offset, Move(elemFuncIndices)))
         return false;
 
     env_->elemSegments.back().elemCodeRangeIndices = Move(codeRangeIndices);
     return true;
 }
 
+static_assert(sizeof(ModuleHash) <= sizeof(mozilla::SHA1Sum::Hash),
+              "The ModuleHash size shall not exceed the SHA1 hash size.");
+
+void
+ModuleGenerator::generateBytecodeHash(const ShareableBytes& bytecode)
+{
+    mozilla::SHA1Sum::Hash hash;
+    mozilla::SHA1Sum sha1Sum;
+    sha1Sum.update(bytecode.begin(), bytecode.length());
+    sha1Sum.finish(hash);
+    memcpy(metadata_->hash, hash, sizeof(ModuleHash));
+}
+
 SharedModule
 ModuleGenerator::finish(const ShareableBytes& bytecode)
 {
     MOZ_ASSERT(!activeFuncDef_);
     MOZ_ASSERT(finishedFuncDefs_);
 
     if (!finishFuncExports())
         return nullptr;
@@ -1180,16 +1194,18 @@ ModuleGenerator::finish(const ShareableB
         MOZ_ASSERT(debugTrapFarJumpOffset >= lastOffset);
         lastOffset = debugTrapFarJumpOffset;
     }
 #endif
 
     if (!finishLinkData())
         return nullptr;
 
+    generateBytecodeHash(bytecode);
+
     return SharedModule(js_new<Module>(Move(assumptions_),
                                        Move(code),
                                        Move(linkData_),
                                        Move(env_->imports),
                                        Move(env_->exports),
                                        Move(env_->dataSegments),
                                        Move(env_->elemSegments),
                                        *metadata_,
--- a/js/src/wasm/WasmGenerator.h
+++ b/js/src/wasm/WasmGenerator.h
@@ -259,16 +259,17 @@ class MOZ_STACK_CLASS ModuleGenerator
     uint32_t numFuncImports() const;
     MOZ_MUST_USE bool patchCallSites();
     MOZ_MUST_USE bool patchFarJumps(const TrapExitOffsetArray& trapExits, const Offsets& debugTrapStub);
     MOZ_MUST_USE bool finishTask(CompileTask* task);
     MOZ_MUST_USE bool finishOutstandingTask();
     MOZ_MUST_USE bool finishFuncExports();
     MOZ_MUST_USE bool finishCodegen();
     MOZ_MUST_USE bool finishLinkData();
+    void generateBytecodeHash(const ShareableBytes& bytecode);
     MOZ_MUST_USE bool addFuncImport(const Sig& sig, uint32_t globalDataOffset);
     MOZ_MUST_USE bool allocateGlobalBytes(uint32_t bytes, uint32_t align, uint32_t* globalDataOff);
     MOZ_MUST_USE bool allocateGlobal(GlobalDesc* global);
 
     MOZ_MUST_USE bool launchBatchCompile();
 
     MOZ_MUST_USE bool initAsmJS(Metadata* asmJSMetadata);
     MOZ_MUST_USE bool initWasm(const CompileArgs& args);