Line data Source code
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 : * This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : /*
8 : * JS bytecode descriptors, disassemblers, and (expression) decompilers.
9 : */
10 :
11 : #include "jsopcodeinlines.h"
12 :
13 : #define __STDC_FORMAT_MACROS
14 :
15 : #include "mozilla/Attributes.h"
16 : #include "mozilla/SizePrintfMacros.h"
17 : #include "mozilla/Sprintf.h"
18 : #include "mozilla/Vector.h"
19 :
20 : #include <algorithm>
21 : #include <ctype.h>
22 : #include <inttypes.h>
23 : #include <stdio.h>
24 : #include <string.h>
25 :
26 : #include "jsapi.h"
27 : #include "jsatom.h"
28 : #include "jscntxt.h"
29 : #include "jscompartment.h"
30 : #include "jsfun.h"
31 : #include "jsnum.h"
32 : #include "jsobj.h"
33 : #include "jsprf.h"
34 : #include "jsscript.h"
35 : #include "jsstr.h"
36 : #include "jstypes.h"
37 : #include "jsutil.h"
38 :
39 : #include "frontend/BytecodeCompiler.h"
40 : #include "frontend/SourceNotes.h"
41 : #include "gc/GCInternals.h"
42 : #include "js/CharacterEncoding.h"
43 : #include "vm/CodeCoverage.h"
44 : #include "vm/EnvironmentObject.h"
45 : #include "vm/Opcodes.h"
46 : #include "vm/Shape.h"
47 : #include "vm/StringBuffer.h"
48 :
49 : #include "jscntxtinlines.h"
50 : #include "jscompartmentinlines.h"
51 : #include "jsobjinlines.h"
52 : #include "jsscriptinlines.h"
53 :
54 : using namespace js;
55 : using namespace js::gc;
56 :
57 : using JS::AutoCheckCannotGC;
58 :
59 : using js::frontend::IsIdentifier;
60 :
61 : /*
62 : * Index limit must stay within 32 bits.
63 : */
64 : JS_STATIC_ASSERT(sizeof(uint32_t) * JS_BITS_PER_BYTE >= INDEX_LIMIT_LOG2 + 1);
65 :
66 : const JSCodeSpec js::CodeSpec[] = {
67 : #define MAKE_CODESPEC(op,val,name,token,length,nuses,ndefs,format) {length,nuses,ndefs,format},
68 : FOR_EACH_OPCODE(MAKE_CODESPEC)
69 : #undef MAKE_CODESPEC
70 : };
71 :
72 : const unsigned js::NumCodeSpecs = JS_ARRAY_LENGTH(CodeSpec);
73 :
74 : /*
75 : * Each element of the array is either a source literal associated with JS
76 : * bytecode or null.
77 : */
78 : static const char * const CodeToken[] = {
79 : #define TOKEN(op, val, name, token, ...) token,
80 : FOR_EACH_OPCODE(TOKEN)
81 : #undef TOKEN
82 : };
83 :
84 : /*
85 : * Array of JS bytecode names used by PC count JSON, DEBUG-only Disassemble
86 : * and JIT debug spew.
87 : */
88 : const char * const js::CodeName[] = {
89 : #define OPNAME(op, val, name, ...) name,
90 : FOR_EACH_OPCODE(OPNAME)
91 : #undef OPNAME
92 : };
93 :
94 : /************************************************************************/
95 :
96 : static bool
97 : DecompileArgumentFromStack(JSContext* cx, int formalIndex, char** res);
98 :
99 : size_t
100 3968 : js::GetVariableBytecodeLength(jsbytecode* pc)
101 : {
102 3968 : JSOp op = JSOp(*pc);
103 3968 : MOZ_ASSERT(CodeSpec[op].length == -1);
104 3968 : switch (op) {
105 : case JSOP_TABLESWITCH: {
106 : /* Structure: default-jump case-low case-high case1-jump ... */
107 3968 : pc += JUMP_OFFSET_LEN;
108 3968 : int32_t low = GET_JUMP_OFFSET(pc);
109 3968 : pc += JUMP_OFFSET_LEN;
110 3968 : int32_t high = GET_JUMP_OFFSET(pc);
111 3968 : unsigned ncases = unsigned(high - low + 1);
112 7936 : return 1 + 3 * JUMP_OFFSET_LEN + ncases * JUMP_OFFSET_LEN;
113 : }
114 : default:
115 0 : MOZ_CRASH("Unexpected op");
116 : }
117 : }
118 :
119 : unsigned
120 395989 : js::StackUses(JSScript* script, jsbytecode* pc)
121 : {
122 395989 : JSOp op = (JSOp) *pc;
123 395989 : const JSCodeSpec& cs = CodeSpec[op];
124 395989 : if (cs.nuses >= 0)
125 365301 : return cs.nuses;
126 :
127 30688 : MOZ_ASSERT(CodeSpec[op].nuses == -1);
128 30688 : switch (op) {
129 : case JSOP_POPN:
130 5 : return GET_UINT16(pc);
131 : case JSOP_NEW:
132 : case JSOP_SUPERCALL:
133 1681 : return 2 + GET_ARGC(pc) + 1;
134 : default:
135 : /* stack: fun, this, [argc arguments] */
136 29002 : MOZ_ASSERT(op == JSOP_CALL || op == JSOP_CALL_IGNORES_RV || op == JSOP_EVAL ||
137 : op == JSOP_CALLITER ||
138 : op == JSOP_STRICTEVAL || op == JSOP_FUNCALL || op == JSOP_FUNAPPLY);
139 29002 : return 2 + GET_ARGC(pc);
140 : }
141 : }
142 :
143 : unsigned
144 493072 : js::StackDefs(JSScript* script, jsbytecode* pc)
145 : {
146 493072 : JSOp op = (JSOp) *pc;
147 493072 : const JSCodeSpec& cs = CodeSpec[op];
148 493072 : MOZ_ASSERT(cs.ndefs >= 0);
149 493072 : return cs.ndefs;
150 : }
151 :
152 : const char * PCCounts::numExecName = "interp";
153 :
154 : static MOZ_MUST_USE bool
155 0 : DumpIonScriptCounts(Sprinter* sp, HandleScript script, jit::IonScriptCounts* ionCounts)
156 : {
157 0 : if (!sp->jsprintf("IonScript [%" PRIuSIZE " blocks]:\n", ionCounts->numBlocks()))
158 0 : return false;
159 :
160 0 : for (size_t i = 0; i < ionCounts->numBlocks(); i++) {
161 0 : const jit::IonBlockCounts& block = ionCounts->block(i);
162 0 : unsigned lineNumber = 0, columnNumber = 0;
163 0 : lineNumber = PCToLineNumber(script, script->offsetToPC(block.offset()), &columnNumber);
164 0 : if (!sp->jsprintf("BB #%" PRIu32 " [%05u,%u,%u]",
165 : block.id(), block.offset(), lineNumber, columnNumber))
166 : {
167 0 : return false;
168 : }
169 0 : if (block.description()) {
170 0 : if (!sp->jsprintf(" [inlined %s]", block.description()))
171 0 : return false;
172 : }
173 0 : for (size_t j = 0; j < block.numSuccessors(); j++) {
174 0 : if (!sp->jsprintf(" -> #%" PRIu32, block.successor(j)))
175 0 : return false;
176 : }
177 0 : if (!sp->jsprintf(" :: %" PRIu64 " hits\n", block.hitCount()))
178 0 : return false;
179 0 : if (!sp->jsprintf("%s\n", block.code()))
180 0 : return false;
181 : }
182 :
183 0 : return true;
184 : }
185 :
186 : static MOZ_MUST_USE bool
187 0 : DumpPCCounts(JSContext* cx, HandleScript script, Sprinter* sp)
188 : {
189 0 : MOZ_ASSERT(script->hasScriptCounts());
190 :
191 : #ifdef DEBUG
192 0 : jsbytecode* pc = script->code();
193 0 : while (pc < script->codeEnd()) {
194 0 : jsbytecode* next = GetNextPc(pc);
195 :
196 0 : if (!Disassemble1(cx, script, pc, script->pcToOffset(pc), true, sp))
197 0 : return false;
198 :
199 0 : if (!sp->put(" {"))
200 0 : return false;
201 :
202 0 : PCCounts* counts = script->maybeGetPCCounts(pc);
203 0 : if (double val = counts ? counts->numExec() : 0.0) {
204 0 : if (!sp->jsprintf("\"%s\": %.0f", PCCounts::numExecName, val))
205 0 : return false;
206 : }
207 0 : if (!sp->put("}\n"))
208 0 : return false;
209 :
210 0 : pc = next;
211 : }
212 : #endif
213 :
214 0 : jit::IonScriptCounts* ionCounts = script->getIonCounts();
215 0 : while (ionCounts) {
216 0 : if (!DumpIonScriptCounts(sp, script, ionCounts))
217 0 : return false;
218 :
219 0 : ionCounts = ionCounts->previous();
220 : }
221 :
222 0 : return true;
223 : }
224 :
225 : bool
226 0 : js::DumpCompartmentPCCounts(JSContext* cx)
227 : {
228 0 : Rooted<GCVector<JSScript*>> scripts(cx, GCVector<JSScript*>(cx));
229 0 : for (auto iter = cx->zone()->cellIter<JSScript>(); !iter.done(); iter.next()) {
230 0 : JSScript* script = iter;
231 0 : if (script->compartment() != cx->compartment())
232 0 : continue;
233 0 : if (script->hasScriptCounts()) {
234 0 : if (!scripts.append(script))
235 0 : return false;
236 : }
237 : }
238 :
239 0 : for (uint32_t i = 0; i < scripts.length(); i++) {
240 0 : HandleScript script = scripts[i];
241 0 : Sprinter sprinter(cx);
242 0 : if (!sprinter.init())
243 0 : return false;
244 :
245 0 : fprintf(stdout, "--- SCRIPT %s:%" PRIuSIZE " ---\n", script->filename(), script->lineno());
246 0 : if (!DumpPCCounts(cx, script, &sprinter))
247 0 : return false;
248 0 : fputs(sprinter.string(), stdout);
249 0 : fprintf(stdout, "--- END SCRIPT %s:%" PRIuSIZE " ---\n", script->filename(), script->lineno());
250 : }
251 :
252 0 : return true;
253 : }
254 :
255 : /////////////////////////////////////////////////////////////////////
256 : // Bytecode Parser
257 : /////////////////////////////////////////////////////////////////////
258 :
259 : // Stores the information about the stack slot, where the value comes from.
260 : // Elements of BytecodeParser::Bytecode.{offsetStack,offsetStackAfter} arrays.
261 : struct OffsetAndDefIndex {
262 : // To make this struct a POD type, keep these properties public.
263 : // Use accessors instead of directly accessing them.
264 :
265 : // The offset of the PC that pushed the value for this slot.
266 : uint32_t offset_;
267 :
268 : // The index in `ndefs` for the PC (0-origin)
269 : uint8_t defIndex_;
270 :
271 : enum : uint8_t {
272 : Normal = 0,
273 :
274 : // Ignored this value in the expression decompilation.
275 : // Used by JSOP_NOP_DESTRUCTURING. See BytecodeParser::simulateOp.
276 : Ignored,
277 :
278 : // The value in this slot comes from 2 or more paths.
279 : // offset_ and defIndex_ holds the information for the path that
280 : // reaches here first.
281 : Merged,
282 : } type_;
283 :
284 3 : uint32_t offset() const {
285 3 : MOZ_ASSERT(!isSpecial());
286 3 : return offset_;
287 : };
288 0 : uint32_t specialOffset() const {
289 0 : MOZ_ASSERT(isSpecial());
290 0 : return offset_;
291 : };
292 :
293 3 : uint8_t defIndex() const {
294 3 : MOZ_ASSERT(!isSpecial());
295 3 : return defIndex_;
296 : }
297 0 : uint8_t specialDefIndex() const {
298 0 : MOZ_ASSERT(isSpecial());
299 0 : return defIndex_;
300 : }
301 :
302 9 : bool isSpecial() const {
303 9 : return type_ != Normal;
304 : }
305 0 : bool isMerged() const {
306 0 : return type_ == Merged;
307 : }
308 4 : bool isIgnored() const {
309 4 : return type_ == Ignored;
310 : }
311 :
312 212 : void set(uint32_t aOffset, uint8_t aDefIndex) {
313 212 : offset_ = aOffset;
314 212 : defIndex_ = aDefIndex;
315 212 : type_ = Normal;
316 212 : }
317 :
318 : // Keep offset_ and defIndex_ values for stack dump.
319 2 : void setMerged() {
320 2 : type_ = Merged;
321 2 : }
322 0 : void setIgnored() {
323 0 : type_ = Ignored;
324 0 : }
325 :
326 2 : bool operator==(const OffsetAndDefIndex& rhs) const {
327 2 : return offset_ == rhs.offset_ &&
328 2 : defIndex_ == rhs.defIndex_;
329 : }
330 :
331 2 : bool operator!=(const OffsetAndDefIndex& rhs) const {
332 2 : return !(*this == rhs);
333 : }
334 : };
335 :
336 : namespace mozilla {
337 :
338 : template <>
339 : struct IsPod<OffsetAndDefIndex> : TrueType {};
340 :
341 : } // namespace mozilla
342 :
343 : namespace {
344 :
345 4 : class BytecodeParser
346 : {
347 : public:
348 : enum class JumpKind {
349 : Simple,
350 : SwitchCase,
351 : SwitchDefault,
352 : TryCatch,
353 : TryFinally
354 : };
355 :
356 : private:
357 : class Bytecode
358 : {
359 : public:
360 486 : explicit Bytecode(const LifoAllocPolicy<Fallible>& alloc)
361 486 : : parsed(false),
362 : stackDepth(0),
363 : offsetStack(nullptr)
364 : #ifdef DEBUG
365 : ,
366 : stackDepthAfter(0),
367 : offsetStackAfter(nullptr),
368 486 : jumpOrigins(alloc)
369 : #endif /* DEBUG */
370 486 : {}
371 :
372 : // Whether this instruction has been analyzed to get its output defines
373 : // and stack.
374 : bool parsed : 1;
375 :
376 : // Stack depth before this opcode.
377 : uint32_t stackDepth;
378 :
379 : // Pointer to array of |stackDepth| offsets. An element at position N
380 : // in the array is the offset of the opcode that defined the
381 : // corresponding stack slot. The top of the stack is at position
382 : // |stackDepth - 1|.
383 : OffsetAndDefIndex* offsetStack;
384 :
385 : #ifdef DEBUG
386 : // stack depth after this opcode.
387 : uint32_t stackDepthAfter;
388 :
389 : // Pointer to array of |stackDepthAfter| offsets.
390 : OffsetAndDefIndex* offsetStackAfter;
391 :
392 : struct JumpInfo {
393 : uint32_t from;
394 : JumpKind kind;
395 :
396 0 : JumpInfo(uint32_t from_, JumpKind kind_)
397 0 : : from(from_),
398 0 : kind(kind_)
399 0 : {}
400 : };
401 :
402 : // A list of offsets of the bytecode that jumps to this bytecode,
403 : // exclusing previous bytecode.
404 : Vector<JumpInfo, 0, LifoAllocPolicy<Fallible>> jumpOrigins;
405 : #endif /* DEBUG */
406 :
407 482 : bool captureOffsetStack(LifoAlloc& alloc, const OffsetAndDefIndex* stack, uint32_t depth) {
408 482 : stackDepth = depth;
409 482 : offsetStack = alloc.newArray<OffsetAndDefIndex>(stackDepth);
410 482 : if (!offsetStack)
411 0 : return false;
412 482 : if (stackDepth) {
413 876 : for (uint32_t n = 0; n < stackDepth; n++)
414 546 : offsetStack[n] = stack[n];
415 : }
416 482 : return true;
417 : }
418 :
419 : #ifdef DEBUG
420 0 : bool captureOffsetStackAfter(LifoAlloc& alloc, const OffsetAndDefIndex* stack,
421 : uint32_t depth) {
422 0 : stackDepthAfter = depth;
423 0 : offsetStackAfter = alloc.newArray<OffsetAndDefIndex>(stackDepthAfter);
424 0 : if (!offsetStackAfter)
425 0 : return false;
426 0 : if (stackDepthAfter) {
427 0 : for (uint32_t n = 0; n < stackDepthAfter; n++)
428 0 : offsetStackAfter[n] = stack[n];
429 : }
430 0 : return true;
431 : }
432 :
433 0 : bool addJump(uint32_t from, JumpKind kind) {
434 0 : return jumpOrigins.append(JumpInfo(from, kind));
435 : }
436 : #endif /* DEBUG */
437 :
438 : // When control-flow merges, intersect the stacks, marking slots that
439 : // are defined by different offsets and/or defIndices merged.
440 : // This is sufficient for forward control-flow. It doesn't grok loops
441 : // -- for that you would have to iterate to a fixed point -- but there
442 : // shouldn't be operands on the stack at a loop back-edge anyway.
443 10 : void mergeOffsetStack(const OffsetAndDefIndex* stack, uint32_t depth) {
444 10 : MOZ_ASSERT(depth == stackDepth);
445 12 : for (uint32_t n = 0; n < stackDepth; n++) {
446 2 : if (stack[n].isIgnored())
447 0 : continue;
448 2 : if (offsetStack[n].isIgnored())
449 0 : offsetStack[n] = stack[n];
450 2 : if (offsetStack[n] != stack[n])
451 2 : offsetStack[n].setMerged();
452 : }
453 10 : }
454 : };
455 :
456 : JSContext* cx_;
457 : LifoAllocScope allocScope_;
458 : RootedScript script_;
459 :
460 : Bytecode** codeArray_;
461 :
462 : #ifdef DEBUG
463 : // Dedicated mode for stack dump.
464 : // Capture stack after each opcode, and also enable special handling for
465 : // some opcodes to make stack transition clearer.
466 : bool isStackDump;
467 : #endif /* DEBUG */
468 :
469 : public:
470 4 : BytecodeParser(JSContext* cx, JSScript* script)
471 4 : : cx_(cx),
472 4 : allocScope_(&cx->tempLifoAlloc()),
473 : script_(cx, script),
474 : codeArray_(nullptr)
475 : #ifdef DEBUG
476 : ,
477 8 : isStackDump(false)
478 : #endif /* DEBUG */
479 4 : {}
480 :
481 : bool parse();
482 :
483 : #ifdef DEBUG
484 : bool isReachable(uint32_t offset) { return maybeCode(offset); }
485 0 : bool isReachable(const jsbytecode* pc) { return maybeCode(pc); }
486 : #endif /* DEBUG */
487 :
488 4 : uint32_t stackDepthAtPC(uint32_t offset) {
489 : // Sometimes the code generator in debug mode asks about the stack depth
490 : // of unreachable code (bug 932180 comment 22). Assume that unreachable
491 : // code has no operands on the stack.
492 4 : return getCode(offset).stackDepth;
493 : }
494 4 : uint32_t stackDepthAtPC(const jsbytecode* pc) {
495 4 : return stackDepthAtPC(script_->pcToOffset(pc));
496 : }
497 :
498 : #ifdef DEBUG
499 0 : uint32_t stackDepthAfterPC(uint32_t offset) {
500 0 : return getCode(offset).stackDepthAfter;
501 : }
502 0 : uint32_t stackDepthAfterPC(const jsbytecode* pc) {
503 0 : return stackDepthAfterPC(script_->pcToOffset(pc));
504 : }
505 : #endif
506 :
507 3 : const OffsetAndDefIndex& offsetForStackOperand(uint32_t offset, int operand) {
508 3 : Bytecode& code = getCode(offset);
509 3 : if (operand < 0) {
510 1 : operand += code.stackDepth;
511 1 : MOZ_ASSERT(operand >= 0);
512 : }
513 3 : MOZ_ASSERT(uint32_t(operand) < code.stackDepth);
514 3 : return code.offsetStack[operand];
515 : }
516 2 : jsbytecode* pcForStackOperand(jsbytecode* pc, int operand, uint8_t* defIndex) {
517 2 : size_t offset = script_->pcToOffset(pc);
518 2 : const OffsetAndDefIndex& offsetAndDefIndex = offsetForStackOperand(offset, operand);
519 2 : if (offsetAndDefIndex.isSpecial())
520 0 : return nullptr;
521 2 : *defIndex = offsetAndDefIndex.defIndex();
522 2 : return script_->offsetToPC(offsetAndDefIndex.offset());
523 : }
524 :
525 : #ifdef DEBUG
526 0 : const OffsetAndDefIndex& offsetForStackOperandAfterPC(uint32_t offset, int operand) {
527 0 : Bytecode& code = getCode(offset);
528 0 : if (operand < 0) {
529 0 : operand += code.stackDepthAfter;
530 0 : MOZ_ASSERT(operand >= 0);
531 : }
532 0 : MOZ_ASSERT(uint32_t(operand) < code.stackDepthAfter);
533 0 : return code.offsetStackAfter[operand];
534 : }
535 : jsbytecode* pcForStackOperandAfterPC(jsbytecode* pc, int operand, uint8_t* defIndex) {
536 : size_t offset = script_->pcToOffset(pc);
537 : const OffsetAndDefIndex& offsetAndDefIndex = offsetForStackOperandAfterPC(offset, operand);
538 : if (offsetAndDefIndex.isSpecial())
539 : return nullptr;
540 : *defIndex = offsetAndDefIndex.defIndex();
541 : return script_->offsetToPC(offsetAndDefIndex.offset());
542 : }
543 :
544 : template <typename Callback>
545 0 : bool forEachJumpOrigins(jsbytecode* pc, Callback callback) {
546 0 : Bytecode& code = getCode(script_->pcToOffset(pc));
547 :
548 0 : for (Bytecode::JumpInfo& info : code.jumpOrigins) {
549 0 : if (!callback(script_->offsetToPC(info.from), info.kind))
550 0 : return false;
551 : }
552 :
553 0 : return true;
554 : }
555 :
556 0 : void setStackDump() {
557 0 : isStackDump = true;
558 0 : }
559 : #endif /* DEBUG */
560 :
561 : private:
562 1462 : LifoAlloc& alloc() {
563 1462 : return allocScope_.alloc();
564 : }
565 :
566 0 : void reportOOM() {
567 0 : allocScope_.releaseEarly();
568 0 : ReportOutOfMemory(cx_);
569 0 : }
570 :
571 : uint32_t numSlots() {
572 : return 1 + script_->nfixed() +
573 : (script_->functionNonDelazifying() ? script_->functionNonDelazifying()->nargs() : 0);
574 : }
575 :
576 494 : uint32_t maximumStackDepth() {
577 494 : return script_->nslots() - script_->nfixed();
578 : }
579 :
580 7 : Bytecode& getCode(uint32_t offset) {
581 7 : MOZ_ASSERT(offset < script_->length());
582 7 : MOZ_ASSERT(codeArray_[offset]);
583 7 : return *codeArray_[offset];
584 : }
585 : Bytecode& getCode(const jsbytecode* pc) { return getCode(script_->pcToOffset(pc)); }
586 :
587 604 : Bytecode* maybeCode(uint32_t offset) {
588 604 : MOZ_ASSERT(offset < script_->length());
589 604 : return codeArray_[offset];
590 : }
591 0 : Bytecode* maybeCode(const jsbytecode* pc) { return maybeCode(script_->pcToOffset(pc)); }
592 :
593 : uint32_t simulateOp(JSOp op, uint32_t offset, OffsetAndDefIndex* offsetStack,
594 : uint32_t stackDepth);
595 :
596 : inline bool recordBytecode(uint32_t offset, const OffsetAndDefIndex* offsetStack,
597 : uint32_t stackDepth);
598 :
599 : inline bool addJump(uint32_t offset, uint32_t* currentOffset,
600 : uint32_t stackDepth, const OffsetAndDefIndex* offsetStack,
601 : jsbytecode* pc, JumpKind kind);
602 : };
603 :
604 : } // anonymous namespace
605 :
606 : uint32_t
607 486 : BytecodeParser::simulateOp(JSOp op, uint32_t offset, OffsetAndDefIndex* offsetStack,
608 : uint32_t stackDepth)
609 : {
610 486 : uint32_t nuses = GetUseCount(script_, offset);
611 486 : uint32_t ndefs = GetDefCount(script_, offset);
612 :
613 486 : MOZ_ASSERT(stackDepth >= nuses);
614 486 : stackDepth -= nuses;
615 486 : MOZ_ASSERT(stackDepth + ndefs <= maximumStackDepth());
616 :
617 : #ifdef DEBUG
618 486 : if (isStackDump) {
619 : // Opcodes that modifies the object but keeps it on the stack while
620 : // initialization should be listed here instead of switch below.
621 : // For error message, they shouldn't be shown as the original object
622 : // after adding properties.
623 : // For stack dump, keeping the input is better.
624 0 : switch (op) {
625 : case JSOP_INITHIDDENPROP:
626 : case JSOP_INITHIDDENPROP_GETTER:
627 : case JSOP_INITHIDDENPROP_SETTER:
628 : case JSOP_INITLOCKEDPROP:
629 : case JSOP_INITPROP:
630 : case JSOP_INITPROP_GETTER:
631 : case JSOP_INITPROP_SETTER:
632 : case JSOP_SETFUNNAME:
633 : // Keep the second value.
634 0 : MOZ_ASSERT(nuses == 2);
635 0 : MOZ_ASSERT(ndefs == 1);
636 0 : goto end;
637 :
638 : case JSOP_INITELEM:
639 : case JSOP_INITELEM_GETTER:
640 : case JSOP_INITELEM_SETTER:
641 : case JSOP_INITHIDDENELEM:
642 : case JSOP_INITHIDDENELEM_GETTER:
643 : case JSOP_INITHIDDENELEM_SETTER:
644 : // Keep the third value.
645 0 : MOZ_ASSERT(nuses == 3);
646 0 : MOZ_ASSERT(ndefs == 1);
647 0 : goto end;
648 :
649 : default:
650 0 : break;
651 : }
652 : }
653 : #endif /* DEBUG */
654 :
655 : // Mark the current offset as defining its values on the offset stack,
656 : // unless it just reshuffles the stack. In that case we want to preserve
657 : // the opcode that generated the original value.
658 486 : switch (op) {
659 : default:
660 586 : for (uint32_t n = 0; n != ndefs; ++n)
661 212 : offsetStack[stackDepth + n].set(offset, n);
662 374 : break;
663 :
664 : case JSOP_NOP_DESTRUCTURING:
665 : // Poison the last offset to not obfuscate the error message.
666 0 : offsetStack[stackDepth - 1].setIgnored();
667 0 : break;
668 :
669 : case JSOP_CASE:
670 : // Keep the switch value.
671 0 : MOZ_ASSERT(ndefs == 1);
672 0 : break;
673 :
674 : case JSOP_DUP:
675 28 : MOZ_ASSERT(ndefs == 2);
676 28 : offsetStack[stackDepth + 1] = offsetStack[stackDepth];
677 28 : break;
678 :
679 : case JSOP_DUP2:
680 0 : MOZ_ASSERT(ndefs == 4);
681 0 : offsetStack[stackDepth + 2] = offsetStack[stackDepth];
682 0 : offsetStack[stackDepth + 3] = offsetStack[stackDepth + 1];
683 0 : break;
684 :
685 : case JSOP_DUPAT: {
686 0 : MOZ_ASSERT(ndefs == 1);
687 0 : jsbytecode* pc = script_->offsetToPC(offset);
688 0 : unsigned n = GET_UINT24(pc);
689 0 : MOZ_ASSERT(n < stackDepth);
690 0 : offsetStack[stackDepth] = offsetStack[stackDepth - 1 - n];
691 0 : break;
692 : }
693 :
694 : case JSOP_SWAP: {
695 26 : MOZ_ASSERT(ndefs == 2);
696 26 : OffsetAndDefIndex tmp = offsetStack[stackDepth + 1];
697 26 : offsetStack[stackDepth + 1] = offsetStack[stackDepth];
698 26 : offsetStack[stackDepth] = tmp;
699 26 : break;
700 : }
701 :
702 : case JSOP_PICK: {
703 0 : jsbytecode* pc = script_->offsetToPC(offset);
704 0 : unsigned n = GET_UINT8(pc);
705 0 : MOZ_ASSERT(ndefs == n + 1);
706 0 : uint32_t top = stackDepth + n;
707 0 : OffsetAndDefIndex tmp = offsetStack[stackDepth];
708 0 : for (uint32_t i = stackDepth; i < top; i++)
709 0 : offsetStack[i] = offsetStack[i + 1];
710 0 : offsetStack[top] = tmp;
711 0 : break;
712 : }
713 :
714 : case JSOP_UNPICK: {
715 0 : jsbytecode* pc = script_->offsetToPC(offset);
716 0 : unsigned n = GET_UINT8(pc);
717 0 : MOZ_ASSERT(ndefs == n + 1);
718 0 : uint32_t top = stackDepth + n;
719 0 : OffsetAndDefIndex tmp = offsetStack[top];
720 0 : for (uint32_t i = top; i > stackDepth; i--)
721 0 : offsetStack[i] = offsetStack[i - 1];
722 0 : offsetStack[stackDepth] = tmp;
723 0 : break;
724 : }
725 :
726 : case JSOP_AND:
727 : case JSOP_CHECKISOBJ:
728 : case JSOP_CHECKISCALLABLE:
729 : case JSOP_CHECKOBJCOERCIBLE:
730 : case JSOP_CHECKTHIS:
731 : case JSOP_CHECKTHISREINIT:
732 : case JSOP_CHECKCLASSHERITAGE:
733 : case JSOP_DEBUGCHECKSELFHOSTED:
734 : case JSOP_INITGLEXICAL:
735 : case JSOP_INITLEXICAL:
736 : case JSOP_OR:
737 : case JSOP_SETALIASEDVAR:
738 : case JSOP_SETARG:
739 : case JSOP_SETINTRINSIC:
740 : case JSOP_SETLOCAL:
741 : case JSOP_THROWSETALIASEDCONST:
742 : case JSOP_THROWSETCALLEE:
743 : case JSOP_THROWSETCONST:
744 : case JSOP_INITALIASEDLEXICAL:
745 : case JSOP_INITIALYIELD:
746 : // Keep the top value.
747 58 : MOZ_ASSERT(nuses == 1);
748 58 : MOZ_ASSERT(ndefs == 1);
749 58 : break;
750 :
751 : case JSOP_INITHOMEOBJECT:
752 : // Keep the top 2 values.
753 0 : MOZ_ASSERT(nuses == 2);
754 0 : MOZ_ASSERT(ndefs == 2);
755 0 : break;
756 :
757 : case JSOP_SETGNAME:
758 : case JSOP_SETNAME:
759 : case JSOP_SETPROP:
760 : case JSOP_STRICTSETGNAME:
761 : case JSOP_STRICTSETNAME:
762 : case JSOP_STRICTSETPROP:
763 : // Keep the top value, removing other 1 value.
764 0 : MOZ_ASSERT(nuses == 2);
765 0 : MOZ_ASSERT(ndefs == 1);
766 0 : offsetStack[stackDepth] = offsetStack[stackDepth + 1];
767 0 : break;
768 :
769 : case JSOP_SETPROP_SUPER:
770 : case JSOP_STRICTSETPROP_SUPER:
771 : // Keep the top value, removing other 2 values.
772 0 : MOZ_ASSERT(nuses == 3);
773 0 : MOZ_ASSERT(ndefs == 1);
774 0 : offsetStack[stackDepth] = offsetStack[stackDepth + 2];
775 0 : break;
776 :
777 : case JSOP_SETELEM_SUPER:
778 : case JSOP_STRICTSETELEM_SUPER:
779 : // Keep the top value, removing other 3 values.
780 0 : MOZ_ASSERT(nuses == 4);
781 0 : MOZ_ASSERT(ndefs == 1);
782 0 : offsetStack[stackDepth] = offsetStack[stackDepth + 3];
783 0 : break;
784 :
785 : case JSOP_ISGENCLOSING:
786 : case JSOP_ISNOITER:
787 : case JSOP_MOREITER:
788 : case JSOP_OPTIMIZE_SPREADCALL:
789 : // Keep the top value and push one more value.
790 0 : MOZ_ASSERT(nuses == 1);
791 0 : MOZ_ASSERT(ndefs == 2);
792 0 : offsetStack[stackDepth + 1].set(offset, 1);
793 0 : break;
794 : }
795 :
796 : #ifdef DEBUG
797 : end:
798 : #endif /* DEBUG */
799 :
800 486 : stackDepth += ndefs;
801 486 : return stackDepth;
802 : }
803 :
804 : bool
805 492 : BytecodeParser::recordBytecode(uint32_t offset, const OffsetAndDefIndex* offsetStack,
806 : uint32_t stackDepth)
807 : {
808 492 : MOZ_ASSERT(offset < script_->length());
809 :
810 492 : Bytecode*& code = codeArray_[offset];
811 492 : if (!code) {
812 482 : code = alloc().new_<Bytecode>(alloc());
813 964 : if (!code ||
814 482 : !code->captureOffsetStack(alloc(), offsetStack, stackDepth))
815 : {
816 0 : reportOOM();
817 0 : return false;
818 : }
819 : } else {
820 10 : code->mergeOffsetStack(offsetStack, stackDepth);
821 : }
822 :
823 492 : return true;
824 : }
825 :
826 : bool
827 28 : BytecodeParser::addJump(uint32_t offset, uint32_t* currentOffset,
828 : uint32_t stackDepth, const OffsetAndDefIndex* offsetStack,
829 : jsbytecode* pc, JumpKind kind)
830 : {
831 28 : if (!recordBytecode(offset, offsetStack, stackDepth))
832 0 : return false;
833 :
834 : #ifdef DEBUG
835 28 : if (isStackDump) {
836 0 : if (!codeArray_[offset]->addJump(script_->pcToOffset(pc), kind)) {
837 0 : reportOOM();
838 0 : return false;
839 : }
840 : }
841 : #endif /* DEBUG */
842 :
843 28 : Bytecode*& code = codeArray_[offset];
844 28 : if (offset < *currentOffset && !code->parsed) {
845 : // Backedge in a while/for loop, whose body has not been parsed due
846 : // to a lack of fallthrough at the loop head. Roll back the offset
847 : // to analyze the body.
848 2 : *currentOffset = offset;
849 : }
850 :
851 28 : return true;
852 : }
853 :
854 : bool
855 4 : BytecodeParser::parse()
856 : {
857 4 : MOZ_ASSERT(!codeArray_);
858 :
859 4 : uint32_t length = script_->length();
860 4 : codeArray_ = alloc().newArray<Bytecode*>(length);
861 :
862 4 : if (!codeArray_) {
863 0 : reportOOM();
864 0 : return false;
865 : }
866 :
867 4 : mozilla::PodZero(codeArray_, length);
868 :
869 : // Fill in stack depth and definitions at initial bytecode.
870 4 : Bytecode* startcode = alloc().new_<Bytecode>(alloc());
871 4 : if (!startcode) {
872 0 : reportOOM();
873 0 : return false;
874 : }
875 :
876 : // Fill in stack depth and definitions at initial bytecode.
877 4 : OffsetAndDefIndex* offsetStack = alloc().newArray<OffsetAndDefIndex>(maximumStackDepth());
878 4 : if (maximumStackDepth() && !offsetStack) {
879 0 : reportOOM();
880 0 : return false;
881 : }
882 :
883 4 : startcode->stackDepth = 0;
884 4 : codeArray_[0] = startcode;
885 :
886 4 : uint32_t offset, nextOffset = 0;
887 1212 : while (nextOffset < length) {
888 604 : offset = nextOffset;
889 :
890 604 : Bytecode* code = maybeCode(offset);
891 604 : jsbytecode* pc = script_->offsetToPC(offset);
892 :
893 604 : JSOp op = (JSOp)*pc;
894 604 : MOZ_ASSERT(op < JSOP_LIMIT);
895 :
896 : // Immediate successor of this bytecode.
897 604 : uint32_t successorOffset = offset + GetBytecodeLength(pc);
898 :
899 : // Next bytecode to analyze. This is either the successor, or is an
900 : // earlier bytecode if this bytecode has a loop backedge.
901 604 : nextOffset = successorOffset;
902 :
903 604 : if (!code) {
904 : // Haven't found a path by which this bytecode is reachable.
905 106 : continue;
906 : }
907 :
908 : // On a jump target, we reload the offsetStack saved for the current
909 : // bytecode, as it contains either the original offset stack, or the
910 : // merged offset stack.
911 498 : if (BytecodeIsJumpTarget(op)) {
912 42 : for (uint32_t n = 0; n < code->stackDepth; ++n)
913 4 : offsetStack[n] = code->offsetStack[n];
914 : }
915 :
916 498 : if (code->parsed) {
917 : // No need to reparse.
918 12 : continue;
919 : }
920 :
921 486 : code->parsed = true;
922 :
923 486 : uint32_t stackDepth = simulateOp(op, offset, offsetStack, code->stackDepth);
924 :
925 : #ifdef DEBUG
926 486 : if (isStackDump) {
927 0 : if (!code->captureOffsetStackAfter(alloc(), offsetStack, stackDepth)) {
928 0 : reportOOM();
929 0 : return false;
930 : }
931 : }
932 : #endif /* DEBUG */
933 :
934 486 : switch (op) {
935 : case JSOP_TABLESWITCH: {
936 0 : uint32_t defaultOffset = offset + GET_JUMP_OFFSET(pc);
937 0 : jsbytecode* pc2 = pc + JUMP_OFFSET_LEN;
938 0 : int32_t low = GET_JUMP_OFFSET(pc2);
939 0 : pc2 += JUMP_OFFSET_LEN;
940 0 : int32_t high = GET_JUMP_OFFSET(pc2);
941 0 : pc2 += JUMP_OFFSET_LEN;
942 :
943 0 : if (!addJump(defaultOffset, &nextOffset, stackDepth, offsetStack,
944 : pc, JumpKind::SwitchDefault))
945 : {
946 0 : return false;
947 : }
948 :
949 0 : for (int32_t i = low; i <= high; i++) {
950 0 : uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc2);
951 0 : if (targetOffset != offset) {
952 0 : if (!addJump(targetOffset, &nextOffset, stackDepth, offsetStack,
953 : pc, JumpKind::SwitchCase))
954 : {
955 0 : return false;
956 : }
957 : }
958 0 : pc2 += JUMP_OFFSET_LEN;
959 : }
960 0 : break;
961 : }
962 :
963 : case JSOP_TRY: {
964 : // Everything between a try and corresponding catch or finally is conditional.
965 : // Note that there is no problem with code which is skipped by a thrown
966 : // exception but is not caught by a later handler in the same function:
967 : // no more code will execute, and it does not matter what is defined.
968 6 : JSTryNote* tn = script_->trynotes()->vector;
969 6 : JSTryNote* tnlimit = tn + script_->trynotes()->length;
970 34 : for (; tn < tnlimit; tn++) {
971 14 : uint32_t startOffset = script_->mainOffset() + tn->start;
972 14 : if (startOffset == offset + 1) {
973 6 : uint32_t catchOffset = startOffset + tn->length;
974 6 : if (tn->kind == JSTRY_CATCH) {
975 6 : if (!addJump(catchOffset, &nextOffset, stackDepth, offsetStack,
976 : pc, JumpKind::TryCatch))
977 : {
978 0 : return false;
979 : }
980 0 : } else if (tn->kind == JSTRY_FINALLY) {
981 0 : if (!addJump(catchOffset, &nextOffset, stackDepth, offsetStack,
982 : pc, JumpKind::TryFinally))
983 : {
984 0 : return false;
985 : }
986 : }
987 : }
988 : }
989 6 : break;
990 : }
991 :
992 : default:
993 480 : break;
994 : }
995 :
996 : // Check basic jump opcodes, which may or may not have a fallthrough.
997 486 : if (IsJumpOpcode(op)) {
998 : // Case instructions do not push the lvalue back when branching.
999 22 : uint32_t newStackDepth = stackDepth;
1000 22 : if (op == JSOP_CASE)
1001 0 : newStackDepth--;
1002 :
1003 22 : uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc);
1004 22 : if (!addJump(targetOffset, &nextOffset, newStackDepth, offsetStack,
1005 : pc, JumpKind::Simple))
1006 0 : return false;
1007 : }
1008 :
1009 : // Handle any fallthrough from this opcode.
1010 486 : if (BytecodeFallsThrough(op)) {
1011 464 : if (!recordBytecode(successorOffset, offsetStack, stackDepth))
1012 0 : return false;
1013 : }
1014 : }
1015 :
1016 4 : return true;
1017 : }
1018 :
1019 : #ifdef DEBUG
1020 :
1021 : bool
1022 0 : js::ReconstructStackDepth(JSContext* cx, JSScript* script, jsbytecode* pc, uint32_t* depth, bool* reachablePC)
1023 : {
1024 0 : BytecodeParser parser(cx, script);
1025 0 : if (!parser.parse())
1026 0 : return false;
1027 :
1028 0 : *reachablePC = parser.isReachable(pc);
1029 :
1030 0 : if (*reachablePC)
1031 0 : *depth = parser.stackDepthAtPC(pc);
1032 :
1033 0 : return true;
1034 : }
1035 :
1036 : static unsigned
1037 : Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc,
1038 : unsigned loc, bool lines, BytecodeParser* parser, Sprinter* sp);
1039 :
1040 : /*
1041 : * If pc != nullptr, include a prefix indicating whether the PC is at the
1042 : * current line. If showAll is true, include the source note type and the
1043 : * entry stack depth.
1044 : */
1045 : static MOZ_MUST_USE bool
1046 0 : DisassembleAtPC(JSContext* cx, JSScript* scriptArg, bool lines,
1047 : jsbytecode* pc, bool showAll, Sprinter* sp)
1048 : {
1049 0 : RootedScript script(cx, scriptArg);
1050 0 : BytecodeParser parser(cx, script);
1051 0 : parser.setStackDump();
1052 0 : if (!parser.parse())
1053 0 : return false;
1054 :
1055 0 : if (showAll) {
1056 0 : if (!sp->jsprintf("%s:%u\n", script->filename(), unsigned(script->lineno())))
1057 0 : return false;
1058 : }
1059 :
1060 0 : if (pc != nullptr) {
1061 0 : if (!sp->put(" "))
1062 0 : return false;
1063 : }
1064 0 : if (showAll) {
1065 0 : if (!sp->put("sn stack "))
1066 0 : return false;
1067 : }
1068 0 : if (!sp->put("loc "))
1069 0 : return false;
1070 0 : if (lines) {
1071 0 : if (!sp->put("line"))
1072 0 : return false;
1073 : }
1074 0 : if (!sp->put(" op\n"))
1075 0 : return false;
1076 :
1077 0 : if (pc != nullptr) {
1078 0 : if (!sp->put(" "))
1079 0 : return false;
1080 : }
1081 0 : if (showAll) {
1082 0 : if (!sp->put("-- ----- "))
1083 0 : return false;
1084 : }
1085 0 : if (!sp->put("----- "))
1086 0 : return false;
1087 0 : if (lines) {
1088 0 : if (!sp->put("----"))
1089 0 : return false;
1090 : }
1091 0 : if (!sp->put(" --\n"))
1092 0 : return false;
1093 :
1094 0 : jsbytecode* next = script->code();
1095 0 : jsbytecode* end = script->codeEnd();
1096 0 : while (next < end) {
1097 0 : if (next == script->main()) {
1098 0 : if (!sp->put("main:\n"))
1099 0 : return false;
1100 : }
1101 0 : if (pc != nullptr) {
1102 0 : if (!sp->put(pc == next ? "--> " : " "))
1103 0 : return false;
1104 : }
1105 0 : if (showAll) {
1106 0 : jssrcnote* sn = GetSrcNote(cx, script, next);
1107 0 : if (sn) {
1108 0 : MOZ_ASSERT(!SN_IS_TERMINATOR(sn));
1109 0 : jssrcnote* next = SN_NEXT(sn);
1110 0 : while (!SN_IS_TERMINATOR(next) && SN_DELTA(next) == 0) {
1111 0 : if (!sp->jsprintf("%02u\n ", SN_TYPE(sn)))
1112 0 : return false;
1113 0 : sn = next;
1114 0 : next = SN_NEXT(sn);
1115 : }
1116 0 : if (!sp->jsprintf("%02u ", SN_TYPE(sn)))
1117 0 : return false;
1118 : } else {
1119 0 : if (!sp->put(" "))
1120 0 : return false;
1121 : }
1122 0 : if (parser.isReachable(next)) {
1123 0 : if (!sp->jsprintf("%05u ", parser.stackDepthAtPC(next)))
1124 0 : return false;
1125 : } else {
1126 0 : if (!sp->put(" "))
1127 0 : return false;
1128 : }
1129 : }
1130 0 : unsigned len = Disassemble1(cx, script, next, script->pcToOffset(next), lines,
1131 0 : &parser, sp);
1132 0 : if (!len)
1133 0 : return false;
1134 :
1135 0 : next += len;
1136 : }
1137 :
1138 0 : return true;
1139 : }
1140 :
1141 : bool
1142 0 : js::Disassemble(JSContext* cx, HandleScript script, bool lines, Sprinter* sp)
1143 : {
1144 0 : return DisassembleAtPC(cx, script, lines, nullptr, false, sp);
1145 : }
1146 :
1147 : JS_FRIEND_API(bool)
1148 0 : js::DumpPC(JSContext* cx, FILE* fp)
1149 : {
1150 0 : gc::AutoSuppressGC suppressGC(cx);
1151 0 : Sprinter sprinter(cx);
1152 0 : if (!sprinter.init())
1153 0 : return false;
1154 0 : ScriptFrameIter iter(cx);
1155 0 : if (iter.done()) {
1156 0 : fprintf(fp, "Empty stack.\n");
1157 0 : return true;
1158 : }
1159 0 : RootedScript script(cx, iter.script());
1160 0 : bool ok = DisassembleAtPC(cx, script, true, iter.pc(), false, &sprinter);
1161 0 : fprintf(fp, "%s", sprinter.string());
1162 0 : return ok;
1163 : }
1164 :
1165 : JS_FRIEND_API(bool)
1166 0 : js::DumpScript(JSContext* cx, JSScript* scriptArg, FILE* fp)
1167 : {
1168 0 : gc::AutoSuppressGC suppressGC(cx);
1169 0 : Sprinter sprinter(cx);
1170 0 : if (!sprinter.init())
1171 0 : return false;
1172 0 : RootedScript script(cx, scriptArg);
1173 0 : bool ok = Disassemble(cx, script, true, &sprinter);
1174 0 : fprintf(fp, "%s", sprinter.string());
1175 0 : return ok;
1176 : }
1177 :
1178 : static bool
1179 0 : ToDisassemblySource(JSContext* cx, HandleValue v, JSAutoByteString* bytes)
1180 : {
1181 0 : if (v.isString()) {
1182 0 : Sprinter sprinter(cx);
1183 0 : if (!sprinter.init())
1184 0 : return false;
1185 0 : char* nbytes = QuoteString(&sprinter, v.toString(), '"');
1186 0 : if (!nbytes)
1187 0 : return false;
1188 0 : UniqueChars copy = JS_smprintf("%s", nbytes);
1189 0 : if (!copy) {
1190 0 : ReportOutOfMemory(cx);
1191 0 : return false;
1192 : }
1193 0 : bytes->initBytes(Move(copy));
1194 0 : return true;
1195 : }
1196 :
1197 0 : if (JS::CurrentThreadIsHeapBusy() || !cx->isAllocAllowed()) {
1198 0 : UniqueChars source = JS_smprintf("<value>");
1199 0 : if (!source) {
1200 0 : ReportOutOfMemory(cx);
1201 0 : return false;
1202 : }
1203 0 : bytes->initBytes(Move(source));
1204 0 : return true;
1205 : }
1206 :
1207 0 : if (v.isObject()) {
1208 0 : JSObject& obj = v.toObject();
1209 :
1210 0 : if (obj.is<JSFunction>()) {
1211 0 : RootedFunction fun(cx, &obj.as<JSFunction>());
1212 0 : JSString* str = JS_DecompileFunction(cx, fun, JS_DONT_PRETTY_PRINT);
1213 0 : if (!str)
1214 0 : return false;
1215 0 : return bytes->encodeLatin1(cx, str);
1216 : }
1217 :
1218 0 : if (obj.is<RegExpObject>()) {
1219 0 : JSString* source = obj.as<RegExpObject>().toString(cx);
1220 0 : if (!source)
1221 0 : return false;
1222 0 : return bytes->encodeLatin1(cx, source);
1223 : }
1224 : }
1225 :
1226 0 : return !!ValueToPrintable(cx, v, bytes, true);
1227 : }
1228 :
1229 : static bool
1230 0 : ToDisassemblySource(JSContext* cx, HandleScope scope, JSAutoByteString* bytes)
1231 : {
1232 0 : UniqueChars source = JS_smprintf("%s {", ScopeKindString(scope->kind()));
1233 0 : if (!source) {
1234 0 : ReportOutOfMemory(cx);
1235 0 : return false;
1236 : }
1237 :
1238 0 : for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
1239 0 : JSAutoByteString nameBytes;
1240 0 : if (!AtomToPrintableString(cx, bi.name(), &nameBytes))
1241 0 : return false;
1242 :
1243 0 : source = JS_sprintf_append(Move(source), "%s: ", nameBytes.ptr());
1244 0 : if (!source) {
1245 0 : ReportOutOfMemory(cx);
1246 0 : return false;
1247 : }
1248 :
1249 0 : BindingLocation loc = bi.location();
1250 0 : switch (loc.kind()) {
1251 : case BindingLocation::Kind::Global:
1252 0 : source = JS_sprintf_append(Move(source), "global");
1253 0 : break;
1254 :
1255 : case BindingLocation::Kind::Frame:
1256 0 : source = JS_sprintf_append(Move(source), "frame slot %u", loc.slot());
1257 0 : break;
1258 :
1259 : case BindingLocation::Kind::Environment:
1260 0 : source = JS_sprintf_append(Move(source), "env slot %u", loc.slot());
1261 0 : break;
1262 :
1263 : case BindingLocation::Kind::Argument:
1264 0 : source = JS_sprintf_append(Move(source), "arg slot %u", loc.slot());
1265 0 : break;
1266 :
1267 : case BindingLocation::Kind::NamedLambdaCallee:
1268 0 : source = JS_sprintf_append(Move(source), "named lambda callee");
1269 0 : break;
1270 :
1271 : case BindingLocation::Kind::Import:
1272 0 : source = JS_sprintf_append(Move(source), "import");
1273 0 : break;
1274 : }
1275 :
1276 0 : if (!source) {
1277 0 : ReportOutOfMemory(cx);
1278 0 : return false;
1279 : }
1280 :
1281 0 : if (!bi.isLast()) {
1282 0 : source = JS_sprintf_append(Move(source), ", ");
1283 0 : if (!source) {
1284 0 : ReportOutOfMemory(cx);
1285 0 : return false;
1286 : }
1287 : }
1288 : }
1289 :
1290 0 : source = JS_sprintf_append(Move(source), "}");
1291 0 : if (!source) {
1292 0 : ReportOutOfMemory(cx);
1293 0 : return false;
1294 : }
1295 :
1296 0 : bytes->initBytes(Move(source));
1297 0 : return true;
1298 : }
1299 :
1300 : static bool
1301 0 : DumpJumpOrigins(JSContext* cx, HandleScript script, jsbytecode* pc,
1302 : BytecodeParser* parser, Sprinter* sp)
1303 : {
1304 0 : bool called = false;
1305 0 : auto callback = [&script, &sp, &called](jsbytecode* pc, BytecodeParser::JumpKind kind) {
1306 0 : if (!called) {
1307 0 : called = true;
1308 0 : if (!sp->put("\n# "))
1309 0 : return false;
1310 : } else {
1311 0 : if (!sp->put(", "))
1312 0 : return false;
1313 : }
1314 :
1315 0 : switch (kind) {
1316 : case BytecodeParser::JumpKind::Simple:
1317 0 : break;
1318 :
1319 : case BytecodeParser::JumpKind::SwitchCase:
1320 0 : if (!sp->put("switch-case "))
1321 0 : return false;
1322 0 : break;
1323 :
1324 : case BytecodeParser::JumpKind::SwitchDefault:
1325 0 : if (!sp->put("switch-default "))
1326 0 : return false;
1327 0 : break;
1328 :
1329 : case BytecodeParser::JumpKind::TryCatch:
1330 0 : if (!sp->put("try-catch "))
1331 0 : return false;
1332 0 : break;
1333 :
1334 : case BytecodeParser::JumpKind::TryFinally:
1335 0 : if (!sp->put("try-finally "))
1336 0 : return false;
1337 0 : break;
1338 : }
1339 :
1340 0 : if (!sp->jsprintf("from %s @ %05u", CodeName[*pc], unsigned(script->pcToOffset(pc))))
1341 0 : return false;
1342 :
1343 0 : return true;
1344 0 : };
1345 0 : if (!parser->forEachJumpOrigins(pc, callback))
1346 0 : return false;
1347 0 : if (called) {
1348 0 : if (!sp->put("\n"))
1349 0 : return false;
1350 : }
1351 :
1352 0 : return true;
1353 : }
1354 :
1355 : static bool
1356 : DecompileAtPCForStackDump(JSContext* cx, HandleScript script,
1357 : const OffsetAndDefIndex& offsetAndDefIndex, Sprinter* sp);
1358 :
1359 : static unsigned
1360 0 : Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc,
1361 : unsigned loc, bool lines, BytecodeParser* parser, Sprinter* sp)
1362 : {
1363 0 : if (parser && parser->isReachable(pc)) {
1364 0 : if (!DumpJumpOrigins(cx, script, pc, parser, sp))
1365 0 : return 0;
1366 : }
1367 :
1368 0 : size_t before = sp->stringEnd() - sp->string();
1369 0 : bool stackDumped = false;
1370 0 : auto dumpStack = [&cx, &script, &pc, &parser, &sp, &before, &stackDumped]() {
1371 0 : if (!parser)
1372 0 : return true;
1373 0 : if (stackDumped)
1374 0 : return true;
1375 0 : stackDumped = true;
1376 :
1377 0 : size_t after = sp->stringEnd() - sp->string();
1378 0 : MOZ_ASSERT(after >= before);
1379 :
1380 : static const size_t stack_column = 40;
1381 0 : for (size_t i = after - before; i < stack_column - 1; i++) {
1382 0 : if (!sp->put(" "))
1383 0 : return false;
1384 : }
1385 :
1386 0 : if (!sp->put(" # "))
1387 0 : return false;
1388 :
1389 0 : if (!parser->isReachable(pc)) {
1390 0 : if (!sp->put("!!! UNREACHABLE !!!"))
1391 0 : return false;
1392 : } else {
1393 0 : uint32_t depth = parser->stackDepthAfterPC(pc);
1394 :
1395 0 : for (uint32_t i = 0; i < depth; i++) {
1396 0 : if (i) {
1397 0 : if (!sp->put(" "))
1398 0 : return false;
1399 : }
1400 :
1401 : const OffsetAndDefIndex& offsetAndDefIndex
1402 0 : = parser->offsetForStackOperandAfterPC(script->pcToOffset(pc), i);
1403 : // This will decompile the stack for the same PC many times.
1404 : // We'll avoid optimizing it since this is a testing function
1405 : // and it won't be worth managing cached expression here.
1406 0 : if (!DecompileAtPCForStackDump(cx, script, offsetAndDefIndex, sp))
1407 0 : return false;
1408 : }
1409 : }
1410 :
1411 0 : return true;
1412 0 : };
1413 :
1414 0 : JSOp op = (JSOp)*pc;
1415 0 : if (op >= JSOP_LIMIT) {
1416 : char numBuf1[12], numBuf2[12];
1417 0 : SprintfLiteral(numBuf1, "%d", op);
1418 0 : SprintfLiteral(numBuf2, "%d", JSOP_LIMIT);
1419 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BYTECODE_TOO_BIG,
1420 0 : numBuf1, numBuf2);
1421 0 : return 0;
1422 : }
1423 0 : const JSCodeSpec* cs = &CodeSpec[op];
1424 0 : ptrdiff_t len = (ptrdiff_t) cs->length;
1425 0 : if (!sp->jsprintf("%05u:", loc))
1426 0 : return 0;
1427 0 : if (lines) {
1428 0 : if (!sp->jsprintf("%4u", PCToLineNumber(script, pc)))
1429 0 : return 0;
1430 : }
1431 0 : if (!sp->jsprintf(" %s", CodeName[op]))
1432 0 : return 0;
1433 :
1434 : int i;
1435 0 : switch (JOF_TYPE(cs->format)) {
1436 : case JOF_BYTE:
1437 : // Scan the trynotes to find the associated catch block
1438 : // and make the try opcode look like a jump instruction
1439 : // with an offset. This simplifies code coverage analysis
1440 : // based on this disassembled output.
1441 0 : if (op == JSOP_TRY) {
1442 0 : TryNoteArray* trynotes = script->trynotes();
1443 : uint32_t i;
1444 0 : size_t mainOffset = script->mainOffset();
1445 0 : for(i = 0; i < trynotes->length; i++) {
1446 0 : JSTryNote note = trynotes->vector[i];
1447 0 : if (note.kind == JSTRY_CATCH && note.start + mainOffset == loc + 1) {
1448 0 : if (!sp->jsprintf(" %u (%+d)",
1449 0 : unsigned(loc + note.length + 1),
1450 0 : int(note.length + 1)))
1451 : {
1452 0 : return 0;
1453 : }
1454 0 : break;
1455 : }
1456 : }
1457 : }
1458 0 : break;
1459 :
1460 : case JOF_JUMP: {
1461 0 : ptrdiff_t off = GET_JUMP_OFFSET(pc);
1462 0 : if (!sp->jsprintf(" %u (%+d)", unsigned(loc + int(off)), int(off)))
1463 0 : return 0;
1464 0 : break;
1465 : }
1466 :
1467 : case JOF_SCOPE: {
1468 0 : RootedScope scope(cx, script->getScope(GET_UINT32_INDEX(pc)));
1469 0 : JSAutoByteString bytes;
1470 0 : if (!ToDisassemblySource(cx, scope, &bytes))
1471 0 : return 0;
1472 0 : if (!sp->jsprintf(" %s", bytes.ptr()))
1473 0 : return 0;
1474 0 : break;
1475 : }
1476 :
1477 : case JOF_ENVCOORD: {
1478 : RootedValue v(cx,
1479 0 : StringValue(EnvironmentCoordinateName(cx->caches().envCoordinateNameCache, script, pc)));
1480 0 : JSAutoByteString bytes;
1481 0 : if (!ToDisassemblySource(cx, v, &bytes))
1482 0 : return 0;
1483 0 : EnvironmentCoordinate ec(pc);
1484 0 : if (!sp->jsprintf(" %s (hops = %u, slot = %u)", bytes.ptr(), ec.hops(), ec.slot()))
1485 0 : return 0;
1486 0 : break;
1487 : }
1488 :
1489 : case JOF_ATOM: {
1490 0 : RootedValue v(cx, StringValue(script->getAtom(GET_UINT32_INDEX(pc))));
1491 0 : JSAutoByteString bytes;
1492 0 : if (!ToDisassemblySource(cx, v, &bytes))
1493 0 : return 0;
1494 0 : if (!sp->jsprintf(" %s", bytes.ptr()))
1495 0 : return 0;
1496 0 : break;
1497 : }
1498 :
1499 : case JOF_DOUBLE: {
1500 0 : RootedValue v(cx, script->getConst(GET_UINT32_INDEX(pc)));
1501 0 : JSAutoByteString bytes;
1502 0 : if (!ToDisassemblySource(cx, v, &bytes))
1503 0 : return 0;
1504 0 : if (!sp->jsprintf(" %s", bytes.ptr()))
1505 0 : return 0;
1506 0 : break;
1507 : }
1508 :
1509 : case JOF_OBJECT: {
1510 : /* Don't call obj.toSource if analysis/inference is active. */
1511 0 : if (script->zone()->types.activeAnalysis) {
1512 0 : if (!sp->jsprintf(" object"))
1513 0 : return 0;
1514 0 : break;
1515 : }
1516 :
1517 0 : JSObject* obj = script->getObject(GET_UINT32_INDEX(pc));
1518 : {
1519 0 : JSAutoByteString bytes;
1520 0 : RootedValue v(cx, ObjectValue(*obj));
1521 0 : if (!ToDisassemblySource(cx, v, &bytes))
1522 0 : return 0;
1523 0 : if (!sp->jsprintf(" %s", bytes.ptr()))
1524 0 : return 0;
1525 : }
1526 0 : break;
1527 : }
1528 :
1529 : case JOF_REGEXP: {
1530 0 : js::RegExpObject* obj = script->getRegExp(pc);
1531 0 : JSAutoByteString bytes;
1532 0 : RootedValue v(cx, ObjectValue(*obj));
1533 0 : if (!ToDisassemblySource(cx, v, &bytes))
1534 0 : return 0;
1535 0 : if (!sp->jsprintf(" %s", bytes.ptr()))
1536 0 : return 0;
1537 0 : break;
1538 : }
1539 :
1540 : case JOF_TABLESWITCH:
1541 : {
1542 : int32_t i, low, high;
1543 :
1544 0 : ptrdiff_t off = GET_JUMP_OFFSET(pc);
1545 0 : jsbytecode* pc2 = pc + JUMP_OFFSET_LEN;
1546 0 : low = GET_JUMP_OFFSET(pc2);
1547 0 : pc2 += JUMP_OFFSET_LEN;
1548 0 : high = GET_JUMP_OFFSET(pc2);
1549 0 : pc2 += JUMP_OFFSET_LEN;
1550 0 : if (!sp->jsprintf(" defaultOffset %d low %d high %d", int(off), low, high))
1551 0 : return 0;
1552 :
1553 : // Display stack dump before diplaying the offsets for each case.
1554 0 : if (!dumpStack())
1555 0 : return 0;
1556 :
1557 0 : for (i = low; i <= high; i++) {
1558 0 : off = GET_JUMP_OFFSET(pc2);
1559 0 : if (!sp->jsprintf("\n\t%d: %d", i, int(off)))
1560 0 : return 0;
1561 0 : pc2 += JUMP_OFFSET_LEN;
1562 : }
1563 0 : len = 1 + pc2 - pc;
1564 0 : break;
1565 : }
1566 :
1567 : case JOF_QARG:
1568 0 : if (!sp->jsprintf(" %u", GET_ARGNO(pc)))
1569 0 : return 0;
1570 0 : break;
1571 :
1572 : case JOF_LOCAL:
1573 0 : if (!sp->jsprintf(" %u", GET_LOCALNO(pc)))
1574 0 : return 0;
1575 0 : break;
1576 :
1577 : case JOF_UINT32:
1578 0 : if (!sp->jsprintf(" %u", GET_UINT32(pc)))
1579 0 : return 0;
1580 0 : break;
1581 :
1582 : case JOF_UINT16:
1583 0 : i = (int)GET_UINT16(pc);
1584 0 : goto print_int;
1585 :
1586 : case JOF_UINT24:
1587 0 : MOZ_ASSERT(len == 4);
1588 0 : i = (int)GET_UINT24(pc);
1589 0 : goto print_int;
1590 :
1591 : case JOF_UINT8:
1592 0 : i = GET_UINT8(pc);
1593 0 : goto print_int;
1594 :
1595 : case JOF_INT8:
1596 0 : i = GET_INT8(pc);
1597 0 : goto print_int;
1598 :
1599 : case JOF_INT32:
1600 0 : MOZ_ASSERT(op == JSOP_INT32);
1601 0 : i = GET_INT32(pc);
1602 : print_int:
1603 0 : if (!sp->jsprintf(" %d", i))
1604 0 : return 0;
1605 0 : break;
1606 :
1607 : default: {
1608 : char numBuf[12];
1609 0 : SprintfLiteral(numBuf, "%x", cs->format);
1610 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_UNKNOWN_FORMAT, numBuf);
1611 0 : return 0;
1612 : }
1613 : }
1614 :
1615 0 : if (!dumpStack())
1616 0 : return 0;
1617 :
1618 0 : if (!sp->put("\n"))
1619 0 : return 0;
1620 0 : return len;
1621 : }
1622 :
1623 : unsigned
1624 0 : js::Disassemble1(JSContext* cx, JS::Handle<JSScript*> script, jsbytecode* pc, unsigned loc,
1625 : bool lines, Sprinter* sp)
1626 : {
1627 0 : return Disassemble1(cx, script, pc, loc, lines, nullptr, sp);
1628 : }
1629 :
1630 : #endif /* DEBUG */
1631 :
1632 : namespace {
1633 : /*
1634 : * The expression decompiler is invoked by error handling code to produce a
1635 : * string representation of the erroring expression. As it's only a debugging
1636 : * tool, it only supports basic expressions. For anything complicated, it simply
1637 : * puts "(intermediate value)" into the error result.
1638 : *
1639 : * Here's the basic algorithm:
1640 : *
1641 : * 1. Find the stack location of the value whose expression we wish to
1642 : * decompile. The error handler can explicitly pass this as an
1643 : * argument. Otherwise, we search backwards down the stack for the offending
1644 : * value.
1645 : *
1646 : * 2. Instantiate and run a BytecodeParser for the current frame. This creates a
1647 : * stack of pcs parallel to the interpreter stack; given an interpreter stack
1648 : * location, the corresponding pc stack location contains the opcode that pushed
1649 : * the value in the interpreter. Now, with the result of step 1, we have the
1650 : * opcode responsible for pushing the value we want to decompile.
1651 : *
1652 : * 3. Pass the opcode to decompilePC. decompilePC is the main decompiler
1653 : * routine, responsible for a string representation of the expression that
1654 : * generated a certain stack location. decompilePC looks at one opcode and
1655 : * returns the JS source equivalent of that opcode.
1656 : *
1657 : * 4. Expressions can, of course, contain subexpressions. For example, the
1658 : * literals "4" and "5" are subexpressions of the addition operator in "4 +
1659 : * 5". If we need to decompile a subexpression, we call decompilePC (step 2)
1660 : * recursively on the operands' pcs. The result is a depth-first traversal of
1661 : * the expression tree.
1662 : *
1663 : */
1664 2 : struct ExpressionDecompiler
1665 : {
1666 : JSContext* cx;
1667 : RootedScript script;
1668 : BytecodeParser parser;
1669 : Sprinter sprinter;
1670 :
1671 : #ifdef DEBUG
1672 : // Dedicated mode for stack dump.
1673 : // Generates an expression for stack dump, including internal state,
1674 : // and also disables special handling for self-hosted code.
1675 : bool isStackDump;
1676 : #endif /* DEBUG */
1677 :
1678 2 : ExpressionDecompiler(JSContext* cx, JSScript* script)
1679 2 : : cx(cx),
1680 : script(cx, script),
1681 : parser(cx, script),
1682 : sprinter(cx)
1683 : #ifdef DEBUG
1684 : ,
1685 2 : isStackDump(false)
1686 : #endif /* DEBUG */
1687 2 : {}
1688 : bool init();
1689 : bool decompilePCForStackOperand(jsbytecode* pc, int i);
1690 : bool decompilePC(jsbytecode* pc, uint8_t defIndex);
1691 : bool decompilePC(const OffsetAndDefIndex& offsetAndDefIndex);
1692 : JSAtom* getArg(unsigned slot);
1693 : JSAtom* loadAtom(jsbytecode* pc);
1694 : bool quote(JSString* s, uint32_t quote);
1695 : bool write(const char* s);
1696 : bool write(JSString* str);
1697 : bool getOutput(char** out);
1698 : #ifdef DEBUG
1699 0 : void setStackDump() {
1700 0 : isStackDump = true;
1701 0 : parser.setStackDump();
1702 0 : }
1703 : #endif /* DEBUG */
1704 : };
1705 :
1706 : bool
1707 1 : ExpressionDecompiler::decompilePCForStackOperand(jsbytecode* pc, int i)
1708 : {
1709 1 : return decompilePC(parser.offsetForStackOperand(script->pcToOffset(pc), i));
1710 : }
1711 :
1712 : bool
1713 3 : ExpressionDecompiler::decompilePC(jsbytecode* pc, uint8_t defIndex)
1714 : {
1715 3 : MOZ_ASSERT(script->containsPC(pc));
1716 :
1717 3 : JSOp op = (JSOp)*pc;
1718 :
1719 3 : if (const char* token = CodeToken[op]) {
1720 0 : MOZ_ASSERT(defIndex == 0);
1721 0 : MOZ_ASSERT(CodeSpec[op].ndefs == 1);
1722 :
1723 : // Handle simple cases of binary and unary operators.
1724 0 : switch (CodeSpec[op].nuses) {
1725 : case 2: {
1726 0 : jssrcnote* sn = GetSrcNote(cx, script, pc);
1727 0 : if (!sn || SN_TYPE(sn) != SRC_ASSIGNOP)
1728 0 : return write("(") &&
1729 0 : decompilePCForStackOperand(pc, -2) &&
1730 0 : write(" ") &&
1731 0 : write(token) &&
1732 0 : write(" ") &&
1733 0 : decompilePCForStackOperand(pc, -1) &&
1734 0 : write(")");
1735 0 : break;
1736 : }
1737 : case 1:
1738 0 : return write("(") &&
1739 0 : write(token) &&
1740 0 : decompilePCForStackOperand(pc, -1) &&
1741 0 : write(")");
1742 : default:
1743 0 : break;
1744 : }
1745 : }
1746 :
1747 3 : switch (op) {
1748 : case JSOP_DELNAME:
1749 0 : return write("(delete ") &&
1750 0 : write(loadAtom(pc)) &&
1751 0 : write(")");
1752 :
1753 : case JSOP_GETGNAME:
1754 : case JSOP_GETNAME:
1755 : case JSOP_GETINTRINSIC:
1756 1 : return write(loadAtom(pc));
1757 : case JSOP_GETARG: {
1758 0 : unsigned slot = GET_ARGNO(pc);
1759 :
1760 : // For self-hosted scripts that are called from non-self-hosted code,
1761 : // decompiling the parameter name in the self-hosted script is
1762 : // unhelpful. Decompile the argument name instead.
1763 0 : if (script->selfHosted()
1764 : #ifdef DEBUG
1765 : // For stack dump, argument name is not necessary.
1766 0 : && !isStackDump
1767 : #endif /* DEBUG */
1768 : )
1769 : {
1770 : char* result;
1771 0 : if (!DecompileArgumentFromStack(cx, slot, &result))
1772 0 : return false;
1773 :
1774 : // Note that decompiling the argument in the parent frame might
1775 : // not succeed.
1776 0 : if (result) {
1777 0 : bool ok = write(result);
1778 0 : js_free(result);
1779 0 : return ok;
1780 : }
1781 : }
1782 :
1783 0 : JSAtom* atom = getArg(slot);
1784 0 : if (!atom)
1785 0 : return false;
1786 0 : return write(atom);
1787 : }
1788 : case JSOP_GETLOCAL: {
1789 1 : JSAtom* atom = FrameSlotName(script, pc);
1790 1 : MOZ_ASSERT(atom);
1791 1 : return write(atom);
1792 : }
1793 : case JSOP_GETALIASEDVAR: {
1794 0 : JSAtom* atom = EnvironmentCoordinateName(cx->caches().envCoordinateNameCache, script, pc);
1795 0 : MOZ_ASSERT(atom);
1796 0 : return write(atom);
1797 : }
1798 :
1799 : case JSOP_DELPROP:
1800 : case JSOP_STRICTDELPROP:
1801 : case JSOP_LENGTH:
1802 : case JSOP_GETPROP:
1803 : case JSOP_GETBOUNDNAME:
1804 : case JSOP_CALLPROP: {
1805 1 : bool hasDelete = op == JSOP_DELPROP || op == JSOP_STRICTDELPROP;
1806 2 : RootedAtom prop(cx, (op == JSOP_LENGTH) ? cx->names().length : loadAtom(pc));
1807 1 : return (hasDelete ? write("(delete ") : true) &&
1808 1 : decompilePCForStackOperand(pc, -1) &&
1809 1 : (IsIdentifier(prop)
1810 1 : ? write(".") && quote(prop, '\0')
1811 4 : : write("[") && quote(prop, '\'') && write("]")) &&
1812 1 : (hasDelete ? write(")") : true);
1813 : }
1814 : case JSOP_GETPROP_SUPER:
1815 : {
1816 0 : RootedAtom prop(cx, loadAtom(pc));
1817 0 : return write("super.") &&
1818 0 : quote(prop, '\0');
1819 : }
1820 : case JSOP_SETELEM:
1821 : case JSOP_STRICTSETELEM:
1822 : // NOTE: We don't show the right hand side of the operation because
1823 : // it's used in error messages like: "a[0] is not readable".
1824 : //
1825 : // We could though.
1826 0 : return decompilePCForStackOperand(pc, -3) &&
1827 0 : write("[") &&
1828 0 : decompilePCForStackOperand(pc, -2) &&
1829 0 : write("]");
1830 :
1831 : case JSOP_DELELEM:
1832 : case JSOP_STRICTDELELEM:
1833 : case JSOP_GETELEM:
1834 : case JSOP_CALLELEM: {
1835 0 : bool hasDelete = (op == JSOP_DELELEM || op == JSOP_STRICTDELELEM);
1836 0 : return (hasDelete ? write("(delete ") : true) &&
1837 0 : decompilePCForStackOperand(pc, -2) &&
1838 0 : write("[") &&
1839 0 : decompilePCForStackOperand(pc, -1) &&
1840 0 : write("]") &&
1841 0 : (hasDelete ? write(")") : true);
1842 : }
1843 :
1844 : case JSOP_GETELEM_SUPER:
1845 0 : return write("super[") &&
1846 0 : decompilePCForStackOperand(pc, -3) &&
1847 0 : write("]");
1848 : case JSOP_NULL:
1849 0 : return write(js_null_str);
1850 : case JSOP_TRUE:
1851 0 : return write(js_true_str);
1852 : case JSOP_FALSE:
1853 0 : return write(js_false_str);
1854 : case JSOP_ZERO:
1855 : case JSOP_ONE:
1856 : case JSOP_INT8:
1857 : case JSOP_UINT16:
1858 : case JSOP_UINT24:
1859 : case JSOP_INT32:
1860 0 : return sprinter.printf("%d", GetBytecodeInteger(pc));
1861 : case JSOP_STRING:
1862 0 : return quote(loadAtom(pc), '"');
1863 : case JSOP_SYMBOL: {
1864 0 : unsigned i = uint8_t(pc[1]);
1865 0 : MOZ_ASSERT(i < JS::WellKnownSymbolLimit);
1866 0 : if (i < JS::WellKnownSymbolLimit)
1867 0 : return write(cx->names().wellKnownSymbolDescriptions()[i]);
1868 0 : break;
1869 : }
1870 : case JSOP_UNDEFINED:
1871 0 : return write(js_undefined_str);
1872 : case JSOP_GLOBALTHIS:
1873 : // |this| could convert to a very long object initialiser, so cite it by
1874 : // its keyword name.
1875 0 : return write(js_this_str);
1876 : case JSOP_NEWTARGET:
1877 0 : return write("new.target");
1878 : case JSOP_CALL:
1879 : case JSOP_CALL_IGNORES_RV:
1880 : case JSOP_CALLITER:
1881 : case JSOP_FUNCALL:
1882 : case JSOP_FUNAPPLY:
1883 0 : return decompilePCForStackOperand(pc, -int32_t(GET_ARGC(pc) + 2)) &&
1884 0 : write("(...)");
1885 : case JSOP_SPREADCALL:
1886 0 : return decompilePCForStackOperand(pc, -3) &&
1887 0 : write("(...)");
1888 : case JSOP_NEWARRAY:
1889 0 : return write("[]");
1890 : case JSOP_REGEXP: {
1891 0 : RootedObject obj(cx, script->getObject(GET_UINT32_INDEX(pc)));
1892 0 : JSString* str = obj->as<RegExpObject>().toString(cx);
1893 0 : if (!str)
1894 0 : return false;
1895 0 : return write(str);
1896 : }
1897 : case JSOP_NEWARRAY_COPYONWRITE: {
1898 0 : RootedObject obj(cx, script->getObject(GET_UINT32_INDEX(pc)));
1899 0 : Handle<ArrayObject*> aobj = obj.as<ArrayObject>();
1900 0 : if (!write("["))
1901 0 : return false;
1902 0 : for (size_t i = 0; i < aobj->getDenseInitializedLength(); i++) {
1903 0 : if (i > 0 && !write(", "))
1904 0 : return false;
1905 :
1906 0 : RootedValue v(cx, aobj->getDenseElement(i));
1907 0 : MOZ_RELEASE_ASSERT(v.isPrimitive() && !v.isMagic());
1908 :
1909 0 : JSString* str = ValueToSource(cx, v);
1910 0 : if (!str || !write(str))
1911 0 : return false;
1912 : }
1913 0 : return write("]");
1914 : }
1915 : case JSOP_OBJECT: {
1916 0 : JSObject* obj = script->getObject(GET_UINT32_INDEX(pc));
1917 0 : RootedValue objv(cx, ObjectValue(*obj));
1918 0 : JSString* str = ValueToSource(cx, objv);
1919 0 : if (!str)
1920 0 : return false;
1921 0 : return write(str);
1922 : }
1923 : case JSOP_VOID:
1924 0 : return write("(void ") &&
1925 0 : decompilePCForStackOperand(pc, -1) &&
1926 0 : write(")");
1927 :
1928 : case JSOP_SUPERCALL:
1929 : case JSOP_SPREADSUPERCALL:
1930 0 : return write("super(...)");
1931 : case JSOP_SUPERFUN:
1932 0 : return write("super");
1933 :
1934 : case JSOP_EVAL:
1935 : case JSOP_SPREADEVAL:
1936 : case JSOP_STRICTEVAL:
1937 : case JSOP_STRICTSPREADEVAL:
1938 0 : return write("eval(...)");
1939 :
1940 : case JSOP_NEW:
1941 0 : return write("(new ") &&
1942 0 : decompilePCForStackOperand(pc, -int32_t(GET_ARGC(pc) + 3)) &&
1943 0 : write("(...))");
1944 :
1945 : case JSOP_SPREADNEW:
1946 0 : return write("(new ") &&
1947 0 : decompilePCForStackOperand(pc, -4) &&
1948 0 : write("(...))");
1949 :
1950 : case JSOP_TYPEOF:
1951 : case JSOP_TYPEOFEXPR:
1952 0 : return write("(typeof ") &&
1953 0 : decompilePCForStackOperand(pc, -1) &&
1954 0 : write(")");
1955 :
1956 : case JSOP_INITELEM_ARRAY:
1957 0 : return write("[...]");
1958 :
1959 : case JSOP_INITELEM_INC:
1960 0 : if (defIndex == 0)
1961 0 : return write("[...]");
1962 0 : MOZ_ASSERT(defIndex == 1);
1963 : #ifdef DEBUG
1964 : // INDEX won't be be exposed to error message.
1965 0 : if (isStackDump)
1966 0 : return write("INDEX");
1967 : #endif
1968 0 : break;
1969 :
1970 : default:
1971 0 : break;
1972 : }
1973 :
1974 : #ifdef DEBUG
1975 0 : if (isStackDump) {
1976 : // Special decompilation for stack dump.
1977 0 : switch (op) {
1978 : case JSOP_ARGUMENTS:
1979 0 : return write("arguments");
1980 :
1981 : case JSOP_BINDGNAME:
1982 0 : return write("GLOBAL");
1983 :
1984 : case JSOP_BINDNAME:
1985 : case JSOP_BINDVAR:
1986 0 : return write("ENV");
1987 :
1988 : case JSOP_CALLEE:
1989 0 : return write("CALLEE");
1990 :
1991 : case JSOP_CALLSITEOBJ:
1992 0 : return write("OBJ");
1993 :
1994 : case JSOP_CLASSCONSTRUCTOR:
1995 : case JSOP_DERIVEDCONSTRUCTOR:
1996 0 : return write("CONSTRUCTOR");
1997 :
1998 : case JSOP_DOUBLE:
1999 0 : return sprinter.printf("%lf", script->getConst(GET_UINT32_INDEX(pc)).toDouble());
2000 :
2001 : case JSOP_EXCEPTION:
2002 0 : return write("EXCEPTION");
2003 :
2004 : case JSOP_FINALLY:
2005 0 : if (defIndex == 0)
2006 0 : return write("THROWING");
2007 0 : MOZ_ASSERT(defIndex == 1);
2008 0 : return write("PC");
2009 :
2010 : case JSOP_GIMPLICITTHIS:
2011 : case JSOP_FUNCTIONTHIS:
2012 : case JSOP_IMPLICITTHIS:
2013 0 : return write("THIS");
2014 :
2015 : case JSOP_FUNWITHPROTO:
2016 0 : return write("FUN");
2017 :
2018 : case JSOP_GENERATOR:
2019 0 : return write("GENERATOR");
2020 :
2021 : case JSOP_GETIMPORT:
2022 0 : return write("VAL");
2023 :
2024 : case JSOP_GETRVAL:
2025 0 : return write("RVAL");
2026 :
2027 : case JSOP_HOLE:
2028 0 : return write("HOLE");
2029 :
2030 : case JSOP_ISGENCLOSING:
2031 : // For stack dump, defIndex == 0 is not used.
2032 0 : MOZ_ASSERT(defIndex == 1);
2033 0 : return write("ISGENCLOSING");
2034 :
2035 : case JSOP_ISNOITER:
2036 : // For stack dump, defIndex == 0 is not used.
2037 0 : MOZ_ASSERT(defIndex == 1);
2038 0 : return write("ISNOITER");
2039 :
2040 : case JSOP_IS_CONSTRUCTING:
2041 0 : return write("JS_IS_CONSTRUCTING");
2042 :
2043 : case JSOP_ITER:
2044 0 : return write("ITER");
2045 :
2046 : case JSOP_LAMBDA:
2047 : case JSOP_LAMBDA_ARROW:
2048 : case JSOP_TOASYNC:
2049 : case JSOP_TOASYNCGEN:
2050 0 : return write("FUN");
2051 :
2052 : case JSOP_TOASYNCITER:
2053 0 : return write("ASYNCITER");
2054 :
2055 : case JSOP_MOREITER:
2056 : // For stack dump, defIndex == 0 is not used.
2057 0 : MOZ_ASSERT(defIndex == 1);
2058 0 : return write("MOREITER");
2059 :
2060 : case JSOP_MUTATEPROTO:
2061 0 : return write("SUCCEEDED");
2062 :
2063 : case JSOP_NEWINIT:
2064 : case JSOP_NEWOBJECT:
2065 : case JSOP_OBJWITHPROTO:
2066 0 : return write("OBJ");
2067 :
2068 : case JSOP_OPTIMIZE_SPREADCALL:
2069 : // For stack dump, defIndex == 0 is not used.
2070 0 : MOZ_ASSERT(defIndex == 1);
2071 0 : return write("OPTIMIZED");
2072 :
2073 : case JSOP_REST:
2074 0 : return write("REST");
2075 :
2076 : case JSOP_RESUME:
2077 0 : return write("RVAL");
2078 :
2079 : case JSOP_SPREADCALLARRAY:
2080 0 : return write("[]");
2081 :
2082 : case JSOP_SUPERBASE:
2083 0 : return write("HOMEOBJECTPROTO");
2084 :
2085 : case JSOP_TOID:
2086 0 : return write("TOID(") &&
2087 0 : decompilePCForStackOperand(pc, -1) &&
2088 0 : write(")");
2089 : case JSOP_TOSTRING:
2090 0 : return write("TOSTRING(") &&
2091 0 : decompilePCForStackOperand(pc, -1) &&
2092 0 : write(")");
2093 :
2094 : case JSOP_UNINITIALIZED:
2095 0 : return write("UNINITIALIZED");
2096 :
2097 : case JSOP_AWAIT:
2098 : case JSOP_YIELD:
2099 : // Printing "yield SOMETHING" is confusing since the operand doesn't
2100 : // match to the syntax, since the stack operand for "yield 10" is
2101 : // the result object, not 10.
2102 0 : return write("RVAL");
2103 :
2104 : default:
2105 0 : break;
2106 : }
2107 0 : return write("<unknown>");
2108 : }
2109 : #endif /* DEBUG */
2110 :
2111 0 : return write("(intermediate value)");
2112 : }
2113 :
2114 : bool
2115 1 : ExpressionDecompiler::decompilePC(const OffsetAndDefIndex& offsetAndDefIndex)
2116 : {
2117 1 : if (offsetAndDefIndex.isSpecial()) {
2118 : #ifdef DEBUG
2119 0 : if (isStackDump) {
2120 0 : if (offsetAndDefIndex.isMerged()) {
2121 0 : if (!write("merged<"))
2122 0 : return false;
2123 0 : } else if (offsetAndDefIndex.isIgnored()) {
2124 0 : if (!write("ignored<"))
2125 0 : return false;
2126 : }
2127 :
2128 0 : if (!decompilePC(script->offsetToPC(offsetAndDefIndex.specialOffset()),
2129 0 : offsetAndDefIndex.specialDefIndex()))
2130 : {
2131 0 : return false;
2132 : }
2133 :
2134 0 : if (!write(">"))
2135 0 : return false;
2136 :
2137 0 : return true;
2138 : }
2139 : #endif /* DEBUG */
2140 0 : return write("(intermediate value)");
2141 : }
2142 :
2143 1 : return decompilePC(script->offsetToPC(offsetAndDefIndex.offset()),
2144 2 : offsetAndDefIndex.defIndex());
2145 : }
2146 :
2147 : bool
2148 2 : ExpressionDecompiler::init()
2149 : {
2150 2 : assertSameCompartment(cx, script);
2151 :
2152 2 : if (!sprinter.init())
2153 0 : return false;
2154 :
2155 2 : if (!parser.parse())
2156 0 : return false;
2157 :
2158 2 : return true;
2159 : }
2160 :
2161 : bool
2162 2 : ExpressionDecompiler::write(const char* s)
2163 : {
2164 2 : return sprinter.put(s);
2165 : }
2166 :
2167 : bool
2168 2 : ExpressionDecompiler::write(JSString* str)
2169 : {
2170 2 : if (str == cx->names().dotThis)
2171 0 : return write("this");
2172 2 : return sprinter.putString(str);
2173 : }
2174 :
2175 : bool
2176 1 : ExpressionDecompiler::quote(JSString* s, uint32_t quote)
2177 : {
2178 1 : return QuoteString(&sprinter, s, quote) != nullptr;
2179 : }
2180 :
2181 : JSAtom*
2182 2 : ExpressionDecompiler::loadAtom(jsbytecode* pc)
2183 : {
2184 2 : return script->getAtom(GET_UINT32_INDEX(pc));
2185 : }
2186 :
2187 : JSAtom*
2188 0 : ExpressionDecompiler::getArg(unsigned slot)
2189 : {
2190 0 : MOZ_ASSERT(script->functionNonDelazifying());
2191 0 : MOZ_ASSERT(slot < script->numArgs());
2192 :
2193 0 : for (PositionalFormalParameterIter fi(script); fi; fi++) {
2194 0 : if (fi.argumentSlot() == slot) {
2195 0 : if (!fi.isDestructured())
2196 0 : return fi.name();
2197 :
2198 : // Destructured arguments have no single binding name.
2199 : static const char destructuredParam[] = "(destructured parameter)";
2200 0 : return Atomize(cx, destructuredParam, strlen(destructuredParam));
2201 : }
2202 : }
2203 :
2204 0 : MOZ_CRASH("No binding");
2205 : }
2206 :
2207 : bool
2208 2 : ExpressionDecompiler::getOutput(char** res)
2209 : {
2210 2 : ptrdiff_t len = sprinter.stringEnd() - sprinter.stringAt(0);
2211 2 : *res = cx->pod_malloc<char>(len + 1);
2212 2 : if (!*res)
2213 0 : return false;
2214 2 : js_memcpy(*res, sprinter.stringAt(0), len);
2215 2 : (*res)[len] = 0;
2216 2 : return true;
2217 : }
2218 :
2219 : } // anonymous namespace
2220 :
2221 : #ifdef DEBUG
2222 : static bool
2223 0 : DecompileAtPCForStackDump(JSContext* cx, HandleScript script,
2224 : const OffsetAndDefIndex& offsetAndDefIndex, Sprinter* sp)
2225 : {
2226 0 : ExpressionDecompiler ed(cx, script);
2227 0 : ed.setStackDump();
2228 0 : if (!ed.init())
2229 0 : return false;
2230 :
2231 0 : if (!ed.decompilePC(offsetAndDefIndex))
2232 0 : return false;
2233 :
2234 : char* result;
2235 0 : if (!ed.getOutput(&result))
2236 0 : return false;
2237 :
2238 0 : if (!sp->put(result))
2239 0 : return false;
2240 :
2241 0 : return true;
2242 : }
2243 : #endif /* DEBUG */
2244 :
2245 : static bool
2246 2 : FindStartPC(JSContext* cx, const FrameIter& iter, int spindex, int skipStackHits, const Value& v,
2247 : jsbytecode** valuepc, uint8_t* defIndex)
2248 : {
2249 2 : jsbytecode* current = *valuepc;
2250 2 : *valuepc = nullptr;
2251 2 : *defIndex = 0;
2252 :
2253 2 : if (spindex == JSDVG_IGNORE_STACK)
2254 0 : return true;
2255 :
2256 : /*
2257 : * FIXME: Fall back if iter.isIon(), since the stack snapshot may be for the
2258 : * previous pc (see bug 831120).
2259 : */
2260 2 : if (iter.isIon())
2261 0 : return true;
2262 :
2263 4 : BytecodeParser parser(cx, iter.script());
2264 2 : if (!parser.parse())
2265 0 : return false;
2266 :
2267 2 : if (spindex < 0 && spindex + int(parser.stackDepthAtPC(current)) < 0)
2268 0 : spindex = JSDVG_SEARCH_STACK;
2269 :
2270 2 : if (spindex == JSDVG_SEARCH_STACK) {
2271 2 : size_t index = iter.numFrameSlots();
2272 :
2273 : // The decompiler may be called from inside functions that are not
2274 : // called from script, but via the C++ API directly, such as
2275 : // Invoke. In that case, the youngest script frame may have a
2276 : // completely unrelated pc and stack depth, so we give up.
2277 2 : if (index < size_t(parser.stackDepthAtPC(current)))
2278 0 : return true;
2279 :
2280 : // We search from fp->sp to base to find the most recently calculated
2281 : // value matching v under assumption that it is the value that caused
2282 : // the exception.
2283 2 : int stackHits = 0;
2284 2 : Value s;
2285 2 : do {
2286 2 : if (!index)
2287 0 : return true;
2288 2 : s = iter.frameSlotValue(--index);
2289 2 : } while (s != v || stackHits++ != skipStackHits);
2290 :
2291 :
2292 : // If the current PC has fewer values on the stack than the index we are
2293 : // looking for, the blamed value must be one pushed by the current
2294 : // bytecode (e.g. JSOP_MOREITER), so restore *valuepc.
2295 2 : if (index < size_t(parser.stackDepthAtPC(current))) {
2296 2 : *valuepc = parser.pcForStackOperand(current, index, defIndex);
2297 : } else {
2298 0 : *valuepc = current;
2299 0 : *defIndex = index - size_t(parser.stackDepthAtPC(current));
2300 : }
2301 : } else {
2302 0 : *valuepc = parser.pcForStackOperand(current, spindex, defIndex);
2303 : }
2304 2 : return true;
2305 : }
2306 :
2307 : static bool
2308 2 : DecompileExpressionFromStack(JSContext* cx, int spindex, int skipStackHits, HandleValue v, char** res)
2309 : {
2310 2 : MOZ_ASSERT(spindex < 0 ||
2311 : spindex == JSDVG_IGNORE_STACK ||
2312 : spindex == JSDVG_SEARCH_STACK);
2313 :
2314 2 : *res = nullptr;
2315 :
2316 : #ifdef JS_MORE_DETERMINISTIC
2317 : /*
2318 : * Give up if we need deterministic behavior for differential testing.
2319 : * IonMonkey doesn't use InterpreterFrames and this ensures we get the same
2320 : * error messages.
2321 : */
2322 : return true;
2323 : #endif
2324 :
2325 4 : FrameIter frameIter(cx);
2326 :
2327 2 : if (frameIter.done() || !frameIter.hasScript() || frameIter.compartment() != cx->compartment())
2328 0 : return true;
2329 :
2330 4 : RootedScript script(cx, frameIter.script());
2331 2 : jsbytecode* valuepc = frameIter.pc();
2332 :
2333 2 : MOZ_ASSERT(script->containsPC(valuepc));
2334 :
2335 : // Give up if in prologue.
2336 2 : if (valuepc < script->main())
2337 0 : return true;
2338 :
2339 : uint8_t defIndex;
2340 2 : if (!FindStartPC(cx, frameIter, spindex, skipStackHits, v, &valuepc, &defIndex))
2341 0 : return false;
2342 2 : if (!valuepc)
2343 0 : return true;
2344 :
2345 4 : ExpressionDecompiler ed(cx, script);
2346 2 : if (!ed.init())
2347 0 : return false;
2348 2 : if (!ed.decompilePC(valuepc, defIndex))
2349 0 : return false;
2350 :
2351 2 : return ed.getOutput(res);
2352 : }
2353 :
2354 : UniqueChars
2355 2 : js::DecompileValueGenerator(JSContext* cx, int spindex, HandleValue v,
2356 : HandleString fallbackArg, int skipStackHits)
2357 : {
2358 4 : RootedString fallback(cx, fallbackArg);
2359 : {
2360 : char* result;
2361 2 : if (!DecompileExpressionFromStack(cx, spindex, skipStackHits, v, &result))
2362 2 : return nullptr;
2363 2 : if (result) {
2364 2 : if (strcmp(result, "(intermediate value)"))
2365 2 : return UniqueChars(result);
2366 0 : js_free(result);
2367 : }
2368 : }
2369 0 : if (!fallback) {
2370 0 : if (v.isUndefined())
2371 0 : return UniqueChars(JS_strdup(cx, js_undefined_str)); // Prevent users from seeing "(void 0)"
2372 0 : fallback = ValueToSource(cx, v);
2373 0 : if (!fallback)
2374 0 : return UniqueChars(nullptr);
2375 : }
2376 :
2377 0 : return UniqueChars(JS_EncodeString(cx, fallback));
2378 : }
2379 :
2380 : static bool
2381 0 : DecompileArgumentFromStack(JSContext* cx, int formalIndex, char** res)
2382 : {
2383 0 : MOZ_ASSERT(formalIndex >= 0);
2384 :
2385 0 : *res = nullptr;
2386 :
2387 : #ifdef JS_MORE_DETERMINISTIC
2388 : /* See note in DecompileExpressionFromStack. */
2389 : return true;
2390 : #endif
2391 :
2392 : /*
2393 : * Settle on the nearest script frame, which should be the builtin that
2394 : * called the intrinsic.
2395 : */
2396 0 : FrameIter frameIter(cx);
2397 0 : MOZ_ASSERT(!frameIter.done());
2398 0 : MOZ_ASSERT(frameIter.script()->selfHosted());
2399 :
2400 : /*
2401 : * Get the second-to-top frame, the non-self-hosted caller of the builtin
2402 : * that called the intrinsic.
2403 : */
2404 0 : ++frameIter;
2405 0 : if (frameIter.done() ||
2406 0 : !frameIter.hasScript() ||
2407 0 : frameIter.script()->selfHosted() ||
2408 0 : frameIter.compartment() != cx->compartment())
2409 : {
2410 0 : return true;
2411 : }
2412 :
2413 0 : RootedScript script(cx, frameIter.script());
2414 0 : jsbytecode* current = frameIter.pc();
2415 :
2416 0 : MOZ_ASSERT(script->containsPC(current));
2417 :
2418 0 : if (current < script->main())
2419 0 : return true;
2420 :
2421 : /* Don't handle getters, setters or calls from fun.call/fun.apply. */
2422 0 : JSOp op = JSOp(*current);
2423 0 : if (op != JSOP_CALL && op != JSOP_CALL_IGNORES_RV && op != JSOP_NEW)
2424 0 : return true;
2425 :
2426 0 : if (static_cast<unsigned>(formalIndex) >= GET_ARGC(current))
2427 0 : return true;
2428 :
2429 0 : BytecodeParser parser(cx, script);
2430 0 : if (!parser.parse())
2431 0 : return false;
2432 :
2433 0 : bool pushedNewTarget = op == JSOP_NEW;
2434 0 : int formalStackIndex = parser.stackDepthAtPC(current) - GET_ARGC(current) - pushedNewTarget +
2435 0 : formalIndex;
2436 0 : MOZ_ASSERT(formalStackIndex >= 0);
2437 0 : if (uint32_t(formalStackIndex) >= parser.stackDepthAtPC(current))
2438 0 : return true;
2439 :
2440 0 : ExpressionDecompiler ed(cx, script);
2441 0 : if (!ed.init())
2442 0 : return false;
2443 0 : if (!ed.decompilePCForStackOperand(current, formalStackIndex))
2444 0 : return false;
2445 :
2446 0 : return ed.getOutput(res);
2447 : }
2448 :
2449 : char*
2450 0 : js::DecompileArgument(JSContext* cx, int formalIndex, HandleValue v)
2451 : {
2452 : {
2453 : char* result;
2454 0 : if (!DecompileArgumentFromStack(cx, formalIndex, &result))
2455 0 : return nullptr;
2456 0 : if (result) {
2457 0 : if (strcmp(result, "(intermediate value)"))
2458 0 : return result;
2459 0 : js_free(result);
2460 : }
2461 : }
2462 0 : if (v.isUndefined())
2463 0 : return JS_strdup(cx, js_undefined_str); // Prevent users from seeing "(void 0)"
2464 :
2465 0 : RootedString fallback(cx, ValueToSource(cx, v));
2466 0 : if (!fallback)
2467 0 : return nullptr;
2468 :
2469 0 : return JS_EncodeString(cx, fallback);
2470 : }
2471 :
2472 : bool
2473 0 : js::CallResultEscapes(jsbytecode* pc)
2474 : {
2475 : /*
2476 : * If we see any of these sequences, the result is unused:
2477 : * - call / pop
2478 : *
2479 : * If we see any of these sequences, the result is only tested for nullness:
2480 : * - call / ifeq
2481 : * - call / not / ifeq
2482 : */
2483 :
2484 0 : if (*pc == JSOP_CALL)
2485 0 : pc += JSOP_CALL_LENGTH;
2486 0 : else if (*pc == JSOP_CALL_IGNORES_RV)
2487 0 : pc += JSOP_CALL_IGNORES_RV_LENGTH;
2488 0 : else if (*pc == JSOP_SPREADCALL)
2489 0 : pc += JSOP_SPREADCALL_LENGTH;
2490 : else
2491 0 : return true;
2492 :
2493 0 : if (*pc == JSOP_POP)
2494 0 : return false;
2495 :
2496 0 : if (*pc == JSOP_NOT)
2497 0 : pc += JSOP_NOT_LENGTH;
2498 :
2499 0 : return *pc != JSOP_IFEQ;
2500 : }
2501 :
2502 : extern bool
2503 0 : js::IsValidBytecodeOffset(JSContext* cx, JSScript* script, size_t offset)
2504 : {
2505 : // This could be faster (by following jump instructions if the target is <= offset).
2506 0 : for (BytecodeRange r(cx, script); !r.empty(); r.popFront()) {
2507 0 : size_t here = r.frontOffset();
2508 0 : if (here >= offset)
2509 0 : return here == offset;
2510 : }
2511 0 : return false;
2512 : }
2513 :
2514 : /*
2515 : * There are three possible PCCount profiling states:
2516 : *
2517 : * 1. None: Neither scripts nor the runtime have count information.
2518 : * 2. Profile: Active scripts have count information, the runtime does not.
2519 : * 3. Query: Scripts do not have count information, the runtime does.
2520 : *
2521 : * When starting to profile scripts, counting begins immediately, with all JIT
2522 : * code discarded and recompiled with counts as necessary. Active interpreter
2523 : * frames will not begin profiling until they begin executing another script
2524 : * (via a call or return).
2525 : *
2526 : * The below API functions manage transitions to new states, according
2527 : * to the table below.
2528 : *
2529 : * Old State
2530 : * -------------------------
2531 : * Function None Profile Query
2532 : * --------
2533 : * StartPCCountProfiling Profile Profile Profile
2534 : * StopPCCountProfiling None Query Query
2535 : * PurgePCCounts None None None
2536 : */
2537 :
2538 : static void
2539 0 : ReleaseScriptCounts(FreeOp* fop)
2540 : {
2541 0 : JSRuntime* rt = fop->runtime();
2542 0 : MOZ_ASSERT(rt->scriptAndCountsVector);
2543 :
2544 0 : fop->delete_(rt->scriptAndCountsVector.ref());
2545 0 : rt->scriptAndCountsVector = nullptr;
2546 0 : }
2547 :
2548 : JS_FRIEND_API(void)
2549 0 : js::StartPCCountProfiling(JSContext* cx)
2550 : {
2551 0 : JSRuntime* rt = cx->runtime();
2552 :
2553 0 : if (rt->profilingScripts)
2554 0 : return;
2555 :
2556 0 : if (rt->scriptAndCountsVector)
2557 0 : ReleaseScriptCounts(rt->defaultFreeOp());
2558 :
2559 0 : ReleaseAllJITCode(rt->defaultFreeOp());
2560 :
2561 0 : rt->profilingScripts = true;
2562 : }
2563 :
2564 : JS_FRIEND_API(void)
2565 0 : js::StopPCCountProfiling(JSContext* cx)
2566 : {
2567 0 : JSRuntime* rt = cx->runtime();
2568 :
2569 0 : if (!rt->profilingScripts)
2570 0 : return;
2571 0 : MOZ_ASSERT(!rt->scriptAndCountsVector);
2572 :
2573 0 : ReleaseAllJITCode(rt->defaultFreeOp());
2574 :
2575 0 : auto* vec = cx->new_<PersistentRooted<ScriptAndCountsVector>>(cx,
2576 0 : ScriptAndCountsVector(SystemAllocPolicy()));
2577 0 : if (!vec)
2578 0 : return;
2579 :
2580 0 : for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
2581 0 : for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next()) {
2582 0 : if (script->hasScriptCounts() && script->types()) {
2583 0 : if (!vec->append(script))
2584 0 : return;
2585 : }
2586 : }
2587 : }
2588 :
2589 0 : rt->profilingScripts = false;
2590 0 : rt->scriptAndCountsVector = vec;
2591 : }
2592 :
2593 : JS_FRIEND_API(void)
2594 0 : js::PurgePCCounts(JSContext* cx)
2595 : {
2596 0 : JSRuntime* rt = cx->runtime();
2597 :
2598 0 : if (!rt->scriptAndCountsVector)
2599 0 : return;
2600 0 : MOZ_ASSERT(!rt->profilingScripts);
2601 :
2602 0 : ReleaseScriptCounts(rt->defaultFreeOp());
2603 : }
2604 :
2605 : JS_FRIEND_API(size_t)
2606 0 : js::GetPCCountScriptCount(JSContext* cx)
2607 : {
2608 0 : JSRuntime* rt = cx->runtime();
2609 :
2610 0 : if (!rt->scriptAndCountsVector)
2611 0 : return 0;
2612 :
2613 0 : return rt->scriptAndCountsVector->length();
2614 : }
2615 :
2616 : enum MaybeComma {NO_COMMA, COMMA};
2617 :
2618 : static MOZ_MUST_USE bool
2619 0 : AppendJSONProperty(StringBuffer& buf, const char* name, MaybeComma comma = COMMA)
2620 : {
2621 0 : if (comma && !buf.append(','))
2622 0 : return false;
2623 :
2624 0 : return buf.append('\"') &&
2625 0 : buf.append(name, strlen(name)) &&
2626 0 : buf.append("\":", 2);
2627 : }
2628 :
2629 : JS_FRIEND_API(JSString*)
2630 0 : js::GetPCCountScriptSummary(JSContext* cx, size_t index)
2631 : {
2632 0 : JSRuntime* rt = cx->runtime();
2633 :
2634 0 : if (!rt->scriptAndCountsVector || index >= rt->scriptAndCountsVector->length()) {
2635 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL);
2636 0 : return nullptr;
2637 : }
2638 :
2639 0 : const ScriptAndCounts& sac = (*rt->scriptAndCountsVector)[index];
2640 0 : RootedScript script(cx, sac.script);
2641 :
2642 : /*
2643 : * OOM on buffer appends here will not be caught immediately, but since
2644 : * StringBuffer uses a TempAllocPolicy will trigger an exception on the
2645 : * context if they occur, which we'll catch before returning.
2646 : */
2647 0 : StringBuffer buf(cx);
2648 :
2649 0 : if (!buf.append('{'))
2650 0 : return nullptr;
2651 :
2652 0 : if (!AppendJSONProperty(buf, "file", NO_COMMA))
2653 0 : return nullptr;
2654 0 : JSString* str = JS_NewStringCopyZ(cx, script->filename());
2655 0 : if (!str || !(str = StringToSource(cx, str)))
2656 0 : return nullptr;
2657 0 : if (!buf.append(str))
2658 0 : return nullptr;
2659 :
2660 0 : if (!AppendJSONProperty(buf, "line"))
2661 0 : return nullptr;
2662 0 : if (!NumberValueToStringBuffer(cx, Int32Value(script->lineno()), buf)) {
2663 0 : return nullptr;
2664 : }
2665 :
2666 0 : if (script->functionNonDelazifying()) {
2667 0 : JSAtom* atom = script->functionNonDelazifying()->displayAtom();
2668 0 : if (atom) {
2669 0 : if (!AppendJSONProperty(buf, "name"))
2670 0 : return nullptr;
2671 0 : if (!(str = StringToSource(cx, atom)))
2672 0 : return nullptr;
2673 0 : if (!buf.append(str))
2674 0 : return nullptr;
2675 : }
2676 : }
2677 :
2678 0 : uint64_t total = 0;
2679 :
2680 0 : jsbytecode* codeEnd = script->codeEnd();
2681 0 : for (jsbytecode* pc = script->code(); pc < codeEnd; pc = GetNextPc(pc)) {
2682 0 : const PCCounts* counts = sac.maybeGetPCCounts(pc);
2683 0 : if (!counts)
2684 0 : continue;
2685 0 : total += counts->numExec();
2686 : }
2687 :
2688 0 : if (!AppendJSONProperty(buf, "totals"))
2689 0 : return nullptr;
2690 0 : if (!buf.append('{'))
2691 0 : return nullptr;
2692 :
2693 0 : if (!AppendJSONProperty(buf, PCCounts::numExecName, NO_COMMA))
2694 0 : return nullptr;
2695 0 : if (!NumberValueToStringBuffer(cx, DoubleValue(total), buf))
2696 0 : return nullptr;
2697 :
2698 0 : uint64_t ionActivity = 0;
2699 0 : jit::IonScriptCounts* ionCounts = sac.getIonCounts();
2700 0 : while (ionCounts) {
2701 0 : for (size_t i = 0; i < ionCounts->numBlocks(); i++)
2702 0 : ionActivity += ionCounts->block(i).hitCount();
2703 0 : ionCounts = ionCounts->previous();
2704 : }
2705 0 : if (ionActivity) {
2706 0 : if (!AppendJSONProperty(buf, "ion", COMMA))
2707 0 : return nullptr;
2708 0 : if (!NumberValueToStringBuffer(cx, DoubleValue(ionActivity), buf))
2709 0 : return nullptr;
2710 : }
2711 :
2712 0 : if (!buf.append('}'))
2713 0 : return nullptr;
2714 0 : if (!buf.append('}'))
2715 0 : return nullptr;
2716 :
2717 0 : MOZ_ASSERT(!cx->isExceptionPending());
2718 :
2719 0 : return buf.finishString();
2720 : }
2721 :
2722 : static bool
2723 0 : GetPCCountJSON(JSContext* cx, const ScriptAndCounts& sac, StringBuffer& buf)
2724 : {
2725 0 : RootedScript script(cx, sac.script);
2726 :
2727 0 : if (!buf.append('{'))
2728 0 : return false;
2729 0 : if (!AppendJSONProperty(buf, "text", NO_COMMA))
2730 0 : return false;
2731 :
2732 0 : JSString* str = JS_DecompileScript(cx, script, nullptr, 0);
2733 0 : if (!str || !(str = StringToSource(cx, str)))
2734 0 : return false;
2735 :
2736 0 : if (!buf.append(str))
2737 0 : return false;
2738 :
2739 0 : if (!AppendJSONProperty(buf, "line"))
2740 0 : return false;
2741 0 : if (!NumberValueToStringBuffer(cx, Int32Value(script->lineno()), buf))
2742 0 : return false;
2743 :
2744 0 : if (!AppendJSONProperty(buf, "opcodes"))
2745 0 : return false;
2746 0 : if (!buf.append('['))
2747 0 : return false;
2748 0 : bool comma = false;
2749 :
2750 0 : SrcNoteLineScanner scanner(script->notes(), script->lineno());
2751 0 : uint64_t hits = 0;
2752 :
2753 0 : jsbytecode* end = script->codeEnd();
2754 0 : for (jsbytecode* pc = script->code(); pc < end; pc = GetNextPc(pc)) {
2755 0 : size_t offset = script->pcToOffset(pc);
2756 0 : JSOp op = JSOp(*pc);
2757 :
2758 : // If the current instruction is a jump target,
2759 : // then update the number of hits.
2760 0 : const PCCounts* counts = sac.maybeGetPCCounts(pc);
2761 0 : if (counts)
2762 0 : hits = counts->numExec();
2763 :
2764 0 : if (comma && !buf.append(','))
2765 0 : return false;
2766 0 : comma = true;
2767 :
2768 0 : if (!buf.append('{'))
2769 0 : return false;
2770 :
2771 0 : if (!AppendJSONProperty(buf, "id", NO_COMMA))
2772 0 : return false;
2773 0 : if (!NumberValueToStringBuffer(cx, Int32Value(offset), buf))
2774 0 : return false;
2775 :
2776 0 : scanner.advanceTo(offset);
2777 :
2778 0 : if (!AppendJSONProperty(buf, "line"))
2779 0 : return false;
2780 0 : if (!NumberValueToStringBuffer(cx, Int32Value(scanner.getLine()), buf))
2781 0 : return false;
2782 :
2783 : {
2784 0 : const char* name = CodeName[op];
2785 0 : if (!AppendJSONProperty(buf, "name"))
2786 0 : return false;
2787 0 : if (!buf.append('\"'))
2788 0 : return false;
2789 0 : if (!buf.append(name, strlen(name)))
2790 0 : return false;
2791 0 : if (!buf.append('\"'))
2792 0 : return false;
2793 : }
2794 :
2795 : {
2796 0 : ExpressionDecompiler ed(cx, script);
2797 0 : if (!ed.init())
2798 0 : return false;
2799 : // defIndex passed here is not used.
2800 0 : if (!ed.decompilePC(pc, /* defIndex = */ 0))
2801 0 : return false;
2802 : char* text;
2803 0 : if (!ed.getOutput(&text))
2804 0 : return false;
2805 0 : JSString* str = JS_NewStringCopyZ(cx, text);
2806 0 : js_free(text);
2807 0 : if (!AppendJSONProperty(buf, "text"))
2808 0 : return false;
2809 0 : if (!str || !(str = StringToSource(cx, str)))
2810 0 : return false;
2811 0 : if (!buf.append(str))
2812 0 : return false;
2813 : }
2814 :
2815 0 : if (!AppendJSONProperty(buf, "counts"))
2816 0 : return false;
2817 0 : if (!buf.append('{'))
2818 0 : return false;
2819 :
2820 0 : if (hits > 0) {
2821 0 : if (!AppendJSONProperty(buf, PCCounts::numExecName, NO_COMMA))
2822 0 : return false;
2823 0 : if (!NumberValueToStringBuffer(cx, DoubleValue(hits), buf))
2824 0 : return false;
2825 : }
2826 :
2827 0 : if (!buf.append('}'))
2828 0 : return false;
2829 0 : if (!buf.append('}'))
2830 0 : return false;
2831 :
2832 : // If the current instruction has thrown,
2833 : // then decrement the hit counts with the number of throws.
2834 0 : counts = sac.maybeGetThrowCounts(pc);
2835 0 : if (counts)
2836 0 : hits -= counts->numExec();
2837 : }
2838 :
2839 0 : if (!buf.append(']'))
2840 0 : return false;
2841 :
2842 0 : jit::IonScriptCounts* ionCounts = sac.getIonCounts();
2843 0 : if (ionCounts) {
2844 0 : if (!AppendJSONProperty(buf, "ion"))
2845 0 : return false;
2846 0 : if (!buf.append('['))
2847 0 : return false;
2848 0 : bool comma = false;
2849 0 : while (ionCounts) {
2850 0 : if (comma && !buf.append(','))
2851 0 : return false;
2852 0 : comma = true;
2853 :
2854 0 : if (!buf.append('['))
2855 0 : return false;
2856 0 : for (size_t i = 0; i < ionCounts->numBlocks(); i++) {
2857 0 : if (i && !buf.append(','))
2858 0 : return false;
2859 0 : const jit::IonBlockCounts& block = ionCounts->block(i);
2860 :
2861 0 : if (!buf.append('{'))
2862 0 : return false;
2863 0 : if (!AppendJSONProperty(buf, "id", NO_COMMA))
2864 0 : return false;
2865 0 : if (!NumberValueToStringBuffer(cx, Int32Value(block.id()), buf))
2866 0 : return false;
2867 0 : if (!AppendJSONProperty(buf, "offset"))
2868 0 : return false;
2869 0 : if (!NumberValueToStringBuffer(cx, Int32Value(block.offset()), buf))
2870 0 : return false;
2871 0 : if (!AppendJSONProperty(buf, "successors"))
2872 0 : return false;
2873 0 : if (!buf.append('['))
2874 0 : return false;
2875 0 : for (size_t j = 0; j < block.numSuccessors(); j++) {
2876 0 : if (j && !buf.append(','))
2877 0 : return false;
2878 0 : if (!NumberValueToStringBuffer(cx, Int32Value(block.successor(j)), buf))
2879 0 : return false;
2880 : }
2881 0 : if (!buf.append(']'))
2882 0 : return false;
2883 0 : if (!AppendJSONProperty(buf, "hits"))
2884 0 : return false;
2885 0 : if (!NumberValueToStringBuffer(cx, DoubleValue(block.hitCount()), buf))
2886 0 : return false;
2887 :
2888 0 : if (!AppendJSONProperty(buf, "code"))
2889 0 : return false;
2890 0 : JSString* str = JS_NewStringCopyZ(cx, block.code());
2891 0 : if (!str || !(str = StringToSource(cx, str)))
2892 0 : return false;
2893 0 : if (!buf.append(str))
2894 0 : return false;
2895 0 : if (!buf.append('}'))
2896 0 : return false;
2897 : }
2898 0 : if (!buf.append(']'))
2899 0 : return false;
2900 :
2901 0 : ionCounts = ionCounts->previous();
2902 : }
2903 0 : if (!buf.append(']'))
2904 0 : return false;
2905 : }
2906 :
2907 0 : if (!buf.append('}'))
2908 0 : return false;
2909 :
2910 0 : MOZ_ASSERT(!cx->isExceptionPending());
2911 0 : return true;
2912 : }
2913 :
2914 : JS_FRIEND_API(JSString*)
2915 0 : js::GetPCCountScriptContents(JSContext* cx, size_t index)
2916 : {
2917 0 : JSRuntime* rt = cx->runtime();
2918 :
2919 0 : if (!rt->scriptAndCountsVector || index >= rt->scriptAndCountsVector->length()) {
2920 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL);
2921 0 : return nullptr;
2922 : }
2923 :
2924 0 : const ScriptAndCounts& sac = (*rt->scriptAndCountsVector)[index];
2925 0 : JSScript* script = sac.script;
2926 :
2927 0 : StringBuffer buf(cx);
2928 :
2929 : {
2930 0 : AutoCompartment ac(cx, &script->global());
2931 0 : if (!GetPCCountJSON(cx, sac, buf))
2932 0 : return nullptr;
2933 : }
2934 :
2935 0 : return buf.finishString();
2936 : }
2937 :
2938 : static bool
2939 0 : GenerateLcovInfo(JSContext* cx, JSCompartment* comp, GenericPrinter& out)
2940 : {
2941 0 : JSRuntime* rt = cx->runtime();
2942 :
2943 : // Collect the list of scripts which are part of the current compartment.
2944 : {
2945 0 : js::gc::AutoPrepareForTracing apft(cx, SkipAtoms);
2946 : }
2947 0 : Rooted<ScriptVector> topScripts(cx, ScriptVector(cx));
2948 0 : for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
2949 0 : for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next()) {
2950 0 : if (script->compartment() != comp ||
2951 0 : !script->isTopLevel() ||
2952 0 : !script->filename())
2953 : {
2954 0 : continue;
2955 : }
2956 :
2957 0 : if (!topScripts.append(script))
2958 0 : return false;
2959 : }
2960 : }
2961 :
2962 0 : if (topScripts.length() == 0)
2963 0 : return true;
2964 :
2965 : // Collect code coverage info for one compartment.
2966 0 : coverage::LCovCompartment compCover;
2967 0 : for (JSScript* topLevel: topScripts) {
2968 0 : RootedScript topScript(cx, topLevel);
2969 :
2970 : // We found the top-level script, visit all the functions reachable
2971 : // from the top-level function, and delazify them.
2972 0 : Rooted<ScriptVector> queue(cx, ScriptVector(cx));
2973 0 : if (!queue.append(topLevel))
2974 0 : return false;
2975 :
2976 0 : RootedScript script(cx);
2977 0 : RootedFunction fun(cx);
2978 0 : do {
2979 0 : script = queue.popCopy();
2980 0 : if (script->filename())
2981 0 : compCover.collectCodeCoverageInfo(comp, script, script->filename());
2982 :
2983 : // Iterate from the last to the first object in order to have
2984 : // the functions them visited in the opposite order when popping
2985 : // elements from the stack of remaining scripts, such that the
2986 : // functions are more-less listed with increasing line numbers.
2987 0 : if (!script->hasObjects())
2988 0 : continue;
2989 0 : size_t idx = script->objects()->length;
2990 0 : while (idx--) {
2991 0 : JSObject* obj = script->getObject(idx);
2992 :
2993 : // Only continue on JSFunction objects.
2994 0 : if (!obj->is<JSFunction>())
2995 0 : continue;
2996 0 : fun = &obj->as<JSFunction>();
2997 :
2998 : // Let's skip wasm for now.
2999 0 : if (!fun->isInterpreted())
3000 0 : continue;
3001 :
3002 : // Queue the script in the list of script associated to the
3003 : // current source.
3004 0 : JSScript* childScript = JSFunction::getOrCreateScript(cx, fun);
3005 0 : if (!childScript || !queue.append(childScript))
3006 0 : return false;
3007 : }
3008 0 : } while (!queue.empty());
3009 : }
3010 :
3011 0 : bool isEmpty = true;
3012 0 : compCover.exportInto(out, &isEmpty);
3013 0 : if (out.hadOutOfMemory())
3014 0 : return false;
3015 0 : return true;
3016 : }
3017 :
3018 : JS_FRIEND_API(char*)
3019 0 : js::GetCodeCoverageSummary(JSContext* cx, size_t* length)
3020 : {
3021 0 : Sprinter out(cx);
3022 :
3023 0 : if (!out.init())
3024 0 : return nullptr;
3025 :
3026 0 : if (!GenerateLcovInfo(cx, cx->compartment(), out)) {
3027 0 : JS_ReportOutOfMemory(cx);
3028 0 : return nullptr;
3029 : }
3030 :
3031 0 : if (out.hadOutOfMemory()) {
3032 0 : JS_ReportOutOfMemory(cx);
3033 0 : return nullptr;
3034 : }
3035 :
3036 0 : ptrdiff_t len = out.stringEnd() - out.string();
3037 0 : char* res = cx->pod_malloc<char>(len + 1);
3038 0 : if (!res) {
3039 0 : JS_ReportOutOfMemory(cx);
3040 0 : return nullptr;
3041 : }
3042 :
3043 0 : js_memcpy(res, out.string(), len);
3044 0 : res[len] = 0;
3045 0 : if (length)
3046 0 : *length = len;
3047 0 : return res;
3048 : }
|