1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
3 *
4 * Copyright 2016 Mozilla Foundation
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19 #include "wasm/WasmDebug.h"
20
21 #include "mozilla/BinarySearch.h"
22
23 #include "ds/Sort.h"
24 #include "gc/FreeOp.h"
25 #include "jit/ExecutableAllocator.h"
26 #include "jit/MacroAssembler.h"
27 #include "util/StringBuffer.h"
28 #include "util/Text.h"
29 #include "vm/Debugger.h"
30 #include "wasm/WasmBinaryToText.h"
31 #include "wasm/WasmInstance.h"
32 #include "wasm/WasmValidate.h"
33
34 using namespace js;
35 using namespace js::jit;
36 using namespace js::wasm;
37
38 using mozilla::BinarySearchIf;
39
searchLineByOffset(JSContext * cx,uint32_t offset,size_t * exprlocIndex)40 bool GeneratedSourceMap::searchLineByOffset(JSContext* cx, uint32_t offset,
41 size_t* exprlocIndex) {
42 MOZ_ASSERT(!exprlocs_.empty());
43 size_t exprlocsLength = exprlocs_.length();
44
45 // Lazily build sorted array for fast log(n) lookup.
46 if (!sortedByOffsetExprLocIndices_) {
47 ExprLocIndexVector scratch;
48 auto indices = MakeUnique<ExprLocIndexVector>();
49 if (!indices || !indices->resize(exprlocsLength) ||
50 !scratch.resize(exprlocsLength)) {
51 ReportOutOfMemory(cx);
52 return false;
53 }
54 sortedByOffsetExprLocIndices_ = Move(indices);
55
56 for (size_t i = 0; i < exprlocsLength; i++)
57 (*sortedByOffsetExprLocIndices_)[i] = i;
58
59 auto compareExprLocViaIndex = [&](uint32_t i, uint32_t j,
60 bool* lessOrEqualp) -> bool {
61 *lessOrEqualp = exprlocs_[i].offset <= exprlocs_[j].offset;
62 return true;
63 };
64 MOZ_ALWAYS_TRUE(MergeSort(sortedByOffsetExprLocIndices_->begin(),
65 exprlocsLength, scratch.begin(),
66 compareExprLocViaIndex));
67 }
68
69 // Allowing non-exact search and if BinarySearchIf returns out-of-bound
70 // index, moving the index to the last index.
71 auto lookupFn = [&](uint32_t i) -> int {
72 const ExprLoc& loc = exprlocs_[i];
73 return offset == loc.offset ? 0 : offset < loc.offset ? -1 : 1;
74 };
75 size_t match;
76 Unused << BinarySearchIf(sortedByOffsetExprLocIndices_->begin(), 0,
77 exprlocsLength, lookupFn, &match);
78 if (match >= exprlocsLength) match = exprlocsLength - 1;
79 *exprlocIndex = (*sortedByOffsetExprLocIndices_)[match];
80 return true;
81 }
82
sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const83 size_t GeneratedSourceMap::sizeOfExcludingThis(
84 mozilla::MallocSizeOf mallocSizeOf) const {
85 size_t size = exprlocs_.sizeOfExcludingThis(mallocSizeOf);
86 if (sortedByOffsetExprLocIndices_)
87 size += sortedByOffsetExprLocIndices_->sizeOfIncludingThis(mallocSizeOf);
88 return size;
89 }
90
DebugState(SharedCode code,const ShareableBytes * maybeBytecode,bool binarySource)91 DebugState::DebugState(SharedCode code, const ShareableBytes* maybeBytecode,
92 bool binarySource)
93 : code_(Move(code)),
94 maybeBytecode_(maybeBytecode),
95 binarySource_(binarySource),
96 enterAndLeaveFrameTrapsCounter_(0) {
97 MOZ_ASSERT_IF(debugEnabled(), maybeBytecode);
98 }
99
100 const char enabledMessage[] =
101 "Restart with developer tools open to view WebAssembly source";
102
103 const char tooBigMessage[] =
104 "Unfortunately, this WebAssembly module is too big to view as text.\n"
105 "We are working hard to remove this limitation.";
106
107 const char notGeneratedMessage[] = "WebAssembly text generation was disabled.";
108
109 static const unsigned TooBig = 1000000;
110
111 static const uint32_t DefaultBinarySourceColumnNumber = 1;
112
SlowCallSiteSearchByOffset(const MetadataTier & metadata,uint32_t offset)113 static const CallSite* SlowCallSiteSearchByOffset(const MetadataTier& metadata,
114 uint32_t offset) {
115 for (const CallSite& callSite : metadata.callSites) {
116 if (callSite.lineOrBytecode() == offset &&
117 callSite.kind() == CallSiteDesc::Breakpoint)
118 return &callSite;
119 }
120 return nullptr;
121 }
122
createText(JSContext * cx)123 JSString* DebugState::createText(JSContext* cx) {
124 StringBuffer buffer(cx);
125 if (!maybeBytecode_) {
126 if (!buffer.append(enabledMessage)) return nullptr;
127
128 MOZ_ASSERT(!maybeSourceMap_);
129 } else if (binarySource_) {
130 if (!buffer.append(notGeneratedMessage)) return nullptr;
131 return buffer.finishString();
132 } else if (maybeBytecode_->bytes.length() > TooBig) {
133 if (!buffer.append(tooBigMessage)) return nullptr;
134
135 MOZ_ASSERT(!maybeSourceMap_);
136 } else {
137 const Bytes& bytes = maybeBytecode_->bytes;
138 auto sourceMap = MakeUnique<GeneratedSourceMap>();
139 if (!sourceMap) {
140 ReportOutOfMemory(cx);
141 return nullptr;
142 }
143 maybeSourceMap_ = Move(sourceMap);
144
145 if (!BinaryToText(cx, bytes.begin(), bytes.length(), buffer,
146 maybeSourceMap_.get()))
147 return nullptr;
148
149 #if DEBUG
150 // Check that expression locations are sorted by line number.
151 uint32_t lastLineno = 0;
152 for (const ExprLoc& loc : maybeSourceMap_->exprlocs()) {
153 MOZ_ASSERT(lastLineno <= loc.lineno);
154 lastLineno = loc.lineno;
155 }
156 #endif
157 }
158
159 return buffer.finishString();
160 }
161
ensureSourceMap(JSContext * cx)162 bool DebugState::ensureSourceMap(JSContext* cx) {
163 if (maybeSourceMap_ || !maybeBytecode_) return true;
164
165 // We just need to cache maybeSourceMap_, ignoring the text result.
166 return createText(cx);
167 }
168
169 struct LineComparator {
170 const uint32_t lineno;
LineComparatorLineComparator171 explicit LineComparator(uint32_t lineno) : lineno(lineno) {}
172
operator ()LineComparator173 int operator()(const ExprLoc& loc) const {
174 return lineno == loc.lineno ? 0 : lineno < loc.lineno ? -1 : 1;
175 }
176 };
177
getLineOffsets(JSContext * cx,size_t lineno,Vector<uint32_t> * offsets)178 bool DebugState::getLineOffsets(JSContext* cx, size_t lineno,
179 Vector<uint32_t>* offsets) {
180 if (!debugEnabled()) return true;
181
182 if (binarySource_) {
183 const CallSite* callsite =
184 SlowCallSiteSearchByOffset(metadata(Tier::Debug), lineno);
185 if (callsite && !offsets->append(lineno)) return false;
186 return true;
187 }
188
189 if (!ensureSourceMap(cx)) return false;
190
191 if (!maybeSourceMap_)
192 return true; // no source text available, keep offsets empty.
193
194 ExprLocVector& exprlocs = maybeSourceMap_->exprlocs();
195
196 // Binary search for the expression with the specified line number and
197 // rewind to the first expression, if more than one expression on the same
198 // line.
199 size_t match;
200 if (!BinarySearchIf(exprlocs, 0, exprlocs.length(), LineComparator(lineno),
201 &match))
202 return true;
203
204 while (match > 0 && exprlocs[match - 1].lineno == lineno) match--;
205
206 // Return all expression offsets that were printed on the specified line.
207 for (size_t i = match; i < exprlocs.length() && exprlocs[i].lineno == lineno;
208 i++) {
209 if (!offsets->append(exprlocs[i].offset)) return false;
210 }
211
212 return true;
213 }
214
getAllColumnOffsets(JSContext * cx,Vector<ExprLoc> * offsets)215 bool DebugState::getAllColumnOffsets(JSContext* cx, Vector<ExprLoc>* offsets) {
216 if (!metadata().debugEnabled) return true;
217
218 if (binarySource_) {
219 for (const CallSite& callSite : metadata(Tier::Debug).callSites) {
220 if (callSite.kind() != CallSite::Breakpoint) continue;
221 uint32_t offset = callSite.lineOrBytecode();
222 if (!offsets->emplaceBack(offset, DefaultBinarySourceColumnNumber,
223 offset))
224 return false;
225 }
226 return true;
227 }
228
229 if (!ensureSourceMap(cx)) return false;
230
231 if (!maybeSourceMap_)
232 return true; // no source text available, keep offsets empty.
233
234 return offsets->appendAll(maybeSourceMap_->exprlocs());
235 }
236
getOffsetLocation(JSContext * cx,uint32_t offset,bool * found,size_t * lineno,size_t * column)237 bool DebugState::getOffsetLocation(JSContext* cx, uint32_t offset, bool* found,
238 size_t* lineno, size_t* column) {
239 *found = false;
240 if (!debugEnabled()) return true;
241
242 if (binarySource_) {
243 if (!SlowCallSiteSearchByOffset(metadata(Tier::Debug), offset))
244 return true; // offset was not found
245 *found = true;
246 *lineno = offset;
247 *column = DefaultBinarySourceColumnNumber;
248 return true;
249 }
250
251 if (!ensureSourceMap(cx)) return false;
252
253 if (!maybeSourceMap_ || maybeSourceMap_->exprlocs().empty())
254 return true; // no source text available
255
256 size_t foundAt;
257 if (!maybeSourceMap_->searchLineByOffset(cx, offset, &foundAt)) return false;
258
259 const ExprLoc& loc = maybeSourceMap_->exprlocs()[foundAt];
260 *found = true;
261 *lineno = loc.lineno;
262 *column = loc.column;
263 return true;
264 }
265
totalSourceLines(JSContext * cx,uint32_t * count)266 bool DebugState::totalSourceLines(JSContext* cx, uint32_t* count) {
267 *count = 0;
268 if (!debugEnabled()) return true;
269
270 if (binarySource_) {
271 if (maybeBytecode_) *count = maybeBytecode_->length();
272 return true;
273 }
274
275 if (!ensureSourceMap(cx)) return false;
276
277 if (maybeSourceMap_) *count = maybeSourceMap_->totalLines();
278 return true;
279 }
280
stepModeEnabled(uint32_t funcIndex) const281 bool DebugState::stepModeEnabled(uint32_t funcIndex) const {
282 return stepModeCounters_.initialized() && stepModeCounters_.lookup(funcIndex);
283 }
284
incrementStepModeCount(JSContext * cx,uint32_t funcIndex)285 bool DebugState::incrementStepModeCount(JSContext* cx, uint32_t funcIndex) {
286 MOZ_ASSERT(debugEnabled());
287 const CodeRange& codeRange =
288 codeRanges(Tier::Debug)[debugFuncToCodeRangeIndex(funcIndex)];
289 MOZ_ASSERT(codeRange.isFunction());
290
291 if (!stepModeCounters_.initialized() && !stepModeCounters_.init()) {
292 ReportOutOfMemory(cx);
293 return false;
294 }
295
296 StepModeCounters::AddPtr p = stepModeCounters_.lookupForAdd(funcIndex);
297 if (p) {
298 MOZ_ASSERT(p->value() > 0);
299 p->value()++;
300 return true;
301 }
302 if (!stepModeCounters_.add(p, funcIndex, 1)) {
303 ReportOutOfMemory(cx);
304 return false;
305 }
306
307 AutoWritableJitCode awjc(
308 cx->runtime(), code_->segment(Tier::Debug).base() + codeRange.begin(),
309 codeRange.end() - codeRange.begin());
310 AutoFlushICache afc("Code::incrementStepModeCount");
311
312 for (const CallSite& callSite : callSites(Tier::Debug)) {
313 if (callSite.kind() != CallSite::Breakpoint) continue;
314 uint32_t offset = callSite.returnAddressOffset();
315 if (codeRange.begin() <= offset && offset <= codeRange.end())
316 toggleDebugTrap(offset, true);
317 }
318 return true;
319 }
320
decrementStepModeCount(FreeOp * fop,uint32_t funcIndex)321 bool DebugState::decrementStepModeCount(FreeOp* fop, uint32_t funcIndex) {
322 MOZ_ASSERT(debugEnabled());
323 const CodeRange& codeRange =
324 codeRanges(Tier::Debug)[debugFuncToCodeRangeIndex(funcIndex)];
325 MOZ_ASSERT(codeRange.isFunction());
326
327 MOZ_ASSERT(stepModeCounters_.initialized() && !stepModeCounters_.empty());
328 StepModeCounters::Ptr p = stepModeCounters_.lookup(funcIndex);
329 MOZ_ASSERT(p);
330 if (--p->value()) return true;
331
332 stepModeCounters_.remove(p);
333
334 AutoWritableJitCode awjc(
335 fop->runtime(), code_->segment(Tier::Debug).base() + codeRange.begin(),
336 codeRange.end() - codeRange.begin());
337 AutoFlushICache afc("Code::decrementStepModeCount");
338
339 for (const CallSite& callSite : callSites(Tier::Debug)) {
340 if (callSite.kind() != CallSite::Breakpoint) continue;
341 uint32_t offset = callSite.returnAddressOffset();
342 if (codeRange.begin() <= offset && offset <= codeRange.end()) {
343 bool enabled =
344 breakpointSites_.initialized() && breakpointSites_.has(offset);
345 toggleDebugTrap(offset, enabled);
346 }
347 }
348 return true;
349 }
350
hasBreakpointTrapAtOffset(uint32_t offset)351 bool DebugState::hasBreakpointTrapAtOffset(uint32_t offset) {
352 if (!debugEnabled()) return false;
353 return SlowCallSiteSearchByOffset(metadata(Tier::Debug), offset);
354 }
355
toggleBreakpointTrap(JSRuntime * rt,uint32_t offset,bool enabled)356 void DebugState::toggleBreakpointTrap(JSRuntime* rt, uint32_t offset,
357 bool enabled) {
358 MOZ_ASSERT(debugEnabled());
359 const CallSite* callSite =
360 SlowCallSiteSearchByOffset(metadata(Tier::Debug), offset);
361 if (!callSite) return;
362 size_t debugTrapOffset = callSite->returnAddressOffset();
363
364 const ModuleSegment& codeSegment = code_->segment(Tier::Debug);
365 const CodeRange* codeRange =
366 code_->lookupFuncRange(codeSegment.base() + debugTrapOffset);
367 MOZ_ASSERT(codeRange);
368
369 if (stepModeCounters_.initialized() &&
370 stepModeCounters_.lookup(codeRange->funcIndex()))
371 return; // no need to toggle when step mode is enabled
372
373 AutoWritableJitCode awjc(rt, codeSegment.base(), codeSegment.length());
374 AutoFlushICache afc("Code::toggleBreakpointTrap");
375 AutoFlushICache::setRange(uintptr_t(codeSegment.base()),
376 codeSegment.length());
377 toggleDebugTrap(debugTrapOffset, enabled);
378 }
379
getOrCreateBreakpointSite(JSContext * cx,uint32_t offset)380 WasmBreakpointSite* DebugState::getOrCreateBreakpointSite(JSContext* cx,
381 uint32_t offset) {
382 WasmBreakpointSite* site;
383 if (!breakpointSites_.initialized() && !breakpointSites_.init()) {
384 ReportOutOfMemory(cx);
385 return nullptr;
386 }
387
388 WasmBreakpointSiteMap::AddPtr p = breakpointSites_.lookupForAdd(offset);
389 if (!p) {
390 site = cx->zone()->new_<WasmBreakpointSite>(this, offset);
391 if (!site || !breakpointSites_.add(p, offset, site)) {
392 js_delete(site);
393 ReportOutOfMemory(cx);
394 return nullptr;
395 }
396 } else {
397 site = p->value();
398 }
399 return site;
400 }
401
hasBreakpointSite(uint32_t offset)402 bool DebugState::hasBreakpointSite(uint32_t offset) {
403 return breakpointSites_.initialized() && breakpointSites_.has(offset);
404 }
405
destroyBreakpointSite(FreeOp * fop,uint32_t offset)406 void DebugState::destroyBreakpointSite(FreeOp* fop, uint32_t offset) {
407 MOZ_ASSERT(breakpointSites_.initialized());
408 WasmBreakpointSiteMap::Ptr p = breakpointSites_.lookup(offset);
409 MOZ_ASSERT(p);
410 fop->delete_(p->value());
411 breakpointSites_.remove(p);
412 }
413
clearBreakpointsIn(JSContext * cx,WasmInstanceObject * instance,js::Debugger * dbg,JSObject * handler)414 bool DebugState::clearBreakpointsIn(JSContext* cx, WasmInstanceObject* instance,
415 js::Debugger* dbg, JSObject* handler) {
416 MOZ_ASSERT(instance);
417 if (!breakpointSites_.initialized()) return true;
418
419 // Make copy of all sites list, so breakpointSites_ can be modified by
420 // destroyBreakpointSite calls.
421 Vector<WasmBreakpointSite*> sites(cx);
422 if (!sites.resize(breakpointSites_.count())) return false;
423 size_t i = 0;
424 for (WasmBreakpointSiteMap::Range r = breakpointSites_.all(); !r.empty();
425 r.popFront())
426 sites[i++] = r.front().value();
427
428 for (WasmBreakpointSite* site : sites) {
429 Breakpoint* nextbp;
430 for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = nextbp) {
431 nextbp = bp->nextInSite();
432 if (bp->asWasm()->wasmInstance == instance &&
433 (!dbg || bp->debugger == dbg) &&
434 (!handler || bp->getHandler() == handler)) {
435 bp->destroy(cx->runtime()->defaultFreeOp());
436 }
437 }
438 }
439 return true;
440 }
441
toggleDebugTrap(uint32_t offset,bool enabled)442 void DebugState::toggleDebugTrap(uint32_t offset, bool enabled) {
443 MOZ_ASSERT(offset);
444 uint8_t* trap = code_->segment(Tier::Debug).base() + offset;
445 const Uint32Vector& farJumpOffsets =
446 metadata(Tier::Debug).debugTrapFarJumpOffsets;
447 if (enabled) {
448 MOZ_ASSERT(farJumpOffsets.length() > 0);
449 size_t i = 0;
450 while (i < farJumpOffsets.length() && offset < farJumpOffsets[i]) i++;
451 if (i >= farJumpOffsets.length() ||
452 (i > 0 && offset - farJumpOffsets[i - 1] < farJumpOffsets[i] - offset))
453 i--;
454 uint8_t* farJump = code_->segment(Tier::Debug).base() + farJumpOffsets[i];
455 MacroAssembler::patchNopToCall(trap, farJump);
456 } else {
457 MacroAssembler::patchCallToNop(trap);
458 }
459 }
460
adjustEnterAndLeaveFrameTrapsState(JSContext * cx,bool enabled)461 void DebugState::adjustEnterAndLeaveFrameTrapsState(JSContext* cx,
462 bool enabled) {
463 MOZ_ASSERT(debugEnabled());
464 MOZ_ASSERT_IF(!enabled, enterAndLeaveFrameTrapsCounter_ > 0);
465
466 bool wasEnabled = enterAndLeaveFrameTrapsCounter_ > 0;
467 if (enabled)
468 ++enterAndLeaveFrameTrapsCounter_;
469 else
470 --enterAndLeaveFrameTrapsCounter_;
471 bool stillEnabled = enterAndLeaveFrameTrapsCounter_ > 0;
472 if (wasEnabled == stillEnabled) return;
473
474 const ModuleSegment& codeSegment = code_->segment(Tier::Debug);
475 AutoWritableJitCode awjc(cx->runtime(), codeSegment.base(),
476 codeSegment.length());
477 AutoFlushICache afc("Code::adjustEnterAndLeaveFrameTrapsState");
478 AutoFlushICache::setRange(uintptr_t(codeSegment.base()),
479 codeSegment.length());
480 for (const CallSite& callSite : callSites(Tier::Debug)) {
481 if (callSite.kind() != CallSite::EnterFrame &&
482 callSite.kind() != CallSite::LeaveFrame)
483 continue;
484 toggleDebugTrap(callSite.returnAddressOffset(), stillEnabled);
485 }
486 }
487
debugGetLocalTypes(uint32_t funcIndex,ValTypeVector * locals,size_t * argsLength)488 bool DebugState::debugGetLocalTypes(uint32_t funcIndex, ValTypeVector* locals,
489 size_t* argsLength) {
490 MOZ_ASSERT(debugEnabled());
491
492 const ValTypeVector& args = metadata().debugFuncArgTypes[funcIndex];
493 *argsLength = args.length();
494 if (!locals->appendAll(args)) return false;
495
496 // Decode local var types from wasm binary function body.
497 const CodeRange& range =
498 codeRanges(Tier::Debug)[debugFuncToCodeRangeIndex(funcIndex)];
499 // In wasm, the Code points to the function start via funcLineOrBytecode.
500 MOZ_ASSERT(!metadata().isAsmJS() && maybeBytecode_);
501 size_t offsetInModule = range.funcLineOrBytecode();
502 Decoder d(maybeBytecode_->begin() + offsetInModule, maybeBytecode_->end(),
503 offsetInModule, /* error = */ nullptr);
504 return DecodeLocalEntries(d, metadata().kind, locals);
505 }
506
debugGetResultType(uint32_t funcIndex)507 ExprType DebugState::debugGetResultType(uint32_t funcIndex) {
508 MOZ_ASSERT(debugEnabled());
509 return metadata().debugFuncReturnTypes[funcIndex];
510 }
511
getGlobal(Instance & instance,uint32_t globalIndex,MutableHandleValue vp)512 bool DebugState::getGlobal(Instance& instance, uint32_t globalIndex,
513 MutableHandleValue vp) {
514 const GlobalDesc& global = metadata().globals[globalIndex];
515
516 if (global.isConstant()) {
517 Val value = global.constantValue();
518 switch (value.type()) {
519 case ValType::I32:
520 vp.set(Int32Value(value.i32()));
521 break;
522 case ValType::I64:
523 // Just display as a Number; it's ok if we lose some precision
524 vp.set(NumberValue((double)value.i64()));
525 break;
526 case ValType::F32:
527 vp.set(NumberValue(JS::CanonicalizeNaN(value.f32())));
528 break;
529 case ValType::F64:
530 vp.set(NumberValue(JS::CanonicalizeNaN(value.f64())));
531 break;
532 default:
533 MOZ_CRASH("Global constant type");
534 }
535 return true;
536 }
537
538 uint8_t* globalData = instance.globalData();
539 void* dataPtr = globalData + global.offset();
540 switch (global.type()) {
541 case ValType::I32: {
542 vp.set(Int32Value(*static_cast<int32_t*>(dataPtr)));
543 break;
544 }
545 case ValType::I64: {
546 // Just display as a Number; it's ok if we lose some precision
547 vp.set(NumberValue((double)*static_cast<int64_t*>(dataPtr)));
548 break;
549 }
550 case ValType::F32: {
551 vp.set(NumberValue(JS::CanonicalizeNaN(*static_cast<float*>(dataPtr))));
552 break;
553 }
554 case ValType::F64: {
555 vp.set(NumberValue(JS::CanonicalizeNaN(*static_cast<double*>(dataPtr))));
556 break;
557 }
558 default:
559 MOZ_CRASH("Global variable type");
560 break;
561 }
562 return true;
563 }
564
debugDisplayURL(JSContext * cx) const565 JSString* DebugState::debugDisplayURL(JSContext* cx) const {
566 // Build wasm module URL from following parts:
567 // - "wasm:" as protocol;
568 // - URI encoded filename from metadata (if can be encoded), plus ":";
569 // - 64-bit hash of the module bytes (as hex dump).
570
571 js::StringBuffer result(cx);
572 if (!result.append("wasm:")) return nullptr;
573
574 if (const char* filename = metadata().filename.get()) {
575 js::StringBuffer filenamePrefix(cx);
576 // EncodeURI returns false due to invalid chars or OOM -- fail only
577 // during OOM.
578 if (!EncodeURI(cx, filenamePrefix, filename, strlen(filename))) {
579 if (!cx->isExceptionPending()) return nullptr;
580 cx->clearPendingException(); // ignore invalid URI
581 } else if (!result.append(filenamePrefix.finishString())) {
582 return nullptr;
583 }
584 }
585
586 if (metadata().debugEnabled) {
587 if (!result.append(":")) return nullptr;
588
589 const ModuleHash& hash = metadata().debugHash;
590 for (size_t i = 0; i < sizeof(ModuleHash); i++) {
591 char digit1 = hash[i] / 16, digit2 = hash[i] % 16;
592 if (!result.append(
593 (char)(digit1 < 10 ? digit1 + '0' : digit1 + 'a' - 10)))
594 return nullptr;
595 if (!result.append(
596 (char)(digit2 < 10 ? digit2 + '0' : digit2 + 'a' - 10)))
597 return nullptr;
598 }
599 }
600
601 return result.finishString();
602 }
603
getSourceMappingURL(JSContext * cx,MutableHandleString result) const604 bool DebugState::getSourceMappingURL(JSContext* cx,
605 MutableHandleString result) const {
606 result.set(nullptr);
607 if (!maybeBytecode_) return true;
608
609 for (const CustomSection& customSection : metadata().customSections) {
610 const NameInBytecode& sectionName = customSection.name;
611 if (strlen(SourceMappingURLSectionName) != sectionName.length ||
612 memcmp(SourceMappingURLSectionName,
613 maybeBytecode_->begin() + sectionName.offset,
614 sectionName.length) != 0) {
615 continue;
616 }
617
618 // Parse found "SourceMappingURL" custom section.
619 Decoder d(
620 maybeBytecode_->begin() + customSection.offset,
621 maybeBytecode_->begin() + customSection.offset + customSection.length,
622 customSection.offset,
623 /* error = */ nullptr);
624 uint32_t nchars;
625 if (!d.readVarU32(&nchars)) return true; // ignoring invalid section data
626 const uint8_t* chars;
627 if (!d.readBytes(nchars, &chars) || d.currentPosition() != d.end())
628 return true; // ignoring invalid section data
629
630 UTF8Chars utf8Chars(reinterpret_cast<const char*>(chars), nchars);
631 JSString* str = JS_NewStringCopyUTF8N(cx, utf8Chars);
632 if (!str) return false;
633 result.set(str);
634 return true;
635 }
636
637 // Check presence of "SourceMap:" HTTP response header.
638 char* sourceMapURL = metadata().sourceMapURL.get();
639 if (sourceMapURL && strlen(sourceMapURL)) {
640 UTF8Chars utf8Chars(sourceMapURL, strlen(sourceMapURL));
641 JSString* str = JS_NewStringCopyUTF8N(cx, utf8Chars);
642 if (!str) return false;
643 result.set(str);
644 }
645 return true;
646 }
647
addSizeOfMisc(MallocSizeOf mallocSizeOf,Metadata::SeenSet * seenMetadata,ShareableBytes::SeenSet * seenBytes,Code::SeenSet * seenCode,size_t * code,size_t * data) const648 void DebugState::addSizeOfMisc(MallocSizeOf mallocSizeOf,
649 Metadata::SeenSet* seenMetadata,
650 ShareableBytes::SeenSet* seenBytes,
651 Code::SeenSet* seenCode, size_t* code,
652 size_t* data) const {
653 code_->addSizeOfMiscIfNotSeen(mallocSizeOf, seenMetadata, seenCode, code,
654 data);
655 if (maybeSourceMap_)
656 *data += maybeSourceMap_->sizeOfExcludingThis(mallocSizeOf);
657 if (maybeBytecode_)
658 *data +=
659 maybeBytecode_->sizeOfIncludingThisIfNotSeen(mallocSizeOf, seenBytes);
660 }
661