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
--- 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);