js/src/jsopcode.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial