Wed, 31 Dec 2014 06:09:35 +0100
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 | } |