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