|
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 #include <ctype.h> |
|
14 #include <stdarg.h> |
|
15 #include <stdio.h> |
|
16 #include <string.h> |
|
17 |
|
18 #include "jsanalyze.h" |
|
19 #include "jsapi.h" |
|
20 #include "jsatom.h" |
|
21 #include "jscntxt.h" |
|
22 #include "jscompartment.h" |
|
23 #include "jsfun.h" |
|
24 #include "jsnum.h" |
|
25 #include "jsobj.h" |
|
26 #include "jsprf.h" |
|
27 #include "jsscript.h" |
|
28 #include "jsstr.h" |
|
29 #include "jstypes.h" |
|
30 #include "jsutil.h" |
|
31 |
|
32 #include "frontend/BytecodeCompiler.h" |
|
33 #include "frontend/SourceNotes.h" |
|
34 #include "js/CharacterEncoding.h" |
|
35 #include "vm/Opcodes.h" |
|
36 #include "vm/ScopeObject.h" |
|
37 #include "vm/Shape.h" |
|
38 #include "vm/StringBuffer.h" |
|
39 |
|
40 #include "jscntxtinlines.h" |
|
41 #include "jscompartmentinlines.h" |
|
42 #include "jsinferinlines.h" |
|
43 #include "jsobjinlines.h" |
|
44 #include "jsscriptinlines.h" |
|
45 |
|
46 using namespace js; |
|
47 using namespace js::gc; |
|
48 |
|
49 using js::frontend::IsIdentifier; |
|
50 |
|
51 /* |
|
52 * Index limit must stay within 32 bits. |
|
53 */ |
|
54 JS_STATIC_ASSERT(sizeof(uint32_t) * JS_BITS_PER_BYTE >= INDEX_LIMIT_LOG2 + 1); |
|
55 |
|
56 const JSCodeSpec js_CodeSpec[] = { |
|
57 #define MAKE_CODESPEC(op,val,name,token,length,nuses,ndefs,format) {length,nuses,ndefs,format}, |
|
58 FOR_EACH_OPCODE(MAKE_CODESPEC) |
|
59 #undef MAKE_CODESPEC |
|
60 }; |
|
61 |
|
62 const unsigned js_NumCodeSpecs = JS_ARRAY_LENGTH(js_CodeSpec); |
|
63 |
|
64 /* |
|
65 * Each element of the array is either a source literal associated with JS |
|
66 * bytecode or null. |
|
67 */ |
|
68 static const char * const CodeToken[] = { |
|
69 #define TOKEN(op, val, name, token, ...) token, |
|
70 FOR_EACH_OPCODE(TOKEN) |
|
71 #undef TOKEN |
|
72 }; |
|
73 |
|
74 /* |
|
75 * Array of JS bytecode names used by PC count JSON, DEBUG-only js_Disassemble |
|
76 * and JIT debug spew. |
|
77 */ |
|
78 const char * const js_CodeName[] = { |
|
79 #define OPNAME(op, val, name, ...) name, |
|
80 FOR_EACH_OPCODE(OPNAME) |
|
81 #undef OPNAME |
|
82 }; |
|
83 |
|
84 /************************************************************************/ |
|
85 |
|
86 #define COUNTS_LEN 16 |
|
87 |
|
88 size_t |
|
89 js_GetVariableBytecodeLength(jsbytecode *pc) |
|
90 { |
|
91 JSOp op = JSOp(*pc); |
|
92 JS_ASSERT(js_CodeSpec[op].length == -1); |
|
93 switch (op) { |
|
94 case JSOP_TABLESWITCH: { |
|
95 /* Structure: default-jump case-low case-high case1-jump ... */ |
|
96 pc += JUMP_OFFSET_LEN; |
|
97 int32_t low = GET_JUMP_OFFSET(pc); |
|
98 pc += JUMP_OFFSET_LEN; |
|
99 int32_t high = GET_JUMP_OFFSET(pc); |
|
100 unsigned ncases = unsigned(high - low + 1); |
|
101 return 1 + 3 * JUMP_OFFSET_LEN + ncases * JUMP_OFFSET_LEN; |
|
102 } |
|
103 default: |
|
104 MOZ_ASSUME_UNREACHABLE("Unexpected op"); |
|
105 } |
|
106 } |
|
107 |
|
108 unsigned |
|
109 js::StackUses(JSScript *script, jsbytecode *pc) |
|
110 { |
|
111 JSOp op = (JSOp) *pc; |
|
112 const JSCodeSpec &cs = js_CodeSpec[op]; |
|
113 if (cs.nuses >= 0) |
|
114 return cs.nuses; |
|
115 |
|
116 JS_ASSERT(js_CodeSpec[op].nuses == -1); |
|
117 switch (op) { |
|
118 case JSOP_POPN: |
|
119 return GET_UINT16(pc); |
|
120 default: |
|
121 /* stack: fun, this, [argc arguments] */ |
|
122 JS_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL || |
|
123 op == JSOP_FUNCALL || op == JSOP_FUNAPPLY); |
|
124 return 2 + GET_ARGC(pc); |
|
125 } |
|
126 } |
|
127 |
|
128 unsigned |
|
129 js::StackDefs(JSScript *script, jsbytecode *pc) |
|
130 { |
|
131 JSOp op = (JSOp) *pc; |
|
132 const JSCodeSpec &cs = js_CodeSpec[op]; |
|
133 JS_ASSERT (cs.ndefs >= 0); |
|
134 return cs.ndefs; |
|
135 } |
|
136 |
|
137 static const char * const countBaseNames[] = { |
|
138 "interp" |
|
139 }; |
|
140 |
|
141 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) == PCCounts::BASE_LIMIT); |
|
142 |
|
143 static const char * const countAccessNames[] = { |
|
144 "infer_mono", |
|
145 "infer_di", |
|
146 "infer_poly", |
|
147 "infer_barrier", |
|
148 "infer_nobarrier", |
|
149 "observe_undefined", |
|
150 "observe_null", |
|
151 "observe_boolean", |
|
152 "observe_int32", |
|
153 "observe_double", |
|
154 "observe_string", |
|
155 "observe_object" |
|
156 }; |
|
157 |
|
158 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) + |
|
159 JS_ARRAY_LENGTH(countAccessNames) == PCCounts::ACCESS_LIMIT); |
|
160 |
|
161 static const char * const countElementNames[] = { |
|
162 "id_int", |
|
163 "id_double", |
|
164 "id_other", |
|
165 "id_unknown", |
|
166 "elem_typed", |
|
167 "elem_packed", |
|
168 "elem_dense", |
|
169 "elem_other" |
|
170 }; |
|
171 |
|
172 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) + |
|
173 JS_ARRAY_LENGTH(countAccessNames) + |
|
174 JS_ARRAY_LENGTH(countElementNames) == PCCounts::ELEM_LIMIT); |
|
175 |
|
176 static const char * const countPropertyNames[] = { |
|
177 "prop_static", |
|
178 "prop_definite", |
|
179 "prop_other" |
|
180 }; |
|
181 |
|
182 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) + |
|
183 JS_ARRAY_LENGTH(countAccessNames) + |
|
184 JS_ARRAY_LENGTH(countPropertyNames) == PCCounts::PROP_LIMIT); |
|
185 |
|
186 static const char * const countArithNames[] = { |
|
187 "arith_int", |
|
188 "arith_double", |
|
189 "arith_other", |
|
190 "arith_unknown", |
|
191 }; |
|
192 |
|
193 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) + |
|
194 JS_ARRAY_LENGTH(countArithNames) == PCCounts::ARITH_LIMIT); |
|
195 |
|
196 /* static */ const char * |
|
197 PCCounts::countName(JSOp op, size_t which) |
|
198 { |
|
199 JS_ASSERT(which < numCounts(op)); |
|
200 |
|
201 if (which < BASE_LIMIT) |
|
202 return countBaseNames[which]; |
|
203 |
|
204 if (accessOp(op)) { |
|
205 if (which < ACCESS_LIMIT) |
|
206 return countAccessNames[which - BASE_LIMIT]; |
|
207 if (elementOp(op)) |
|
208 return countElementNames[which - ACCESS_LIMIT]; |
|
209 if (propertyOp(op)) |
|
210 return countPropertyNames[which - ACCESS_LIMIT]; |
|
211 MOZ_ASSUME_UNREACHABLE("bad op"); |
|
212 } |
|
213 |
|
214 if (arithOp(op)) |
|
215 return countArithNames[which - BASE_LIMIT]; |
|
216 |
|
217 MOZ_ASSUME_UNREACHABLE("bad op"); |
|
218 } |
|
219 |
|
220 #ifdef JS_ION |
|
221 void |
|
222 js::DumpIonScriptCounts(Sprinter *sp, jit::IonScriptCounts *ionCounts) |
|
223 { |
|
224 Sprint(sp, "IonScript [%lu blocks]:\n", ionCounts->numBlocks()); |
|
225 for (size_t i = 0; i < ionCounts->numBlocks(); i++) { |
|
226 const jit::IonBlockCounts &block = ionCounts->block(i); |
|
227 if (block.hitCount() < 10) |
|
228 continue; |
|
229 Sprint(sp, "BB #%lu [%05u]", block.id(), block.offset()); |
|
230 for (size_t j = 0; j < block.numSuccessors(); j++) |
|
231 Sprint(sp, " -> #%lu", block.successor(j)); |
|
232 Sprint(sp, " :: %llu hits\n", block.hitCount()); |
|
233 Sprint(sp, "%s\n", block.code()); |
|
234 } |
|
235 } |
|
236 #endif |
|
237 |
|
238 void |
|
239 js_DumpPCCounts(JSContext *cx, HandleScript script, js::Sprinter *sp) |
|
240 { |
|
241 JS_ASSERT(script->hasScriptCounts()); |
|
242 |
|
243 #ifdef DEBUG |
|
244 jsbytecode *pc = script->code(); |
|
245 while (pc < script->codeEnd()) { |
|
246 JSOp op = JSOp(*pc); |
|
247 jsbytecode *next = GetNextPc(pc); |
|
248 |
|
249 if (!js_Disassemble1(cx, script, pc, script->pcToOffset(pc), true, sp)) |
|
250 return; |
|
251 |
|
252 size_t total = PCCounts::numCounts(op); |
|
253 double *raw = script->getPCCounts(pc).rawCounts(); |
|
254 |
|
255 Sprint(sp, " {"); |
|
256 bool printed = false; |
|
257 for (size_t i = 0; i < total; i++) { |
|
258 double val = raw[i]; |
|
259 if (val) { |
|
260 if (printed) |
|
261 Sprint(sp, ", "); |
|
262 Sprint(sp, "\"%s\": %.0f", PCCounts::countName(op, i), val); |
|
263 printed = true; |
|
264 } |
|
265 } |
|
266 Sprint(sp, "}\n"); |
|
267 |
|
268 pc = next; |
|
269 } |
|
270 #endif |
|
271 |
|
272 #ifdef JS_ION |
|
273 jit::IonScriptCounts *ionCounts = script->getIonCounts(); |
|
274 |
|
275 while (ionCounts) { |
|
276 DumpIonScriptCounts(sp, ionCounts); |
|
277 ionCounts = ionCounts->previous(); |
|
278 } |
|
279 #endif |
|
280 } |
|
281 |
|
282 ///////////////////////////////////////////////////////////////////// |
|
283 // Bytecode Parser |
|
284 ///////////////////////////////////////////////////////////////////// |
|
285 |
|
286 namespace { |
|
287 |
|
288 class BytecodeParser |
|
289 { |
|
290 class Bytecode |
|
291 { |
|
292 public: |
|
293 Bytecode() { mozilla::PodZero(this); } |
|
294 |
|
295 // Whether this instruction has been analyzed to get its output defines |
|
296 // and stack. |
|
297 bool parsed : 1; |
|
298 |
|
299 // Stack depth before this opcode. |
|
300 uint32_t stackDepth; |
|
301 |
|
302 // Pointer to array of |stackDepth| offsets. An element at position N |
|
303 // in the array is the offset of the opcode that defined the |
|
304 // corresponding stack slot. The top of the stack is at position |
|
305 // |stackDepth - 1|. |
|
306 uint32_t *offsetStack; |
|
307 |
|
308 bool captureOffsetStack(LifoAlloc &alloc, const uint32_t *stack, uint32_t depth) { |
|
309 stackDepth = depth; |
|
310 offsetStack = alloc.newArray<uint32_t>(stackDepth); |
|
311 if (stackDepth) { |
|
312 if (!offsetStack) |
|
313 return false; |
|
314 for (uint32_t n = 0; n < stackDepth; n++) |
|
315 offsetStack[n] = stack[n]; |
|
316 } |
|
317 return true; |
|
318 } |
|
319 |
|
320 // When control-flow merges, intersect the stacks, marking slots that |
|
321 // are defined by different offsets with the UINT32_MAX sentinel. |
|
322 // This is sufficient for forward control-flow. It doesn't grok loops |
|
323 // -- for that you would have to iterate to a fixed point -- but there |
|
324 // shouldn't be operands on the stack at a loop back-edge anyway. |
|
325 void mergeOffsetStack(const uint32_t *stack, uint32_t depth) { |
|
326 JS_ASSERT(depth == stackDepth); |
|
327 for (uint32_t n = 0; n < stackDepth; n++) |
|
328 if (offsetStack[n] != stack[n]) |
|
329 offsetStack[n] = UINT32_MAX; |
|
330 } |
|
331 }; |
|
332 |
|
333 JSContext *cx_; |
|
334 LifoAllocScope allocScope_; |
|
335 RootedScript script_; |
|
336 |
|
337 Bytecode **codeArray_; |
|
338 |
|
339 public: |
|
340 BytecodeParser(JSContext *cx, JSScript *script) |
|
341 : cx_(cx), |
|
342 allocScope_(&cx->tempLifoAlloc()), |
|
343 script_(cx, script), |
|
344 codeArray_(nullptr) { } |
|
345 |
|
346 bool parse(); |
|
347 |
|
348 #ifdef DEBUG |
|
349 bool isReachable(uint32_t offset) { return maybeCode(offset); } |
|
350 bool isReachable(const jsbytecode *pc) { return maybeCode(pc); } |
|
351 #endif |
|
352 |
|
353 uint32_t stackDepthAtPC(uint32_t offset) { |
|
354 // Sometimes the code generator in debug mode asks about the stack depth |
|
355 // of unreachable code (bug 932180 comment 22). Assume that unreachable |
|
356 // code has no operands on the stack. |
|
357 return getCode(offset).stackDepth; |
|
358 } |
|
359 uint32_t stackDepthAtPC(const jsbytecode *pc) { return stackDepthAtPC(script_->pcToOffset(pc)); } |
|
360 |
|
361 uint32_t offsetForStackOperand(uint32_t offset, int operand) { |
|
362 Bytecode &code = getCode(offset); |
|
363 if (operand < 0) { |
|
364 operand += code.stackDepth; |
|
365 JS_ASSERT(operand >= 0); |
|
366 } |
|
367 JS_ASSERT(uint32_t(operand) < code.stackDepth); |
|
368 return code.offsetStack[operand]; |
|
369 } |
|
370 jsbytecode *pcForStackOperand(jsbytecode *pc, int operand) { |
|
371 uint32_t offset = offsetForStackOperand(script_->pcToOffset(pc), operand); |
|
372 if (offset == UINT32_MAX) |
|
373 return nullptr; |
|
374 return script_->offsetToPC(offsetForStackOperand(script_->pcToOffset(pc), operand)); |
|
375 } |
|
376 |
|
377 private: |
|
378 LifoAlloc &alloc() { |
|
379 return allocScope_.alloc(); |
|
380 } |
|
381 |
|
382 void reportOOM() { |
|
383 allocScope_.releaseEarly(); |
|
384 js_ReportOutOfMemory(cx_); |
|
385 } |
|
386 |
|
387 uint32_t numSlots() { |
|
388 return 1 + script_->nfixed() + |
|
389 (script_->functionNonDelazifying() ? script_->functionNonDelazifying()->nargs() : 0); |
|
390 } |
|
391 |
|
392 uint32_t maximumStackDepth() { |
|
393 return script_->nslots() - script_->nfixed(); |
|
394 } |
|
395 |
|
396 Bytecode& getCode(uint32_t offset) { |
|
397 JS_ASSERT(offset < script_->length()); |
|
398 JS_ASSERT(codeArray_[offset]); |
|
399 return *codeArray_[offset]; |
|
400 } |
|
401 Bytecode& getCode(const jsbytecode *pc) { return getCode(script_->pcToOffset(pc)); } |
|
402 |
|
403 Bytecode* maybeCode(uint32_t offset) { |
|
404 JS_ASSERT(offset < script_->length()); |
|
405 return codeArray_[offset]; |
|
406 } |
|
407 Bytecode* maybeCode(const jsbytecode *pc) { return maybeCode(script_->pcToOffset(pc)); } |
|
408 |
|
409 uint32_t simulateOp(JSOp op, uint32_t offset, uint32_t *offsetStack, uint32_t stackDepth); |
|
410 |
|
411 inline bool addJump(uint32_t offset, uint32_t *currentOffset, |
|
412 uint32_t stackDepth, const uint32_t *offsetStack); |
|
413 }; |
|
414 |
|
415 } // anonymous namespace |
|
416 |
|
417 uint32_t |
|
418 BytecodeParser::simulateOp(JSOp op, uint32_t offset, uint32_t *offsetStack, uint32_t stackDepth) |
|
419 { |
|
420 uint32_t nuses = GetUseCount(script_, offset); |
|
421 uint32_t ndefs = GetDefCount(script_, offset); |
|
422 |
|
423 JS_ASSERT(stackDepth >= nuses); |
|
424 stackDepth -= nuses; |
|
425 JS_ASSERT(stackDepth + ndefs <= maximumStackDepth()); |
|
426 |
|
427 // Mark the current offset as defining its values on the offset stack, |
|
428 // unless it just reshuffles the stack. In that case we want to preserve |
|
429 // the opcode that generated the original value. |
|
430 switch (op) { |
|
431 default: |
|
432 for (uint32_t n = 0; n != ndefs; ++n) |
|
433 offsetStack[stackDepth + n] = offset; |
|
434 break; |
|
435 |
|
436 case JSOP_CASE: |
|
437 /* Keep the switch value. */ |
|
438 JS_ASSERT(ndefs == 1); |
|
439 break; |
|
440 |
|
441 case JSOP_DUP: |
|
442 JS_ASSERT(ndefs == 2); |
|
443 if (offsetStack) |
|
444 offsetStack[stackDepth + 1] = offsetStack[stackDepth]; |
|
445 break; |
|
446 |
|
447 case JSOP_DUP2: |
|
448 JS_ASSERT(ndefs == 4); |
|
449 if (offsetStack) { |
|
450 offsetStack[stackDepth + 2] = offsetStack[stackDepth]; |
|
451 offsetStack[stackDepth + 3] = offsetStack[stackDepth + 1]; |
|
452 } |
|
453 break; |
|
454 |
|
455 case JSOP_DUPAT: { |
|
456 JS_ASSERT(ndefs == 1); |
|
457 jsbytecode *pc = script_->offsetToPC(offset); |
|
458 unsigned n = GET_UINT24(pc); |
|
459 JS_ASSERT(n < stackDepth); |
|
460 if (offsetStack) |
|
461 offsetStack[stackDepth] = offsetStack[stackDepth - 1 - n]; |
|
462 break; |
|
463 } |
|
464 |
|
465 case JSOP_SWAP: |
|
466 JS_ASSERT(ndefs == 2); |
|
467 if (offsetStack) { |
|
468 uint32_t tmp = offsetStack[stackDepth + 1]; |
|
469 offsetStack[stackDepth + 1] = offsetStack[stackDepth]; |
|
470 offsetStack[stackDepth] = tmp; |
|
471 } |
|
472 break; |
|
473 } |
|
474 stackDepth += ndefs; |
|
475 return stackDepth; |
|
476 } |
|
477 |
|
478 bool |
|
479 BytecodeParser::addJump(uint32_t offset, uint32_t *currentOffset, |
|
480 uint32_t stackDepth, const uint32_t *offsetStack) |
|
481 { |
|
482 JS_ASSERT(offset < script_->length()); |
|
483 |
|
484 Bytecode *&code = codeArray_[offset]; |
|
485 if (!code) { |
|
486 code = alloc().new_<Bytecode>(); |
|
487 if (!code) |
|
488 return false; |
|
489 if (!code->captureOffsetStack(alloc(), offsetStack, stackDepth)) { |
|
490 reportOOM(); |
|
491 return false; |
|
492 } |
|
493 } else { |
|
494 code->mergeOffsetStack(offsetStack, stackDepth); |
|
495 } |
|
496 |
|
497 if (offset < *currentOffset && !code->parsed) { |
|
498 // Backedge in a while/for loop, whose body has not been parsed due |
|
499 // to a lack of fallthrough at the loop head. Roll back the offset |
|
500 // to analyze the body. |
|
501 *currentOffset = offset; |
|
502 } |
|
503 |
|
504 return true; |
|
505 } |
|
506 |
|
507 bool |
|
508 BytecodeParser::parse() |
|
509 { |
|
510 JS_ASSERT(!codeArray_); |
|
511 |
|
512 uint32_t length = script_->length(); |
|
513 codeArray_ = alloc().newArray<Bytecode*>(length); |
|
514 |
|
515 if (!codeArray_) { |
|
516 reportOOM(); |
|
517 return false; |
|
518 } |
|
519 |
|
520 mozilla::PodZero(codeArray_, length); |
|
521 |
|
522 // Fill in stack depth and definitions at initial bytecode. |
|
523 Bytecode *startcode = alloc().new_<Bytecode>(); |
|
524 if (!startcode) { |
|
525 reportOOM(); |
|
526 return false; |
|
527 } |
|
528 |
|
529 // Fill in stack depth and definitions at initial bytecode. |
|
530 uint32_t *offsetStack = alloc().newArray<uint32_t>(maximumStackDepth()); |
|
531 if (maximumStackDepth() && !offsetStack) { |
|
532 reportOOM(); |
|
533 return false; |
|
534 } |
|
535 |
|
536 startcode->stackDepth = 0; |
|
537 codeArray_[0] = startcode; |
|
538 |
|
539 uint32_t offset, nextOffset = 0; |
|
540 while (nextOffset < length) { |
|
541 offset = nextOffset; |
|
542 |
|
543 Bytecode *code = maybeCode(offset); |
|
544 jsbytecode *pc = script_->offsetToPC(offset); |
|
545 |
|
546 JSOp op = (JSOp)*pc; |
|
547 JS_ASSERT(op < JSOP_LIMIT); |
|
548 |
|
549 // Immediate successor of this bytecode. |
|
550 uint32_t successorOffset = offset + GetBytecodeLength(pc); |
|
551 |
|
552 // Next bytecode to analyze. This is either the successor, or is an |
|
553 // earlier bytecode if this bytecode has a loop backedge. |
|
554 nextOffset = successorOffset; |
|
555 |
|
556 if (!code) { |
|
557 // Haven't found a path by which this bytecode is reachable. |
|
558 continue; |
|
559 } |
|
560 |
|
561 if (code->parsed) { |
|
562 // No need to reparse. |
|
563 continue; |
|
564 } |
|
565 |
|
566 code->parsed = true; |
|
567 |
|
568 uint32_t stackDepth = simulateOp(op, offset, offsetStack, code->stackDepth); |
|
569 |
|
570 switch (op) { |
|
571 case JSOP_TABLESWITCH: { |
|
572 uint32_t defaultOffset = offset + GET_JUMP_OFFSET(pc); |
|
573 jsbytecode *pc2 = pc + JUMP_OFFSET_LEN; |
|
574 int32_t low = GET_JUMP_OFFSET(pc2); |
|
575 pc2 += JUMP_OFFSET_LEN; |
|
576 int32_t high = GET_JUMP_OFFSET(pc2); |
|
577 pc2 += JUMP_OFFSET_LEN; |
|
578 |
|
579 if (!addJump(defaultOffset, &nextOffset, stackDepth, offsetStack)) |
|
580 return false; |
|
581 |
|
582 for (int32_t i = low; i <= high; i++) { |
|
583 uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc2); |
|
584 if (targetOffset != offset) { |
|
585 if (!addJump(targetOffset, &nextOffset, stackDepth, offsetStack)) |
|
586 return false; |
|
587 } |
|
588 pc2 += JUMP_OFFSET_LEN; |
|
589 } |
|
590 break; |
|
591 } |
|
592 |
|
593 case JSOP_TRY: { |
|
594 // Everything between a try and corresponding catch or finally is conditional. |
|
595 // Note that there is no problem with code which is skipped by a thrown |
|
596 // exception but is not caught by a later handler in the same function: |
|
597 // no more code will execute, and it does not matter what is defined. |
|
598 JSTryNote *tn = script_->trynotes()->vector; |
|
599 JSTryNote *tnlimit = tn + script_->trynotes()->length; |
|
600 for (; tn < tnlimit; tn++) { |
|
601 uint32_t startOffset = script_->mainOffset() + tn->start; |
|
602 if (startOffset == offset + 1) { |
|
603 uint32_t catchOffset = startOffset + tn->length; |
|
604 if (tn->kind != JSTRY_ITER && tn->kind != JSTRY_LOOP) { |
|
605 if (!addJump(catchOffset, &nextOffset, stackDepth, offsetStack)) |
|
606 return false; |
|
607 } |
|
608 } |
|
609 } |
|
610 break; |
|
611 } |
|
612 |
|
613 default: |
|
614 break; |
|
615 } |
|
616 |
|
617 // Check basic jump opcodes, which may or may not have a fallthrough. |
|
618 if (IsJumpOpcode(op)) { |
|
619 // Case instructions do not push the lvalue back when branching. |
|
620 uint32_t newStackDepth = stackDepth; |
|
621 if (op == JSOP_CASE) |
|
622 newStackDepth--; |
|
623 |
|
624 uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc); |
|
625 if (!addJump(targetOffset, &nextOffset, newStackDepth, offsetStack)) |
|
626 return false; |
|
627 } |
|
628 |
|
629 // Handle any fallthrough from this opcode. |
|
630 if (BytecodeFallsThrough(op)) { |
|
631 JS_ASSERT(successorOffset < script_->length()); |
|
632 |
|
633 Bytecode *&nextcode = codeArray_[successorOffset]; |
|
634 |
|
635 if (!nextcode) { |
|
636 nextcode = alloc().new_<Bytecode>(); |
|
637 if (!nextcode) { |
|
638 reportOOM(); |
|
639 return false; |
|
640 } |
|
641 if (!nextcode->captureOffsetStack(alloc(), offsetStack, stackDepth)) { |
|
642 reportOOM(); |
|
643 return false; |
|
644 } |
|
645 } else { |
|
646 nextcode->mergeOffsetStack(offsetStack, stackDepth); |
|
647 } |
|
648 } |
|
649 } |
|
650 |
|
651 return true; |
|
652 } |
|
653 |
|
654 #ifdef DEBUG |
|
655 |
|
656 bool |
|
657 js::ReconstructStackDepth(JSContext *cx, JSScript *script, jsbytecode *pc, uint32_t *depth, bool *reachablePC) |
|
658 { |
|
659 BytecodeParser parser(cx, script); |
|
660 if (!parser.parse()) |
|
661 return false; |
|
662 |
|
663 *reachablePC = parser.isReachable(pc); |
|
664 |
|
665 if (*reachablePC) |
|
666 *depth = parser.stackDepthAtPC(pc); |
|
667 |
|
668 return true; |
|
669 } |
|
670 |
|
671 /* |
|
672 * If pc != nullptr, include a prefix indicating whether the PC is at the |
|
673 * current line. If showAll is true, include the source note type and the |
|
674 * entry stack depth. |
|
675 */ |
|
676 JS_FRIEND_API(bool) |
|
677 js_DisassembleAtPC(JSContext *cx, JSScript *scriptArg, bool lines, |
|
678 jsbytecode *pc, bool showAll, Sprinter *sp) |
|
679 { |
|
680 RootedScript script(cx, scriptArg); |
|
681 BytecodeParser parser(cx, script); |
|
682 |
|
683 jsbytecode *next, *end; |
|
684 unsigned len; |
|
685 |
|
686 if (showAll && !parser.parse()) |
|
687 return false; |
|
688 |
|
689 if (showAll) |
|
690 Sprint(sp, "%s:%u\n", script->filename(), script->lineno()); |
|
691 |
|
692 if (pc != nullptr) |
|
693 sp->put(" "); |
|
694 if (showAll) |
|
695 sp->put("sn stack "); |
|
696 sp->put("loc "); |
|
697 if (lines) |
|
698 sp->put("line"); |
|
699 sp->put(" op\n"); |
|
700 |
|
701 if (pc != nullptr) |
|
702 sp->put(" "); |
|
703 if (showAll) |
|
704 sp->put("-- ----- "); |
|
705 sp->put("----- "); |
|
706 if (lines) |
|
707 sp->put("----"); |
|
708 sp->put(" --\n"); |
|
709 |
|
710 next = script->code(); |
|
711 end = script->codeEnd(); |
|
712 while (next < end) { |
|
713 if (next == script->main()) |
|
714 sp->put("main:\n"); |
|
715 if (pc != nullptr) { |
|
716 if (pc == next) |
|
717 sp->put("--> "); |
|
718 else |
|
719 sp->put(" "); |
|
720 } |
|
721 if (showAll) { |
|
722 jssrcnote *sn = js_GetSrcNote(cx, script, next); |
|
723 if (sn) { |
|
724 JS_ASSERT(!SN_IS_TERMINATOR(sn)); |
|
725 jssrcnote *next = SN_NEXT(sn); |
|
726 while (!SN_IS_TERMINATOR(next) && SN_DELTA(next) == 0) { |
|
727 Sprint(sp, "%02u\n ", SN_TYPE(sn)); |
|
728 sn = next; |
|
729 next = SN_NEXT(sn); |
|
730 } |
|
731 Sprint(sp, "%02u ", SN_TYPE(sn)); |
|
732 } |
|
733 else |
|
734 sp->put(" "); |
|
735 if (parser.isReachable(next)) |
|
736 Sprint(sp, "%05u ", parser.stackDepthAtPC(next)); |
|
737 else |
|
738 Sprint(sp, " "); |
|
739 } |
|
740 len = js_Disassemble1(cx, script, next, script->pcToOffset(next), lines, sp); |
|
741 if (!len) |
|
742 return false; |
|
743 next += len; |
|
744 } |
|
745 return true; |
|
746 } |
|
747 |
|
748 bool |
|
749 js_Disassemble(JSContext *cx, HandleScript script, bool lines, Sprinter *sp) |
|
750 { |
|
751 return js_DisassembleAtPC(cx, script, lines, nullptr, false, sp); |
|
752 } |
|
753 |
|
754 JS_FRIEND_API(bool) |
|
755 js_DumpPC(JSContext *cx) |
|
756 { |
|
757 js::gc::AutoSuppressGC suppressGC(cx); |
|
758 Sprinter sprinter(cx); |
|
759 if (!sprinter.init()) |
|
760 return false; |
|
761 ScriptFrameIter iter(cx); |
|
762 RootedScript script(cx, iter.script()); |
|
763 bool ok = js_DisassembleAtPC(cx, script, true, iter.pc(), false, &sprinter); |
|
764 fprintf(stdout, "%s", sprinter.string()); |
|
765 return ok; |
|
766 } |
|
767 |
|
768 JS_FRIEND_API(bool) |
|
769 js_DumpScript(JSContext *cx, JSScript *scriptArg) |
|
770 { |
|
771 js::gc::AutoSuppressGC suppressGC(cx); |
|
772 Sprinter sprinter(cx); |
|
773 if (!sprinter.init()) |
|
774 return false; |
|
775 RootedScript script(cx, scriptArg); |
|
776 bool ok = js_Disassemble(cx, script, true, &sprinter); |
|
777 fprintf(stdout, "%s", sprinter.string()); |
|
778 return ok; |
|
779 } |
|
780 |
|
781 /* |
|
782 * Useful to debug ReconstructPCStack. |
|
783 */ |
|
784 JS_FRIEND_API(bool) |
|
785 js_DumpScriptDepth(JSContext *cx, JSScript *scriptArg, jsbytecode *pc) |
|
786 { |
|
787 js::gc::AutoSuppressGC suppressGC(cx); |
|
788 Sprinter sprinter(cx); |
|
789 if (!sprinter.init()) |
|
790 return false; |
|
791 RootedScript script(cx, scriptArg); |
|
792 bool ok = js_DisassembleAtPC(cx, script, true, pc, true, &sprinter); |
|
793 fprintf(stdout, "%s", sprinter.string()); |
|
794 return ok; |
|
795 } |
|
796 |
|
797 static char * |
|
798 QuoteString(Sprinter *sp, JSString *str, uint32_t quote); |
|
799 |
|
800 static bool |
|
801 ToDisassemblySource(JSContext *cx, HandleValue v, JSAutoByteString *bytes) |
|
802 { |
|
803 if (JSVAL_IS_STRING(v)) { |
|
804 Sprinter sprinter(cx); |
|
805 if (!sprinter.init()) |
|
806 return false; |
|
807 char *nbytes = QuoteString(&sprinter, JSVAL_TO_STRING(v), '"'); |
|
808 if (!nbytes) |
|
809 return false; |
|
810 nbytes = JS_sprintf_append(nullptr, "%s", nbytes); |
|
811 if (!nbytes) |
|
812 return false; |
|
813 bytes->initBytes(nbytes); |
|
814 return true; |
|
815 } |
|
816 |
|
817 if (cx->runtime()->isHeapBusy() || cx->runtime()->noGCOrAllocationCheck) { |
|
818 char *source = JS_sprintf_append(nullptr, "<value>"); |
|
819 if (!source) |
|
820 return false; |
|
821 bytes->initBytes(source); |
|
822 return true; |
|
823 } |
|
824 |
|
825 if (!JSVAL_IS_PRIMITIVE(v)) { |
|
826 JSObject *obj = JSVAL_TO_OBJECT(v); |
|
827 if (obj->is<StaticBlockObject>()) { |
|
828 Rooted<StaticBlockObject*> block(cx, &obj->as<StaticBlockObject>()); |
|
829 char *source = JS_sprintf_append(nullptr, "depth %d {", block->localOffset()); |
|
830 if (!source) |
|
831 return false; |
|
832 |
|
833 Shape::Range<CanGC> r(cx, block->lastProperty()); |
|
834 |
|
835 while (!r.empty()) { |
|
836 Rooted<Shape*> shape(cx, &r.front()); |
|
837 JSAtom *atom = JSID_IS_INT(shape->propid()) |
|
838 ? cx->names().empty |
|
839 : JSID_TO_ATOM(shape->propid()); |
|
840 |
|
841 JSAutoByteString bytes; |
|
842 if (!AtomToPrintableString(cx, atom, &bytes)) |
|
843 return false; |
|
844 |
|
845 r.popFront(); |
|
846 source = JS_sprintf_append(source, "%s: %d%s", |
|
847 bytes.ptr(), |
|
848 block->shapeToIndex(*shape), |
|
849 !r.empty() ? ", " : ""); |
|
850 if (!source) |
|
851 return false; |
|
852 } |
|
853 |
|
854 source = JS_sprintf_append(source, "}"); |
|
855 if (!source) |
|
856 return false; |
|
857 bytes->initBytes(source); |
|
858 return true; |
|
859 } |
|
860 |
|
861 if (obj->is<JSFunction>()) { |
|
862 RootedFunction fun(cx, &obj->as<JSFunction>()); |
|
863 JSString *str = JS_DecompileFunction(cx, fun, JS_DONT_PRETTY_PRINT); |
|
864 if (!str) |
|
865 return false; |
|
866 return bytes->encodeLatin1(cx, str); |
|
867 } |
|
868 |
|
869 if (obj->is<RegExpObject>()) { |
|
870 JSString *source = obj->as<RegExpObject>().toString(cx); |
|
871 if (!source) |
|
872 return false; |
|
873 JS::Anchor<JSString *> anchor(source); |
|
874 return bytes->encodeLatin1(cx, source); |
|
875 } |
|
876 } |
|
877 |
|
878 return !!js_ValueToPrintable(cx, v, bytes, true); |
|
879 } |
|
880 |
|
881 unsigned |
|
882 js_Disassemble1(JSContext *cx, HandleScript script, jsbytecode *pc, |
|
883 unsigned loc, bool lines, Sprinter *sp) |
|
884 { |
|
885 JSOp op = (JSOp)*pc; |
|
886 if (op >= JSOP_LIMIT) { |
|
887 char numBuf1[12], numBuf2[12]; |
|
888 JS_snprintf(numBuf1, sizeof numBuf1, "%d", op); |
|
889 JS_snprintf(numBuf2, sizeof numBuf2, "%d", JSOP_LIMIT); |
|
890 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, |
|
891 JSMSG_BYTECODE_TOO_BIG, numBuf1, numBuf2); |
|
892 return 0; |
|
893 } |
|
894 const JSCodeSpec *cs = &js_CodeSpec[op]; |
|
895 ptrdiff_t len = (ptrdiff_t) cs->length; |
|
896 Sprint(sp, "%05u:", loc); |
|
897 if (lines) |
|
898 Sprint(sp, "%4u", JS_PCToLineNumber(cx, script, pc)); |
|
899 Sprint(sp, " %s", js_CodeName[op]); |
|
900 |
|
901 switch (JOF_TYPE(cs->format)) { |
|
902 case JOF_BYTE: |
|
903 // Scan the trynotes to find the associated catch block |
|
904 // and make the try opcode look like a jump instruction |
|
905 // with an offset. This simplifies code coverage analysis |
|
906 // based on this disassembled output. |
|
907 if (op == JSOP_TRY) { |
|
908 TryNoteArray *trynotes = script->trynotes(); |
|
909 uint32_t i; |
|
910 for(i = 0; i < trynotes->length; i++) { |
|
911 JSTryNote note = trynotes->vector[i]; |
|
912 if (note.kind == JSTRY_CATCH && note.start == loc + 1) { |
|
913 Sprint(sp, " %u (%+d)", |
|
914 (unsigned int) (loc+note.length+1), |
|
915 (int) (note.length+1)); |
|
916 break; |
|
917 } |
|
918 } |
|
919 } |
|
920 break; |
|
921 |
|
922 case JOF_JUMP: { |
|
923 ptrdiff_t off = GET_JUMP_OFFSET(pc); |
|
924 Sprint(sp, " %u (%+d)", loc + (int) off, (int) off); |
|
925 break; |
|
926 } |
|
927 |
|
928 case JOF_SCOPECOORD: { |
|
929 RootedValue v(cx, |
|
930 StringValue(ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc))); |
|
931 JSAutoByteString bytes; |
|
932 if (!ToDisassemblySource(cx, v, &bytes)) |
|
933 return 0; |
|
934 ScopeCoordinate sc(pc); |
|
935 Sprint(sp, " %s (hops = %u, slot = %u)", bytes.ptr(), sc.hops(), sc.slot()); |
|
936 break; |
|
937 } |
|
938 |
|
939 case JOF_ATOM: { |
|
940 RootedValue v(cx, StringValue(script->getAtom(GET_UINT32_INDEX(pc)))); |
|
941 JSAutoByteString bytes; |
|
942 if (!ToDisassemblySource(cx, v, &bytes)) |
|
943 return 0; |
|
944 Sprint(sp, " %s", bytes.ptr()); |
|
945 break; |
|
946 } |
|
947 |
|
948 case JOF_DOUBLE: { |
|
949 RootedValue v(cx, script->getConst(GET_UINT32_INDEX(pc))); |
|
950 JSAutoByteString bytes; |
|
951 if (!ToDisassemblySource(cx, v, &bytes)) |
|
952 return 0; |
|
953 Sprint(sp, " %s", bytes.ptr()); |
|
954 break; |
|
955 } |
|
956 |
|
957 case JOF_OBJECT: { |
|
958 /* Don't call obj.toSource if analysis/inference is active. */ |
|
959 if (script->compartment()->activeAnalysis) { |
|
960 Sprint(sp, " object"); |
|
961 break; |
|
962 } |
|
963 |
|
964 JSObject *obj = script->getObject(GET_UINT32_INDEX(pc)); |
|
965 { |
|
966 JSAutoByteString bytes; |
|
967 RootedValue v(cx, ObjectValue(*obj)); |
|
968 if (!ToDisassemblySource(cx, v, &bytes)) |
|
969 return 0; |
|
970 Sprint(sp, " %s", bytes.ptr()); |
|
971 } |
|
972 break; |
|
973 } |
|
974 |
|
975 case JOF_REGEXP: { |
|
976 JSObject *obj = script->getRegExp(GET_UINT32_INDEX(pc)); |
|
977 JSAutoByteString bytes; |
|
978 RootedValue v(cx, ObjectValue(*obj)); |
|
979 if (!ToDisassemblySource(cx, v, &bytes)) |
|
980 return 0; |
|
981 Sprint(sp, " %s", bytes.ptr()); |
|
982 break; |
|
983 } |
|
984 |
|
985 case JOF_TABLESWITCH: |
|
986 { |
|
987 int32_t i, low, high; |
|
988 |
|
989 ptrdiff_t off = GET_JUMP_OFFSET(pc); |
|
990 jsbytecode *pc2 = pc + JUMP_OFFSET_LEN; |
|
991 low = GET_JUMP_OFFSET(pc2); |
|
992 pc2 += JUMP_OFFSET_LEN; |
|
993 high = GET_JUMP_OFFSET(pc2); |
|
994 pc2 += JUMP_OFFSET_LEN; |
|
995 Sprint(sp, " defaultOffset %d low %d high %d", int(off), low, high); |
|
996 for (i = low; i <= high; i++) { |
|
997 off = GET_JUMP_OFFSET(pc2); |
|
998 Sprint(sp, "\n\t%d: %d", i, int(off)); |
|
999 pc2 += JUMP_OFFSET_LEN; |
|
1000 } |
|
1001 len = 1 + pc2 - pc; |
|
1002 break; |
|
1003 } |
|
1004 |
|
1005 case JOF_QARG: |
|
1006 Sprint(sp, " %u", GET_ARGNO(pc)); |
|
1007 break; |
|
1008 |
|
1009 case JOF_LOCAL: |
|
1010 Sprint(sp, " %u", GET_LOCALNO(pc)); |
|
1011 break; |
|
1012 |
|
1013 { |
|
1014 int i; |
|
1015 |
|
1016 case JOF_UINT16: |
|
1017 i = (int)GET_UINT16(pc); |
|
1018 goto print_int; |
|
1019 |
|
1020 case JOF_UINT24: |
|
1021 JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY || op == JSOP_INITELEM_ARRAY || |
|
1022 op == JSOP_DUPAT); |
|
1023 i = (int)GET_UINT24(pc); |
|
1024 goto print_int; |
|
1025 |
|
1026 case JOF_UINT8: |
|
1027 i = GET_UINT8(pc); |
|
1028 goto print_int; |
|
1029 |
|
1030 case JOF_INT8: |
|
1031 i = GET_INT8(pc); |
|
1032 goto print_int; |
|
1033 |
|
1034 case JOF_INT32: |
|
1035 JS_ASSERT(op == JSOP_INT32); |
|
1036 i = GET_INT32(pc); |
|
1037 print_int: |
|
1038 Sprint(sp, " %d", i); |
|
1039 break; |
|
1040 } |
|
1041 |
|
1042 default: { |
|
1043 char numBuf[12]; |
|
1044 JS_snprintf(numBuf, sizeof numBuf, "%lx", (unsigned long) cs->format); |
|
1045 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, |
|
1046 JSMSG_UNKNOWN_FORMAT, numBuf); |
|
1047 return 0; |
|
1048 } |
|
1049 } |
|
1050 sp->put("\n"); |
|
1051 return len; |
|
1052 } |
|
1053 |
|
1054 #endif /* DEBUG */ |
|
1055 |
|
1056 /************************************************************************/ |
|
1057 |
|
1058 const size_t Sprinter::DefaultSize = 64; |
|
1059 |
|
1060 bool |
|
1061 Sprinter::realloc_(size_t newSize) |
|
1062 { |
|
1063 JS_ASSERT(newSize > (size_t) offset); |
|
1064 char *newBuf = (char *) js_realloc(base, newSize); |
|
1065 if (!newBuf) { |
|
1066 reportOutOfMemory(); |
|
1067 return false; |
|
1068 } |
|
1069 base = newBuf; |
|
1070 size = newSize; |
|
1071 base[size - 1] = 0; |
|
1072 return true; |
|
1073 } |
|
1074 |
|
1075 Sprinter::Sprinter(ExclusiveContext *cx) |
|
1076 : context(cx), |
|
1077 #ifdef DEBUG |
|
1078 initialized(false), |
|
1079 #endif |
|
1080 base(nullptr), size(0), offset(0), reportedOOM(false) |
|
1081 { } |
|
1082 |
|
1083 Sprinter::~Sprinter() |
|
1084 { |
|
1085 #ifdef DEBUG |
|
1086 if (initialized) |
|
1087 checkInvariants(); |
|
1088 #endif |
|
1089 js_free(base); |
|
1090 } |
|
1091 |
|
1092 bool |
|
1093 Sprinter::init() |
|
1094 { |
|
1095 JS_ASSERT(!initialized); |
|
1096 base = (char *) js_malloc(DefaultSize); |
|
1097 if (!base) { |
|
1098 reportOutOfMemory(); |
|
1099 return false; |
|
1100 } |
|
1101 #ifdef DEBUG |
|
1102 initialized = true; |
|
1103 #endif |
|
1104 *base = 0; |
|
1105 size = DefaultSize; |
|
1106 base[size - 1] = 0; |
|
1107 return true; |
|
1108 } |
|
1109 |
|
1110 void |
|
1111 Sprinter::checkInvariants() const |
|
1112 { |
|
1113 JS_ASSERT(initialized); |
|
1114 JS_ASSERT((size_t) offset < size); |
|
1115 JS_ASSERT(base[size - 1] == 0); |
|
1116 } |
|
1117 |
|
1118 const char * |
|
1119 Sprinter::string() const |
|
1120 { |
|
1121 return base; |
|
1122 } |
|
1123 |
|
1124 const char * |
|
1125 Sprinter::stringEnd() const |
|
1126 { |
|
1127 return base + offset; |
|
1128 } |
|
1129 |
|
1130 char * |
|
1131 Sprinter::stringAt(ptrdiff_t off) const |
|
1132 { |
|
1133 JS_ASSERT(off >= 0 && (size_t) off < size); |
|
1134 return base + off; |
|
1135 } |
|
1136 |
|
1137 char & |
|
1138 Sprinter::operator[](size_t off) |
|
1139 { |
|
1140 JS_ASSERT(off < size); |
|
1141 return *(base + off); |
|
1142 } |
|
1143 |
|
1144 char * |
|
1145 Sprinter::reserve(size_t len) |
|
1146 { |
|
1147 InvariantChecker ic(this); |
|
1148 |
|
1149 while (len + 1 > size - offset) { /* Include trailing \0 */ |
|
1150 if (!realloc_(size * 2)) |
|
1151 return nullptr; |
|
1152 } |
|
1153 |
|
1154 char *sb = base + offset; |
|
1155 offset += len; |
|
1156 return sb; |
|
1157 } |
|
1158 |
|
1159 ptrdiff_t |
|
1160 Sprinter::put(const char *s, size_t len) |
|
1161 { |
|
1162 InvariantChecker ic(this); |
|
1163 |
|
1164 const char *oldBase = base; |
|
1165 const char *oldEnd = base + size; |
|
1166 |
|
1167 ptrdiff_t oldOffset = offset; |
|
1168 char *bp = reserve(len); |
|
1169 if (!bp) |
|
1170 return -1; |
|
1171 |
|
1172 /* s is within the buffer already */ |
|
1173 if (s >= oldBase && s < oldEnd) { |
|
1174 /* buffer was realloc'ed */ |
|
1175 if (base != oldBase) |
|
1176 s = stringAt(s - oldBase); /* this is where it lives now */ |
|
1177 memmove(bp, s, len); |
|
1178 } else { |
|
1179 js_memcpy(bp, s, len); |
|
1180 } |
|
1181 |
|
1182 bp[len] = 0; |
|
1183 return oldOffset; |
|
1184 } |
|
1185 |
|
1186 ptrdiff_t |
|
1187 Sprinter::put(const char *s) |
|
1188 { |
|
1189 return put(s, strlen(s)); |
|
1190 } |
|
1191 |
|
1192 ptrdiff_t |
|
1193 Sprinter::putString(JSString *s) |
|
1194 { |
|
1195 InvariantChecker ic(this); |
|
1196 |
|
1197 size_t length = s->length(); |
|
1198 const jschar *chars = s->getChars(context); |
|
1199 if (!chars) |
|
1200 return -1; |
|
1201 |
|
1202 size_t size = length; |
|
1203 if (size == (size_t) -1) |
|
1204 return -1; |
|
1205 |
|
1206 ptrdiff_t oldOffset = offset; |
|
1207 char *buffer = reserve(size); |
|
1208 if (!buffer) |
|
1209 return -1; |
|
1210 DeflateStringToBuffer(nullptr, chars, length, buffer, &size); |
|
1211 buffer[size] = 0; |
|
1212 |
|
1213 return oldOffset; |
|
1214 } |
|
1215 |
|
1216 int |
|
1217 Sprinter::printf(const char *fmt, ...) |
|
1218 { |
|
1219 InvariantChecker ic(this); |
|
1220 |
|
1221 do { |
|
1222 va_list va; |
|
1223 va_start(va, fmt); |
|
1224 int i = vsnprintf(base + offset, size - offset, fmt, va); |
|
1225 va_end(va); |
|
1226 |
|
1227 if (i > -1 && (size_t) i < size - offset) { |
|
1228 offset += i; |
|
1229 return i; |
|
1230 } |
|
1231 } while (realloc_(size * 2)); |
|
1232 |
|
1233 return -1; |
|
1234 } |
|
1235 |
|
1236 ptrdiff_t |
|
1237 Sprinter::getOffset() const |
|
1238 { |
|
1239 return offset; |
|
1240 } |
|
1241 |
|
1242 void |
|
1243 Sprinter::reportOutOfMemory() |
|
1244 { |
|
1245 if (reportedOOM) |
|
1246 return; |
|
1247 if (context) |
|
1248 js_ReportOutOfMemory(context); |
|
1249 reportedOOM = true; |
|
1250 } |
|
1251 |
|
1252 bool |
|
1253 Sprinter::hadOutOfMemory() const |
|
1254 { |
|
1255 return reportedOOM; |
|
1256 } |
|
1257 |
|
1258 ptrdiff_t |
|
1259 js::Sprint(Sprinter *sp, const char *format, ...) |
|
1260 { |
|
1261 va_list ap; |
|
1262 char *bp; |
|
1263 ptrdiff_t offset; |
|
1264 |
|
1265 va_start(ap, format); |
|
1266 bp = JS_vsmprintf(format, ap); /* XXX vsaprintf */ |
|
1267 va_end(ap); |
|
1268 if (!bp) { |
|
1269 sp->reportOutOfMemory(); |
|
1270 return -1; |
|
1271 } |
|
1272 offset = sp->put(bp); |
|
1273 js_free(bp); |
|
1274 return offset; |
|
1275 } |
|
1276 |
|
1277 const char js_EscapeMap[] = { |
|
1278 '\b', 'b', |
|
1279 '\f', 'f', |
|
1280 '\n', 'n', |
|
1281 '\r', 'r', |
|
1282 '\t', 't', |
|
1283 '\v', 'v', |
|
1284 '"', '"', |
|
1285 '\'', '\'', |
|
1286 '\\', '\\', |
|
1287 '\0' |
|
1288 }; |
|
1289 |
|
1290 #define DONT_ESCAPE 0x10000 |
|
1291 |
|
1292 static char * |
|
1293 QuoteString(Sprinter *sp, JSString *str, uint32_t quote) |
|
1294 { |
|
1295 /* Sample off first for later return value pointer computation. */ |
|
1296 bool dontEscape = (quote & DONT_ESCAPE) != 0; |
|
1297 jschar qc = (jschar) quote; |
|
1298 ptrdiff_t offset = sp->getOffset(); |
|
1299 if (qc && Sprint(sp, "%c", (char)qc) < 0) |
|
1300 return nullptr; |
|
1301 |
|
1302 const jschar *s = str->getChars(sp->context); |
|
1303 if (!s) |
|
1304 return nullptr; |
|
1305 const jschar *z = s + str->length(); |
|
1306 |
|
1307 /* Loop control variables: z points at end of string sentinel. */ |
|
1308 for (const jschar *t = s; t < z; s = ++t) { |
|
1309 /* Move t forward from s past un-quote-worthy characters. */ |
|
1310 jschar c = *t; |
|
1311 while (c < 127 && isprint(c) && c != qc && c != '\\' && c != '\t') { |
|
1312 c = *++t; |
|
1313 if (t == z) |
|
1314 break; |
|
1315 } |
|
1316 |
|
1317 { |
|
1318 ptrdiff_t len = t - s; |
|
1319 ptrdiff_t base = sp->getOffset(); |
|
1320 char *bp = sp->reserve(len); |
|
1321 if (!bp) |
|
1322 return nullptr; |
|
1323 |
|
1324 for (ptrdiff_t i = 0; i < len; ++i) |
|
1325 (*sp)[base + i] = (char) *s++; |
|
1326 (*sp)[base + len] = 0; |
|
1327 } |
|
1328 |
|
1329 if (t == z) |
|
1330 break; |
|
1331 |
|
1332 /* Use js_EscapeMap, \u, or \x only if necessary. */ |
|
1333 bool ok; |
|
1334 const char *e; |
|
1335 if (!(c >> 8) && c != 0 && (e = strchr(js_EscapeMap, (int)c)) != nullptr) { |
|
1336 ok = dontEscape |
|
1337 ? Sprint(sp, "%c", (char)c) >= 0 |
|
1338 : Sprint(sp, "\\%c", e[1]) >= 0; |
|
1339 } else { |
|
1340 /* |
|
1341 * Use \x only if the high byte is 0 and we're in a quoted string, |
|
1342 * because ECMA-262 allows only \u, not \x, in Unicode identifiers |
|
1343 * (see bug 621814). |
|
1344 */ |
|
1345 ok = Sprint(sp, (qc && !(c >> 8)) ? "\\x%02X" : "\\u%04X", c) >= 0; |
|
1346 } |
|
1347 if (!ok) |
|
1348 return nullptr; |
|
1349 } |
|
1350 |
|
1351 /* Sprint the closing quote and return the quoted string. */ |
|
1352 if (qc && Sprint(sp, "%c", (char)qc) < 0) |
|
1353 return nullptr; |
|
1354 |
|
1355 /* |
|
1356 * If we haven't Sprint'd anything yet, Sprint an empty string so that |
|
1357 * the return below gives a valid result. |
|
1358 */ |
|
1359 if (offset == sp->getOffset() && Sprint(sp, "") < 0) |
|
1360 return nullptr; |
|
1361 |
|
1362 return sp->stringAt(offset); |
|
1363 } |
|
1364 |
|
1365 JSString * |
|
1366 js_QuoteString(ExclusiveContext *cx, JSString *str, jschar quote) |
|
1367 { |
|
1368 Sprinter sprinter(cx); |
|
1369 if (!sprinter.init()) |
|
1370 return nullptr; |
|
1371 char *bytes = QuoteString(&sprinter, str, quote); |
|
1372 if (!bytes) |
|
1373 return nullptr; |
|
1374 return js_NewStringCopyZ<CanGC>(cx, bytes); |
|
1375 } |
|
1376 |
|
1377 /************************************************************************/ |
|
1378 |
|
1379 namespace { |
|
1380 /* |
|
1381 * The expression decompiler is invoked by error handling code to produce a |
|
1382 * string representation of the erroring expression. As it's only a debugging |
|
1383 * tool, it only supports basic expressions. For anything complicated, it simply |
|
1384 * puts "(intermediate value)" into the error result. |
|
1385 * |
|
1386 * Here's the basic algorithm: |
|
1387 * |
|
1388 * 1. Find the stack location of the value whose expression we wish to |
|
1389 * decompile. The error handler can explicitly pass this as an |
|
1390 * argument. Otherwise, we search backwards down the stack for the offending |
|
1391 * value. |
|
1392 * |
|
1393 * 2. Instantiate and run a BytecodeParser for the current frame. This creates a |
|
1394 * stack of pcs parallel to the interpreter stack; given an interpreter stack |
|
1395 * location, the corresponding pc stack location contains the opcode that pushed |
|
1396 * the value in the interpreter. Now, with the result of step 1, we have the |
|
1397 * opcode responsible for pushing the value we want to decompile. |
|
1398 * |
|
1399 * 3. Pass the opcode to decompilePC. decompilePC is the main decompiler |
|
1400 * routine, responsible for a string representation of the expression that |
|
1401 * generated a certain stack location. decompilePC looks at one opcode and |
|
1402 * returns the JS source equivalent of that opcode. |
|
1403 * |
|
1404 * 4. Expressions can, of course, contain subexpressions. For example, the |
|
1405 * literals "4" and "5" are subexpressions of the addition operator in "4 + |
|
1406 * 5". If we need to decompile a subexpression, we call decompilePC (step 2) |
|
1407 * recursively on the operands' pcs. The result is a depth-first traversal of |
|
1408 * the expression tree. |
|
1409 * |
|
1410 */ |
|
1411 struct ExpressionDecompiler |
|
1412 { |
|
1413 JSContext *cx; |
|
1414 InterpreterFrame *fp; |
|
1415 RootedScript script; |
|
1416 RootedFunction fun; |
|
1417 BindingVector *localNames; |
|
1418 BytecodeParser parser; |
|
1419 Sprinter sprinter; |
|
1420 |
|
1421 ExpressionDecompiler(JSContext *cx, JSScript *script, JSFunction *fun) |
|
1422 : cx(cx), |
|
1423 script(cx, script), |
|
1424 fun(cx, fun), |
|
1425 localNames(nullptr), |
|
1426 parser(cx, script), |
|
1427 sprinter(cx) |
|
1428 {} |
|
1429 ~ExpressionDecompiler(); |
|
1430 bool init(); |
|
1431 bool decompilePCForStackOperand(jsbytecode *pc, int i); |
|
1432 bool decompilePC(jsbytecode *pc); |
|
1433 JSAtom *getLocal(uint32_t local, jsbytecode *pc); |
|
1434 JSAtom *getArg(unsigned slot); |
|
1435 JSAtom *loadAtom(jsbytecode *pc); |
|
1436 bool quote(JSString *s, uint32_t quote); |
|
1437 bool write(const char *s); |
|
1438 bool write(JSString *str); |
|
1439 bool getOutput(char **out); |
|
1440 }; |
|
1441 |
|
1442 bool |
|
1443 ExpressionDecompiler::decompilePCForStackOperand(jsbytecode *pc, int i) |
|
1444 { |
|
1445 pc = parser.pcForStackOperand(pc, i); |
|
1446 if (!pc) |
|
1447 return write("(intermediate value)"); |
|
1448 return decompilePC(pc); |
|
1449 } |
|
1450 |
|
1451 bool |
|
1452 ExpressionDecompiler::decompilePC(jsbytecode *pc) |
|
1453 { |
|
1454 JS_ASSERT(script->containsPC(pc)); |
|
1455 |
|
1456 JSOp op = (JSOp)*pc; |
|
1457 |
|
1458 if (const char *token = CodeToken[op]) { |
|
1459 // Handle simple cases of binary and unary operators. |
|
1460 switch (js_CodeSpec[op].nuses) { |
|
1461 case 2: { |
|
1462 jssrcnote *sn = js_GetSrcNote(cx, script, pc); |
|
1463 if (!sn || SN_TYPE(sn) != SRC_ASSIGNOP) |
|
1464 return write("(") && |
|
1465 decompilePCForStackOperand(pc, -2) && |
|
1466 write(" ") && |
|
1467 write(token) && |
|
1468 write(" ") && |
|
1469 decompilePCForStackOperand(pc, -1) && |
|
1470 write(")"); |
|
1471 break; |
|
1472 } |
|
1473 case 1: |
|
1474 return write(token) && |
|
1475 write("(") && |
|
1476 decompilePCForStackOperand(pc, -1) && |
|
1477 write(")"); |
|
1478 default: |
|
1479 break; |
|
1480 } |
|
1481 } |
|
1482 |
|
1483 switch (op) { |
|
1484 case JSOP_GETGNAME: |
|
1485 case JSOP_NAME: |
|
1486 case JSOP_GETINTRINSIC: |
|
1487 return write(loadAtom(pc)); |
|
1488 case JSOP_GETARG: { |
|
1489 unsigned slot = GET_ARGNO(pc); |
|
1490 JSAtom *atom = getArg(slot); |
|
1491 return write(atom); |
|
1492 } |
|
1493 case JSOP_GETLOCAL: { |
|
1494 uint32_t i = GET_LOCALNO(pc); |
|
1495 if (JSAtom *atom = getLocal(i, pc)) |
|
1496 return write(atom); |
|
1497 return write("(intermediate value)"); |
|
1498 } |
|
1499 case JSOP_GETALIASEDVAR: { |
|
1500 JSAtom *atom = ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc); |
|
1501 JS_ASSERT(atom); |
|
1502 return write(atom); |
|
1503 } |
|
1504 case JSOP_LENGTH: |
|
1505 case JSOP_GETPROP: |
|
1506 case JSOP_CALLPROP: { |
|
1507 RootedAtom prop(cx, (op == JSOP_LENGTH) ? cx->names().length : loadAtom(pc)); |
|
1508 if (!decompilePCForStackOperand(pc, -1)) |
|
1509 return false; |
|
1510 if (IsIdentifier(prop)) { |
|
1511 return write(".") && |
|
1512 quote(prop, '\0'); |
|
1513 } |
|
1514 return write("[") && |
|
1515 quote(prop, '\'') && |
|
1516 write("]"); |
|
1517 } |
|
1518 case JSOP_GETELEM: |
|
1519 case JSOP_CALLELEM: |
|
1520 return decompilePCForStackOperand(pc, -2) && |
|
1521 write("[") && |
|
1522 decompilePCForStackOperand(pc, -1) && |
|
1523 write("]"); |
|
1524 case JSOP_NULL: |
|
1525 return write(js_null_str); |
|
1526 case JSOP_TRUE: |
|
1527 return write(js_true_str); |
|
1528 case JSOP_FALSE: |
|
1529 return write(js_false_str); |
|
1530 case JSOP_ZERO: |
|
1531 case JSOP_ONE: |
|
1532 case JSOP_INT8: |
|
1533 case JSOP_UINT16: |
|
1534 case JSOP_UINT24: |
|
1535 case JSOP_INT32: |
|
1536 return sprinter.printf("%d", GetBytecodeInteger(pc)) >= 0; |
|
1537 case JSOP_STRING: |
|
1538 return quote(loadAtom(pc), '"'); |
|
1539 case JSOP_UNDEFINED: |
|
1540 return write(js_undefined_str); |
|
1541 case JSOP_THIS: |
|
1542 // |this| could convert to a very long object initialiser, so cite it by |
|
1543 // its keyword name. |
|
1544 return write(js_this_str); |
|
1545 case JSOP_CALL: |
|
1546 case JSOP_FUNCALL: |
|
1547 return decompilePCForStackOperand(pc, -int32_t(GET_ARGC(pc) + 2)) && |
|
1548 write("(...)"); |
|
1549 case JSOP_SPREADCALL: |
|
1550 return decompilePCForStackOperand(pc, -int32_t(3)) && |
|
1551 write("(...)"); |
|
1552 case JSOP_NEWARRAY: |
|
1553 return write("[]"); |
|
1554 case JSOP_REGEXP: |
|
1555 case JSOP_OBJECT: { |
|
1556 JSObject *obj = (op == JSOP_REGEXP) |
|
1557 ? script->getRegExp(GET_UINT32_INDEX(pc)) |
|
1558 : script->getObject(GET_UINT32_INDEX(pc)); |
|
1559 RootedValue objv(cx, ObjectValue(*obj)); |
|
1560 JSString *str = ValueToSource(cx, objv); |
|
1561 if (!str) |
|
1562 return false; |
|
1563 return write(str); |
|
1564 } |
|
1565 default: |
|
1566 break; |
|
1567 } |
|
1568 return write("(intermediate value)"); |
|
1569 } |
|
1570 |
|
1571 ExpressionDecompiler::~ExpressionDecompiler() |
|
1572 { |
|
1573 js_delete<BindingVector>(localNames); |
|
1574 } |
|
1575 |
|
1576 bool |
|
1577 ExpressionDecompiler::init() |
|
1578 { |
|
1579 assertSameCompartment(cx, script); |
|
1580 |
|
1581 if (!sprinter.init()) |
|
1582 return false; |
|
1583 |
|
1584 localNames = cx->new_<BindingVector>(cx); |
|
1585 if (!localNames) |
|
1586 return false; |
|
1587 RootedScript script_(cx, script); |
|
1588 if (!FillBindingVector(script_, localNames)) |
|
1589 return false; |
|
1590 |
|
1591 if (!parser.parse()) |
|
1592 return false; |
|
1593 |
|
1594 return true; |
|
1595 } |
|
1596 |
|
1597 bool |
|
1598 ExpressionDecompiler::write(const char *s) |
|
1599 { |
|
1600 return sprinter.put(s) >= 0; |
|
1601 } |
|
1602 |
|
1603 bool |
|
1604 ExpressionDecompiler::write(JSString *str) |
|
1605 { |
|
1606 return sprinter.putString(str) >= 0; |
|
1607 } |
|
1608 |
|
1609 bool |
|
1610 ExpressionDecompiler::quote(JSString *s, uint32_t quote) |
|
1611 { |
|
1612 return QuoteString(&sprinter, s, quote) >= 0; |
|
1613 } |
|
1614 |
|
1615 JSAtom * |
|
1616 ExpressionDecompiler::loadAtom(jsbytecode *pc) |
|
1617 { |
|
1618 return script->getAtom(GET_UINT32_INDEX(pc)); |
|
1619 } |
|
1620 |
|
1621 JSAtom * |
|
1622 ExpressionDecompiler::getArg(unsigned slot) |
|
1623 { |
|
1624 JS_ASSERT(fun); |
|
1625 JS_ASSERT(slot < script->bindings.count()); |
|
1626 return (*localNames)[slot].name(); |
|
1627 } |
|
1628 |
|
1629 JSAtom * |
|
1630 ExpressionDecompiler::getLocal(uint32_t local, jsbytecode *pc) |
|
1631 { |
|
1632 JS_ASSERT(local < script->nfixed()); |
|
1633 if (local < script->nfixedvars()) { |
|
1634 JS_ASSERT(fun); |
|
1635 uint32_t slot = local + fun->nargs(); |
|
1636 JS_ASSERT(slot < script->bindings.count()); |
|
1637 return (*localNames)[slot].name(); |
|
1638 } |
|
1639 for (NestedScopeObject *chain = script->getStaticScope(pc); |
|
1640 chain; |
|
1641 chain = chain->enclosingNestedScope()) { |
|
1642 if (!chain->is<StaticBlockObject>()) |
|
1643 continue; |
|
1644 StaticBlockObject &block = chain->as<StaticBlockObject>(); |
|
1645 if (local < block.localOffset()) |
|
1646 continue; |
|
1647 local -= block.localOffset(); |
|
1648 if (local >= block.numVariables()) |
|
1649 return nullptr; |
|
1650 for (Shape::Range<NoGC> r(block.lastProperty()); !r.empty(); r.popFront()) { |
|
1651 const Shape &shape = r.front(); |
|
1652 if (block.shapeToIndex(shape) == local) |
|
1653 return JSID_TO_ATOM(shape.propid()); |
|
1654 } |
|
1655 break; |
|
1656 } |
|
1657 return nullptr; |
|
1658 } |
|
1659 |
|
1660 bool |
|
1661 ExpressionDecompiler::getOutput(char **res) |
|
1662 { |
|
1663 ptrdiff_t len = sprinter.stringEnd() - sprinter.stringAt(0); |
|
1664 *res = cx->pod_malloc<char>(len + 1); |
|
1665 if (!*res) |
|
1666 return false; |
|
1667 js_memcpy(*res, sprinter.stringAt(0), len); |
|
1668 (*res)[len] = 0; |
|
1669 return true; |
|
1670 } |
|
1671 |
|
1672 } // anonymous namespace |
|
1673 |
|
1674 static bool |
|
1675 FindStartPC(JSContext *cx, const FrameIter &iter, int spindex, int skipStackHits, Value v, |
|
1676 jsbytecode **valuepc) |
|
1677 { |
|
1678 jsbytecode *current = *valuepc; |
|
1679 |
|
1680 if (spindex == JSDVG_IGNORE_STACK) |
|
1681 return true; |
|
1682 |
|
1683 /* |
|
1684 * FIXME: Fall back if iter.isIon(), since the stack snapshot may be for the |
|
1685 * previous pc (see bug 831120). |
|
1686 */ |
|
1687 if (iter.isIon()) |
|
1688 return true; |
|
1689 |
|
1690 *valuepc = nullptr; |
|
1691 |
|
1692 BytecodeParser parser(cx, iter.script()); |
|
1693 if (!parser.parse()) |
|
1694 return false; |
|
1695 |
|
1696 if (spindex < 0 && spindex + int(parser.stackDepthAtPC(current)) < 0) |
|
1697 spindex = JSDVG_SEARCH_STACK; |
|
1698 |
|
1699 if (spindex == JSDVG_SEARCH_STACK) { |
|
1700 size_t index = iter.numFrameSlots(); |
|
1701 JS_ASSERT(index >= size_t(parser.stackDepthAtPC(current))); |
|
1702 |
|
1703 // We search from fp->sp to base to find the most recently calculated |
|
1704 // value matching v under assumption that it is the value that caused |
|
1705 // the exception. |
|
1706 int stackHits = 0; |
|
1707 Value s; |
|
1708 do { |
|
1709 if (!index) |
|
1710 return true; |
|
1711 s = iter.frameSlotValue(--index); |
|
1712 } while (s != v || stackHits++ != skipStackHits); |
|
1713 |
|
1714 // If the current PC has fewer values on the stack than the index we are |
|
1715 // looking for, the blamed value must be one pushed by the current |
|
1716 // bytecode, so restore *valuepc. |
|
1717 jsbytecode *pc = nullptr; |
|
1718 if (index < size_t(parser.stackDepthAtPC(current))) |
|
1719 pc = parser.pcForStackOperand(current, index); |
|
1720 *valuepc = pc ? pc : current; |
|
1721 } else { |
|
1722 jsbytecode *pc = parser.pcForStackOperand(current, spindex); |
|
1723 *valuepc = pc ? pc : current; |
|
1724 } |
|
1725 return true; |
|
1726 } |
|
1727 |
|
1728 static bool |
|
1729 DecompileExpressionFromStack(JSContext *cx, int spindex, int skipStackHits, HandleValue v, char **res) |
|
1730 { |
|
1731 JS_ASSERT(spindex < 0 || |
|
1732 spindex == JSDVG_IGNORE_STACK || |
|
1733 spindex == JSDVG_SEARCH_STACK); |
|
1734 |
|
1735 *res = nullptr; |
|
1736 |
|
1737 #ifdef JS_MORE_DETERMINISTIC |
|
1738 /* |
|
1739 * Give up if we need deterministic behavior for differential testing. |
|
1740 * IonMonkey doesn't use InterpreterFrames and this ensures we get the same |
|
1741 * error messages. |
|
1742 */ |
|
1743 return true; |
|
1744 #endif |
|
1745 |
|
1746 FrameIter frameIter(cx); |
|
1747 |
|
1748 if (frameIter.done() || !frameIter.hasScript()) |
|
1749 return true; |
|
1750 |
|
1751 RootedScript script(cx, frameIter.script()); |
|
1752 AutoCompartment ac(cx, &script->global()); |
|
1753 jsbytecode *valuepc = frameIter.pc(); |
|
1754 RootedFunction fun(cx, frameIter.isFunctionFrame() |
|
1755 ? frameIter.callee() |
|
1756 : nullptr); |
|
1757 |
|
1758 JS_ASSERT(script->containsPC(valuepc)); |
|
1759 |
|
1760 // Give up if in prologue. |
|
1761 if (valuepc < script->main()) |
|
1762 return true; |
|
1763 |
|
1764 if (!FindStartPC(cx, frameIter, spindex, skipStackHits, v, &valuepc)) |
|
1765 return false; |
|
1766 if (!valuepc) |
|
1767 return true; |
|
1768 |
|
1769 ExpressionDecompiler ed(cx, script, fun); |
|
1770 if (!ed.init()) |
|
1771 return false; |
|
1772 if (!ed.decompilePC(valuepc)) |
|
1773 return false; |
|
1774 |
|
1775 return ed.getOutput(res); |
|
1776 } |
|
1777 |
|
1778 char * |
|
1779 js::DecompileValueGenerator(JSContext *cx, int spindex, HandleValue v, |
|
1780 HandleString fallbackArg, int skipStackHits) |
|
1781 { |
|
1782 RootedString fallback(cx, fallbackArg); |
|
1783 { |
|
1784 char *result; |
|
1785 if (!DecompileExpressionFromStack(cx, spindex, skipStackHits, v, &result)) |
|
1786 return nullptr; |
|
1787 if (result) { |
|
1788 if (strcmp(result, "(intermediate value)")) |
|
1789 return result; |
|
1790 js_free(result); |
|
1791 } |
|
1792 } |
|
1793 if (!fallback) { |
|
1794 if (v.isUndefined()) |
|
1795 return JS_strdup(cx, js_undefined_str); // Prevent users from seeing "(void 0)" |
|
1796 fallback = ValueToSource(cx, v); |
|
1797 if (!fallback) |
|
1798 return nullptr; |
|
1799 } |
|
1800 |
|
1801 Rooted<JSLinearString *> linear(cx, fallback->ensureLinear(cx)); |
|
1802 if (!linear) |
|
1803 return nullptr; |
|
1804 TwoByteChars tbchars(linear->chars(), linear->length()); |
|
1805 return LossyTwoByteCharsToNewLatin1CharsZ(cx, tbchars).c_str(); |
|
1806 } |
|
1807 |
|
1808 static bool |
|
1809 DecompileArgumentFromStack(JSContext *cx, int formalIndex, char **res) |
|
1810 { |
|
1811 JS_ASSERT(formalIndex >= 0); |
|
1812 |
|
1813 *res = nullptr; |
|
1814 |
|
1815 #ifdef JS_MORE_DETERMINISTIC |
|
1816 /* See note in DecompileExpressionFromStack. */ |
|
1817 return true; |
|
1818 #endif |
|
1819 |
|
1820 /* |
|
1821 * Settle on the nearest script frame, which should be the builtin that |
|
1822 * called the intrinsic. |
|
1823 */ |
|
1824 FrameIter frameIter(cx); |
|
1825 JS_ASSERT(!frameIter.done()); |
|
1826 |
|
1827 /* |
|
1828 * Get the second-to-top frame, the caller of the builtin that called the |
|
1829 * intrinsic. |
|
1830 */ |
|
1831 ++frameIter; |
|
1832 if (frameIter.done() || !frameIter.hasScript()) |
|
1833 return true; |
|
1834 |
|
1835 RootedScript script(cx, frameIter.script()); |
|
1836 AutoCompartment ac(cx, &script->global()); |
|
1837 jsbytecode *current = frameIter.pc(); |
|
1838 RootedFunction fun(cx, frameIter.isFunctionFrame() |
|
1839 ? frameIter.callee() |
|
1840 : nullptr); |
|
1841 |
|
1842 JS_ASSERT(script->containsPC(current)); |
|
1843 |
|
1844 if (current < script->main()) |
|
1845 return true; |
|
1846 |
|
1847 /* Don't handle getters, setters or calls from fun.call/fun.apply. */ |
|
1848 if (JSOp(*current) != JSOP_CALL || static_cast<unsigned>(formalIndex) >= GET_ARGC(current)) |
|
1849 return true; |
|
1850 |
|
1851 BytecodeParser parser(cx, script); |
|
1852 if (!parser.parse()) |
|
1853 return false; |
|
1854 |
|
1855 int formalStackIndex = parser.stackDepthAtPC(current) - GET_ARGC(current) + formalIndex; |
|
1856 JS_ASSERT(formalStackIndex >= 0); |
|
1857 if (uint32_t(formalStackIndex) >= parser.stackDepthAtPC(current)) |
|
1858 return true; |
|
1859 |
|
1860 ExpressionDecompiler ed(cx, script, fun); |
|
1861 if (!ed.init()) |
|
1862 return false; |
|
1863 if (!ed.decompilePCForStackOperand(current, formalStackIndex)) |
|
1864 return false; |
|
1865 |
|
1866 return ed.getOutput(res); |
|
1867 } |
|
1868 |
|
1869 char * |
|
1870 js::DecompileArgument(JSContext *cx, int formalIndex, HandleValue v) |
|
1871 { |
|
1872 { |
|
1873 char *result; |
|
1874 if (!DecompileArgumentFromStack(cx, formalIndex, &result)) |
|
1875 return nullptr; |
|
1876 if (result) { |
|
1877 if (strcmp(result, "(intermediate value)")) |
|
1878 return result; |
|
1879 js_free(result); |
|
1880 } |
|
1881 } |
|
1882 if (v.isUndefined()) |
|
1883 return JS_strdup(cx, js_undefined_str); // Prevent users from seeing "(void 0)" |
|
1884 RootedString fallback(cx, ValueToSource(cx, v)); |
|
1885 if (!fallback) |
|
1886 return nullptr; |
|
1887 |
|
1888 Rooted<JSLinearString *> linear(cx, fallback->ensureLinear(cx)); |
|
1889 if (!linear) |
|
1890 return nullptr; |
|
1891 return LossyTwoByteCharsToNewLatin1CharsZ(cx, linear->range()).c_str(); |
|
1892 } |
|
1893 |
|
1894 bool |
|
1895 js::CallResultEscapes(jsbytecode *pc) |
|
1896 { |
|
1897 /* |
|
1898 * If we see any of these sequences, the result is unused: |
|
1899 * - call / pop |
|
1900 * |
|
1901 * If we see any of these sequences, the result is only tested for nullness: |
|
1902 * - call / ifeq |
|
1903 * - call / not / ifeq |
|
1904 */ |
|
1905 |
|
1906 if (*pc == JSOP_CALL) |
|
1907 pc += JSOP_CALL_LENGTH; |
|
1908 else if (*pc == JSOP_SPREADCALL) |
|
1909 pc += JSOP_SPREADCALL_LENGTH; |
|
1910 else |
|
1911 return true; |
|
1912 |
|
1913 if (*pc == JSOP_POP) |
|
1914 return false; |
|
1915 |
|
1916 if (*pc == JSOP_NOT) |
|
1917 pc += JSOP_NOT_LENGTH; |
|
1918 |
|
1919 return *pc != JSOP_IFEQ; |
|
1920 } |
|
1921 |
|
1922 extern bool |
|
1923 js::IsValidBytecodeOffset(JSContext *cx, JSScript *script, size_t offset) |
|
1924 { |
|
1925 // This could be faster (by following jump instructions if the target is <= offset). |
|
1926 for (BytecodeRange r(cx, script); !r.empty(); r.popFront()) { |
|
1927 size_t here = r.frontOffset(); |
|
1928 if (here >= offset) |
|
1929 return here == offset; |
|
1930 } |
|
1931 return false; |
|
1932 } |
|
1933 |
|
1934 JS_FRIEND_API(size_t) |
|
1935 js::GetPCCountScriptCount(JSContext *cx) |
|
1936 { |
|
1937 JSRuntime *rt = cx->runtime(); |
|
1938 |
|
1939 if (!rt->scriptAndCountsVector) |
|
1940 return 0; |
|
1941 |
|
1942 return rt->scriptAndCountsVector->length(); |
|
1943 } |
|
1944 |
|
1945 enum MaybeComma {NO_COMMA, COMMA}; |
|
1946 |
|
1947 static void |
|
1948 AppendJSONProperty(StringBuffer &buf, const char *name, MaybeComma comma = COMMA) |
|
1949 { |
|
1950 if (comma) |
|
1951 buf.append(','); |
|
1952 |
|
1953 buf.append('\"'); |
|
1954 buf.appendInflated(name, strlen(name)); |
|
1955 buf.appendInflated("\":", 2); |
|
1956 } |
|
1957 |
|
1958 static void |
|
1959 AppendArrayJSONProperties(JSContext *cx, StringBuffer &buf, |
|
1960 double *values, const char * const *names, unsigned count, |
|
1961 MaybeComma &comma) |
|
1962 { |
|
1963 for (unsigned i = 0; i < count; i++) { |
|
1964 if (values[i]) { |
|
1965 AppendJSONProperty(buf, names[i], comma); |
|
1966 comma = COMMA; |
|
1967 NumberValueToStringBuffer(cx, DoubleValue(values[i]), buf); |
|
1968 } |
|
1969 } |
|
1970 } |
|
1971 |
|
1972 JS_FRIEND_API(JSString *) |
|
1973 js::GetPCCountScriptSummary(JSContext *cx, size_t index) |
|
1974 { |
|
1975 JSRuntime *rt = cx->runtime(); |
|
1976 |
|
1977 if (!rt->scriptAndCountsVector || index >= rt->scriptAndCountsVector->length()) { |
|
1978 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL); |
|
1979 return nullptr; |
|
1980 } |
|
1981 |
|
1982 const ScriptAndCounts &sac = (*rt->scriptAndCountsVector)[index]; |
|
1983 RootedScript script(cx, sac.script); |
|
1984 |
|
1985 /* |
|
1986 * OOM on buffer appends here will not be caught immediately, but since |
|
1987 * StringBuffer uses a ContextAllocPolicy will trigger an exception on the |
|
1988 * context if they occur, which we'll catch before returning. |
|
1989 */ |
|
1990 StringBuffer buf(cx); |
|
1991 |
|
1992 buf.append('{'); |
|
1993 |
|
1994 AppendJSONProperty(buf, "file", NO_COMMA); |
|
1995 JSString *str = JS_NewStringCopyZ(cx, script->filename()); |
|
1996 if (!str || !(str = StringToSource(cx, str))) |
|
1997 return nullptr; |
|
1998 buf.append(str); |
|
1999 |
|
2000 AppendJSONProperty(buf, "line"); |
|
2001 NumberValueToStringBuffer(cx, Int32Value(script->lineno()), buf); |
|
2002 |
|
2003 if (script->functionNonDelazifying()) { |
|
2004 JSAtom *atom = script->functionNonDelazifying()->displayAtom(); |
|
2005 if (atom) { |
|
2006 AppendJSONProperty(buf, "name"); |
|
2007 if (!(str = StringToSource(cx, atom))) |
|
2008 return nullptr; |
|
2009 buf.append(str); |
|
2010 } |
|
2011 } |
|
2012 |
|
2013 double baseTotals[PCCounts::BASE_LIMIT] = {0.0}; |
|
2014 double accessTotals[PCCounts::ACCESS_LIMIT - PCCounts::BASE_LIMIT] = {0.0}; |
|
2015 double elementTotals[PCCounts::ELEM_LIMIT - PCCounts::ACCESS_LIMIT] = {0.0}; |
|
2016 double propertyTotals[PCCounts::PROP_LIMIT - PCCounts::ACCESS_LIMIT] = {0.0}; |
|
2017 double arithTotals[PCCounts::ARITH_LIMIT - PCCounts::BASE_LIMIT] = {0.0}; |
|
2018 |
|
2019 for (unsigned i = 0; i < script->length(); i++) { |
|
2020 PCCounts &counts = sac.getPCCounts(script->offsetToPC(i)); |
|
2021 if (!counts) |
|
2022 continue; |
|
2023 |
|
2024 JSOp op = (JSOp)script->code()[i]; |
|
2025 unsigned numCounts = PCCounts::numCounts(op); |
|
2026 |
|
2027 for (unsigned j = 0; j < numCounts; j++) { |
|
2028 double value = counts.get(j); |
|
2029 if (j < PCCounts::BASE_LIMIT) { |
|
2030 baseTotals[j] += value; |
|
2031 } else if (PCCounts::accessOp(op)) { |
|
2032 if (j < PCCounts::ACCESS_LIMIT) |
|
2033 accessTotals[j - PCCounts::BASE_LIMIT] += value; |
|
2034 else if (PCCounts::elementOp(op)) |
|
2035 elementTotals[j - PCCounts::ACCESS_LIMIT] += value; |
|
2036 else if (PCCounts::propertyOp(op)) |
|
2037 propertyTotals[j - PCCounts::ACCESS_LIMIT] += value; |
|
2038 else |
|
2039 MOZ_ASSUME_UNREACHABLE("Bad opcode"); |
|
2040 } else if (PCCounts::arithOp(op)) { |
|
2041 arithTotals[j - PCCounts::BASE_LIMIT] += value; |
|
2042 } else { |
|
2043 MOZ_ASSUME_UNREACHABLE("Bad opcode"); |
|
2044 } |
|
2045 } |
|
2046 } |
|
2047 |
|
2048 AppendJSONProperty(buf, "totals"); |
|
2049 buf.append('{'); |
|
2050 |
|
2051 MaybeComma comma = NO_COMMA; |
|
2052 |
|
2053 AppendArrayJSONProperties(cx, buf, baseTotals, countBaseNames, |
|
2054 JS_ARRAY_LENGTH(baseTotals), comma); |
|
2055 AppendArrayJSONProperties(cx, buf, accessTotals, countAccessNames, |
|
2056 JS_ARRAY_LENGTH(accessTotals), comma); |
|
2057 AppendArrayJSONProperties(cx, buf, elementTotals, countElementNames, |
|
2058 JS_ARRAY_LENGTH(elementTotals), comma); |
|
2059 AppendArrayJSONProperties(cx, buf, propertyTotals, countPropertyNames, |
|
2060 JS_ARRAY_LENGTH(propertyTotals), comma); |
|
2061 AppendArrayJSONProperties(cx, buf, arithTotals, countArithNames, |
|
2062 JS_ARRAY_LENGTH(arithTotals), comma); |
|
2063 |
|
2064 uint64_t ionActivity = 0; |
|
2065 jit::IonScriptCounts *ionCounts = sac.getIonCounts(); |
|
2066 while (ionCounts) { |
|
2067 for (size_t i = 0; i < ionCounts->numBlocks(); i++) |
|
2068 ionActivity += ionCounts->block(i).hitCount(); |
|
2069 ionCounts = ionCounts->previous(); |
|
2070 } |
|
2071 if (ionActivity) { |
|
2072 AppendJSONProperty(buf, "ion", comma); |
|
2073 NumberValueToStringBuffer(cx, DoubleValue(ionActivity), buf); |
|
2074 } |
|
2075 |
|
2076 buf.append('}'); |
|
2077 buf.append('}'); |
|
2078 |
|
2079 if (cx->isExceptionPending()) |
|
2080 return nullptr; |
|
2081 |
|
2082 return buf.finishString(); |
|
2083 } |
|
2084 |
|
2085 static bool |
|
2086 GetPCCountJSON(JSContext *cx, const ScriptAndCounts &sac, StringBuffer &buf) |
|
2087 { |
|
2088 RootedScript script(cx, sac.script); |
|
2089 |
|
2090 buf.append('{'); |
|
2091 AppendJSONProperty(buf, "text", NO_COMMA); |
|
2092 |
|
2093 JSString *str = JS_DecompileScript(cx, script, nullptr, 0); |
|
2094 if (!str || !(str = StringToSource(cx, str))) |
|
2095 return false; |
|
2096 |
|
2097 buf.append(str); |
|
2098 |
|
2099 AppendJSONProperty(buf, "line"); |
|
2100 NumberValueToStringBuffer(cx, Int32Value(script->lineno()), buf); |
|
2101 |
|
2102 AppendJSONProperty(buf, "opcodes"); |
|
2103 buf.append('['); |
|
2104 bool comma = false; |
|
2105 |
|
2106 SrcNoteLineScanner scanner(script->notes(), script->lineno()); |
|
2107 |
|
2108 for (jsbytecode *pc = script->code(); pc < script->codeEnd(); pc += GetBytecodeLength(pc)) { |
|
2109 size_t offset = script->pcToOffset(pc); |
|
2110 |
|
2111 JSOp op = (JSOp) *pc; |
|
2112 |
|
2113 if (comma) |
|
2114 buf.append(','); |
|
2115 comma = true; |
|
2116 |
|
2117 buf.append('{'); |
|
2118 |
|
2119 AppendJSONProperty(buf, "id", NO_COMMA); |
|
2120 NumberValueToStringBuffer(cx, Int32Value(offset), buf); |
|
2121 |
|
2122 scanner.advanceTo(offset); |
|
2123 |
|
2124 AppendJSONProperty(buf, "line"); |
|
2125 NumberValueToStringBuffer(cx, Int32Value(scanner.getLine()), buf); |
|
2126 |
|
2127 { |
|
2128 const char *name = js_CodeName[op]; |
|
2129 AppendJSONProperty(buf, "name"); |
|
2130 buf.append('\"'); |
|
2131 buf.appendInflated(name, strlen(name)); |
|
2132 buf.append('\"'); |
|
2133 } |
|
2134 |
|
2135 { |
|
2136 ExpressionDecompiler ed(cx, script, script->functionDelazifying()); |
|
2137 if (!ed.init()) |
|
2138 return false; |
|
2139 if (!ed.decompilePC(pc)) |
|
2140 return false; |
|
2141 char *text; |
|
2142 if (!ed.getOutput(&text)) |
|
2143 return false; |
|
2144 AppendJSONProperty(buf, "text"); |
|
2145 JSString *str = JS_NewStringCopyZ(cx, text); |
|
2146 js_free(text); |
|
2147 if (!str || !(str = StringToSource(cx, str))) |
|
2148 return false; |
|
2149 buf.append(str); |
|
2150 } |
|
2151 |
|
2152 PCCounts &counts = sac.getPCCounts(pc); |
|
2153 unsigned numCounts = PCCounts::numCounts(op); |
|
2154 |
|
2155 AppendJSONProperty(buf, "counts"); |
|
2156 buf.append('{'); |
|
2157 |
|
2158 MaybeComma comma = NO_COMMA; |
|
2159 for (unsigned i = 0; i < numCounts; i++) { |
|
2160 double value = counts.get(i); |
|
2161 if (value > 0) { |
|
2162 AppendJSONProperty(buf, PCCounts::countName(op, i), comma); |
|
2163 comma = COMMA; |
|
2164 NumberValueToStringBuffer(cx, DoubleValue(value), buf); |
|
2165 } |
|
2166 } |
|
2167 |
|
2168 buf.append('}'); |
|
2169 buf.append('}'); |
|
2170 } |
|
2171 |
|
2172 buf.append(']'); |
|
2173 |
|
2174 jit::IonScriptCounts *ionCounts = sac.getIonCounts(); |
|
2175 if (ionCounts) { |
|
2176 AppendJSONProperty(buf, "ion"); |
|
2177 buf.append('['); |
|
2178 bool comma = false; |
|
2179 while (ionCounts) { |
|
2180 if (comma) |
|
2181 buf.append(','); |
|
2182 comma = true; |
|
2183 |
|
2184 buf.append('['); |
|
2185 for (size_t i = 0; i < ionCounts->numBlocks(); i++) { |
|
2186 if (i) |
|
2187 buf.append(','); |
|
2188 const jit::IonBlockCounts &block = ionCounts->block(i); |
|
2189 |
|
2190 buf.append('{'); |
|
2191 AppendJSONProperty(buf, "id", NO_COMMA); |
|
2192 NumberValueToStringBuffer(cx, Int32Value(block.id()), buf); |
|
2193 AppendJSONProperty(buf, "offset"); |
|
2194 NumberValueToStringBuffer(cx, Int32Value(block.offset()), buf); |
|
2195 AppendJSONProperty(buf, "successors"); |
|
2196 buf.append('['); |
|
2197 for (size_t j = 0; j < block.numSuccessors(); j++) { |
|
2198 if (j) |
|
2199 buf.append(','); |
|
2200 NumberValueToStringBuffer(cx, Int32Value(block.successor(j)), buf); |
|
2201 } |
|
2202 buf.append(']'); |
|
2203 AppendJSONProperty(buf, "hits"); |
|
2204 NumberValueToStringBuffer(cx, DoubleValue(block.hitCount()), buf); |
|
2205 |
|
2206 AppendJSONProperty(buf, "code"); |
|
2207 JSString *str = JS_NewStringCopyZ(cx, block.code()); |
|
2208 if (!str || !(str = StringToSource(cx, str))) |
|
2209 return false; |
|
2210 buf.append(str); |
|
2211 buf.append('}'); |
|
2212 } |
|
2213 buf.append(']'); |
|
2214 |
|
2215 ionCounts = ionCounts->previous(); |
|
2216 } |
|
2217 buf.append(']'); |
|
2218 } |
|
2219 |
|
2220 buf.append('}'); |
|
2221 |
|
2222 return !cx->isExceptionPending(); |
|
2223 } |
|
2224 |
|
2225 JS_FRIEND_API(JSString *) |
|
2226 js::GetPCCountScriptContents(JSContext *cx, size_t index) |
|
2227 { |
|
2228 JSRuntime *rt = cx->runtime(); |
|
2229 |
|
2230 if (!rt->scriptAndCountsVector || index >= rt->scriptAndCountsVector->length()) { |
|
2231 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL); |
|
2232 return nullptr; |
|
2233 } |
|
2234 |
|
2235 const ScriptAndCounts &sac = (*rt->scriptAndCountsVector)[index]; |
|
2236 JSScript *script = sac.script; |
|
2237 |
|
2238 StringBuffer buf(cx); |
|
2239 |
|
2240 if (!script->functionNonDelazifying() && !script->compileAndGo()) |
|
2241 return buf.finishString(); |
|
2242 |
|
2243 { |
|
2244 AutoCompartment ac(cx, &script->global()); |
|
2245 if (!GetPCCountJSON(cx, sac, buf)) |
|
2246 return nullptr; |
|
2247 } |
|
2248 |
|
2249 return buf.finishString(); |
|
2250 } |