1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/src/jsopcode.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2250 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99: 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/* 1.11 + * JS bytecode descriptors, disassemblers, and (expression) decompilers. 1.12 + */ 1.13 + 1.14 +#include "jsopcodeinlines.h" 1.15 + 1.16 +#include <ctype.h> 1.17 +#include <stdarg.h> 1.18 +#include <stdio.h> 1.19 +#include <string.h> 1.20 + 1.21 +#include "jsanalyze.h" 1.22 +#include "jsapi.h" 1.23 +#include "jsatom.h" 1.24 +#include "jscntxt.h" 1.25 +#include "jscompartment.h" 1.26 +#include "jsfun.h" 1.27 +#include "jsnum.h" 1.28 +#include "jsobj.h" 1.29 +#include "jsprf.h" 1.30 +#include "jsscript.h" 1.31 +#include "jsstr.h" 1.32 +#include "jstypes.h" 1.33 +#include "jsutil.h" 1.34 + 1.35 +#include "frontend/BytecodeCompiler.h" 1.36 +#include "frontend/SourceNotes.h" 1.37 +#include "js/CharacterEncoding.h" 1.38 +#include "vm/Opcodes.h" 1.39 +#include "vm/ScopeObject.h" 1.40 +#include "vm/Shape.h" 1.41 +#include "vm/StringBuffer.h" 1.42 + 1.43 +#include "jscntxtinlines.h" 1.44 +#include "jscompartmentinlines.h" 1.45 +#include "jsinferinlines.h" 1.46 +#include "jsobjinlines.h" 1.47 +#include "jsscriptinlines.h" 1.48 + 1.49 +using namespace js; 1.50 +using namespace js::gc; 1.51 + 1.52 +using js::frontend::IsIdentifier; 1.53 + 1.54 +/* 1.55 + * Index limit must stay within 32 bits. 1.56 + */ 1.57 +JS_STATIC_ASSERT(sizeof(uint32_t) * JS_BITS_PER_BYTE >= INDEX_LIMIT_LOG2 + 1); 1.58 + 1.59 +const JSCodeSpec js_CodeSpec[] = { 1.60 +#define MAKE_CODESPEC(op,val,name,token,length,nuses,ndefs,format) {length,nuses,ndefs,format}, 1.61 + FOR_EACH_OPCODE(MAKE_CODESPEC) 1.62 +#undef MAKE_CODESPEC 1.63 +}; 1.64 + 1.65 +const unsigned js_NumCodeSpecs = JS_ARRAY_LENGTH(js_CodeSpec); 1.66 + 1.67 +/* 1.68 + * Each element of the array is either a source literal associated with JS 1.69 + * bytecode or null. 1.70 + */ 1.71 +static const char * const CodeToken[] = { 1.72 +#define TOKEN(op, val, name, token, ...) token, 1.73 + FOR_EACH_OPCODE(TOKEN) 1.74 +#undef TOKEN 1.75 +}; 1.76 + 1.77 +/* 1.78 + * Array of JS bytecode names used by PC count JSON, DEBUG-only js_Disassemble 1.79 + * and JIT debug spew. 1.80 + */ 1.81 +const char * const js_CodeName[] = { 1.82 +#define OPNAME(op, val, name, ...) name, 1.83 + FOR_EACH_OPCODE(OPNAME) 1.84 +#undef OPNAME 1.85 +}; 1.86 + 1.87 +/************************************************************************/ 1.88 + 1.89 +#define COUNTS_LEN 16 1.90 + 1.91 +size_t 1.92 +js_GetVariableBytecodeLength(jsbytecode *pc) 1.93 +{ 1.94 + JSOp op = JSOp(*pc); 1.95 + JS_ASSERT(js_CodeSpec[op].length == -1); 1.96 + switch (op) { 1.97 + case JSOP_TABLESWITCH: { 1.98 + /* Structure: default-jump case-low case-high case1-jump ... */ 1.99 + pc += JUMP_OFFSET_LEN; 1.100 + int32_t low = GET_JUMP_OFFSET(pc); 1.101 + pc += JUMP_OFFSET_LEN; 1.102 + int32_t high = GET_JUMP_OFFSET(pc); 1.103 + unsigned ncases = unsigned(high - low + 1); 1.104 + return 1 + 3 * JUMP_OFFSET_LEN + ncases * JUMP_OFFSET_LEN; 1.105 + } 1.106 + default: 1.107 + MOZ_ASSUME_UNREACHABLE("Unexpected op"); 1.108 + } 1.109 +} 1.110 + 1.111 +unsigned 1.112 +js::StackUses(JSScript *script, jsbytecode *pc) 1.113 +{ 1.114 + JSOp op = (JSOp) *pc; 1.115 + const JSCodeSpec &cs = js_CodeSpec[op]; 1.116 + if (cs.nuses >= 0) 1.117 + return cs.nuses; 1.118 + 1.119 + JS_ASSERT(js_CodeSpec[op].nuses == -1); 1.120 + switch (op) { 1.121 + case JSOP_POPN: 1.122 + return GET_UINT16(pc); 1.123 + default: 1.124 + /* stack: fun, this, [argc arguments] */ 1.125 + JS_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL || 1.126 + op == JSOP_FUNCALL || op == JSOP_FUNAPPLY); 1.127 + return 2 + GET_ARGC(pc); 1.128 + } 1.129 +} 1.130 + 1.131 +unsigned 1.132 +js::StackDefs(JSScript *script, jsbytecode *pc) 1.133 +{ 1.134 + JSOp op = (JSOp) *pc; 1.135 + const JSCodeSpec &cs = js_CodeSpec[op]; 1.136 + JS_ASSERT (cs.ndefs >= 0); 1.137 + return cs.ndefs; 1.138 +} 1.139 + 1.140 +static const char * const countBaseNames[] = { 1.141 + "interp" 1.142 +}; 1.143 + 1.144 +JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) == PCCounts::BASE_LIMIT); 1.145 + 1.146 +static const char * const countAccessNames[] = { 1.147 + "infer_mono", 1.148 + "infer_di", 1.149 + "infer_poly", 1.150 + "infer_barrier", 1.151 + "infer_nobarrier", 1.152 + "observe_undefined", 1.153 + "observe_null", 1.154 + "observe_boolean", 1.155 + "observe_int32", 1.156 + "observe_double", 1.157 + "observe_string", 1.158 + "observe_object" 1.159 +}; 1.160 + 1.161 +JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) + 1.162 + JS_ARRAY_LENGTH(countAccessNames) == PCCounts::ACCESS_LIMIT); 1.163 + 1.164 +static const char * const countElementNames[] = { 1.165 + "id_int", 1.166 + "id_double", 1.167 + "id_other", 1.168 + "id_unknown", 1.169 + "elem_typed", 1.170 + "elem_packed", 1.171 + "elem_dense", 1.172 + "elem_other" 1.173 +}; 1.174 + 1.175 +JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) + 1.176 + JS_ARRAY_LENGTH(countAccessNames) + 1.177 + JS_ARRAY_LENGTH(countElementNames) == PCCounts::ELEM_LIMIT); 1.178 + 1.179 +static const char * const countPropertyNames[] = { 1.180 + "prop_static", 1.181 + "prop_definite", 1.182 + "prop_other" 1.183 +}; 1.184 + 1.185 +JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) + 1.186 + JS_ARRAY_LENGTH(countAccessNames) + 1.187 + JS_ARRAY_LENGTH(countPropertyNames) == PCCounts::PROP_LIMIT); 1.188 + 1.189 +static const char * const countArithNames[] = { 1.190 + "arith_int", 1.191 + "arith_double", 1.192 + "arith_other", 1.193 + "arith_unknown", 1.194 +}; 1.195 + 1.196 +JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) + 1.197 + JS_ARRAY_LENGTH(countArithNames) == PCCounts::ARITH_LIMIT); 1.198 + 1.199 +/* static */ const char * 1.200 +PCCounts::countName(JSOp op, size_t which) 1.201 +{ 1.202 + JS_ASSERT(which < numCounts(op)); 1.203 + 1.204 + if (which < BASE_LIMIT) 1.205 + return countBaseNames[which]; 1.206 + 1.207 + if (accessOp(op)) { 1.208 + if (which < ACCESS_LIMIT) 1.209 + return countAccessNames[which - BASE_LIMIT]; 1.210 + if (elementOp(op)) 1.211 + return countElementNames[which - ACCESS_LIMIT]; 1.212 + if (propertyOp(op)) 1.213 + return countPropertyNames[which - ACCESS_LIMIT]; 1.214 + MOZ_ASSUME_UNREACHABLE("bad op"); 1.215 + } 1.216 + 1.217 + if (arithOp(op)) 1.218 + return countArithNames[which - BASE_LIMIT]; 1.219 + 1.220 + MOZ_ASSUME_UNREACHABLE("bad op"); 1.221 +} 1.222 + 1.223 +#ifdef JS_ION 1.224 +void 1.225 +js::DumpIonScriptCounts(Sprinter *sp, jit::IonScriptCounts *ionCounts) 1.226 +{ 1.227 + Sprint(sp, "IonScript [%lu blocks]:\n", ionCounts->numBlocks()); 1.228 + for (size_t i = 0; i < ionCounts->numBlocks(); i++) { 1.229 + const jit::IonBlockCounts &block = ionCounts->block(i); 1.230 + if (block.hitCount() < 10) 1.231 + continue; 1.232 + Sprint(sp, "BB #%lu [%05u]", block.id(), block.offset()); 1.233 + for (size_t j = 0; j < block.numSuccessors(); j++) 1.234 + Sprint(sp, " -> #%lu", block.successor(j)); 1.235 + Sprint(sp, " :: %llu hits\n", block.hitCount()); 1.236 + Sprint(sp, "%s\n", block.code()); 1.237 + } 1.238 +} 1.239 +#endif 1.240 + 1.241 +void 1.242 +js_DumpPCCounts(JSContext *cx, HandleScript script, js::Sprinter *sp) 1.243 +{ 1.244 + JS_ASSERT(script->hasScriptCounts()); 1.245 + 1.246 +#ifdef DEBUG 1.247 + jsbytecode *pc = script->code(); 1.248 + while (pc < script->codeEnd()) { 1.249 + JSOp op = JSOp(*pc); 1.250 + jsbytecode *next = GetNextPc(pc); 1.251 + 1.252 + if (!js_Disassemble1(cx, script, pc, script->pcToOffset(pc), true, sp)) 1.253 + return; 1.254 + 1.255 + size_t total = PCCounts::numCounts(op); 1.256 + double *raw = script->getPCCounts(pc).rawCounts(); 1.257 + 1.258 + Sprint(sp, " {"); 1.259 + bool printed = false; 1.260 + for (size_t i = 0; i < total; i++) { 1.261 + double val = raw[i]; 1.262 + if (val) { 1.263 + if (printed) 1.264 + Sprint(sp, ", "); 1.265 + Sprint(sp, "\"%s\": %.0f", PCCounts::countName(op, i), val); 1.266 + printed = true; 1.267 + } 1.268 + } 1.269 + Sprint(sp, "}\n"); 1.270 + 1.271 + pc = next; 1.272 + } 1.273 +#endif 1.274 + 1.275 +#ifdef JS_ION 1.276 + jit::IonScriptCounts *ionCounts = script->getIonCounts(); 1.277 + 1.278 + while (ionCounts) { 1.279 + DumpIonScriptCounts(sp, ionCounts); 1.280 + ionCounts = ionCounts->previous(); 1.281 + } 1.282 +#endif 1.283 +} 1.284 + 1.285 +///////////////////////////////////////////////////////////////////// 1.286 +// Bytecode Parser 1.287 +///////////////////////////////////////////////////////////////////// 1.288 + 1.289 +namespace { 1.290 + 1.291 +class BytecodeParser 1.292 +{ 1.293 + class Bytecode 1.294 + { 1.295 + public: 1.296 + Bytecode() { mozilla::PodZero(this); } 1.297 + 1.298 + // Whether this instruction has been analyzed to get its output defines 1.299 + // and stack. 1.300 + bool parsed : 1; 1.301 + 1.302 + // Stack depth before this opcode. 1.303 + uint32_t stackDepth; 1.304 + 1.305 + // Pointer to array of |stackDepth| offsets. An element at position N 1.306 + // in the array is the offset of the opcode that defined the 1.307 + // corresponding stack slot. The top of the stack is at position 1.308 + // |stackDepth - 1|. 1.309 + uint32_t *offsetStack; 1.310 + 1.311 + bool captureOffsetStack(LifoAlloc &alloc, const uint32_t *stack, uint32_t depth) { 1.312 + stackDepth = depth; 1.313 + offsetStack = alloc.newArray<uint32_t>(stackDepth); 1.314 + if (stackDepth) { 1.315 + if (!offsetStack) 1.316 + return false; 1.317 + for (uint32_t n = 0; n < stackDepth; n++) 1.318 + offsetStack[n] = stack[n]; 1.319 + } 1.320 + return true; 1.321 + } 1.322 + 1.323 + // When control-flow merges, intersect the stacks, marking slots that 1.324 + // are defined by different offsets with the UINT32_MAX sentinel. 1.325 + // This is sufficient for forward control-flow. It doesn't grok loops 1.326 + // -- for that you would have to iterate to a fixed point -- but there 1.327 + // shouldn't be operands on the stack at a loop back-edge anyway. 1.328 + void mergeOffsetStack(const uint32_t *stack, uint32_t depth) { 1.329 + JS_ASSERT(depth == stackDepth); 1.330 + for (uint32_t n = 0; n < stackDepth; n++) 1.331 + if (offsetStack[n] != stack[n]) 1.332 + offsetStack[n] = UINT32_MAX; 1.333 + } 1.334 + }; 1.335 + 1.336 + JSContext *cx_; 1.337 + LifoAllocScope allocScope_; 1.338 + RootedScript script_; 1.339 + 1.340 + Bytecode **codeArray_; 1.341 + 1.342 + public: 1.343 + BytecodeParser(JSContext *cx, JSScript *script) 1.344 + : cx_(cx), 1.345 + allocScope_(&cx->tempLifoAlloc()), 1.346 + script_(cx, script), 1.347 + codeArray_(nullptr) { } 1.348 + 1.349 + bool parse(); 1.350 + 1.351 +#ifdef DEBUG 1.352 + bool isReachable(uint32_t offset) { return maybeCode(offset); } 1.353 + bool isReachable(const jsbytecode *pc) { return maybeCode(pc); } 1.354 +#endif 1.355 + 1.356 + uint32_t stackDepthAtPC(uint32_t offset) { 1.357 + // Sometimes the code generator in debug mode asks about the stack depth 1.358 + // of unreachable code (bug 932180 comment 22). Assume that unreachable 1.359 + // code has no operands on the stack. 1.360 + return getCode(offset).stackDepth; 1.361 + } 1.362 + uint32_t stackDepthAtPC(const jsbytecode *pc) { return stackDepthAtPC(script_->pcToOffset(pc)); } 1.363 + 1.364 + uint32_t offsetForStackOperand(uint32_t offset, int operand) { 1.365 + Bytecode &code = getCode(offset); 1.366 + if (operand < 0) { 1.367 + operand += code.stackDepth; 1.368 + JS_ASSERT(operand >= 0); 1.369 + } 1.370 + JS_ASSERT(uint32_t(operand) < code.stackDepth); 1.371 + return code.offsetStack[operand]; 1.372 + } 1.373 + jsbytecode *pcForStackOperand(jsbytecode *pc, int operand) { 1.374 + uint32_t offset = offsetForStackOperand(script_->pcToOffset(pc), operand); 1.375 + if (offset == UINT32_MAX) 1.376 + return nullptr; 1.377 + return script_->offsetToPC(offsetForStackOperand(script_->pcToOffset(pc), operand)); 1.378 + } 1.379 + 1.380 + private: 1.381 + LifoAlloc &alloc() { 1.382 + return allocScope_.alloc(); 1.383 + } 1.384 + 1.385 + void reportOOM() { 1.386 + allocScope_.releaseEarly(); 1.387 + js_ReportOutOfMemory(cx_); 1.388 + } 1.389 + 1.390 + uint32_t numSlots() { 1.391 + return 1 + script_->nfixed() + 1.392 + (script_->functionNonDelazifying() ? script_->functionNonDelazifying()->nargs() : 0); 1.393 + } 1.394 + 1.395 + uint32_t maximumStackDepth() { 1.396 + return script_->nslots() - script_->nfixed(); 1.397 + } 1.398 + 1.399 + Bytecode& getCode(uint32_t offset) { 1.400 + JS_ASSERT(offset < script_->length()); 1.401 + JS_ASSERT(codeArray_[offset]); 1.402 + return *codeArray_[offset]; 1.403 + } 1.404 + Bytecode& getCode(const jsbytecode *pc) { return getCode(script_->pcToOffset(pc)); } 1.405 + 1.406 + Bytecode* maybeCode(uint32_t offset) { 1.407 + JS_ASSERT(offset < script_->length()); 1.408 + return codeArray_[offset]; 1.409 + } 1.410 + Bytecode* maybeCode(const jsbytecode *pc) { return maybeCode(script_->pcToOffset(pc)); } 1.411 + 1.412 + uint32_t simulateOp(JSOp op, uint32_t offset, uint32_t *offsetStack, uint32_t stackDepth); 1.413 + 1.414 + inline bool addJump(uint32_t offset, uint32_t *currentOffset, 1.415 + uint32_t stackDepth, const uint32_t *offsetStack); 1.416 +}; 1.417 + 1.418 +} // anonymous namespace 1.419 + 1.420 +uint32_t 1.421 +BytecodeParser::simulateOp(JSOp op, uint32_t offset, uint32_t *offsetStack, uint32_t stackDepth) 1.422 +{ 1.423 + uint32_t nuses = GetUseCount(script_, offset); 1.424 + uint32_t ndefs = GetDefCount(script_, offset); 1.425 + 1.426 + JS_ASSERT(stackDepth >= nuses); 1.427 + stackDepth -= nuses; 1.428 + JS_ASSERT(stackDepth + ndefs <= maximumStackDepth()); 1.429 + 1.430 + // Mark the current offset as defining its values on the offset stack, 1.431 + // unless it just reshuffles the stack. In that case we want to preserve 1.432 + // the opcode that generated the original value. 1.433 + switch (op) { 1.434 + default: 1.435 + for (uint32_t n = 0; n != ndefs; ++n) 1.436 + offsetStack[stackDepth + n] = offset; 1.437 + break; 1.438 + 1.439 + case JSOP_CASE: 1.440 + /* Keep the switch value. */ 1.441 + JS_ASSERT(ndefs == 1); 1.442 + break; 1.443 + 1.444 + case JSOP_DUP: 1.445 + JS_ASSERT(ndefs == 2); 1.446 + if (offsetStack) 1.447 + offsetStack[stackDepth + 1] = offsetStack[stackDepth]; 1.448 + break; 1.449 + 1.450 + case JSOP_DUP2: 1.451 + JS_ASSERT(ndefs == 4); 1.452 + if (offsetStack) { 1.453 + offsetStack[stackDepth + 2] = offsetStack[stackDepth]; 1.454 + offsetStack[stackDepth + 3] = offsetStack[stackDepth + 1]; 1.455 + } 1.456 + break; 1.457 + 1.458 + case JSOP_DUPAT: { 1.459 + JS_ASSERT(ndefs == 1); 1.460 + jsbytecode *pc = script_->offsetToPC(offset); 1.461 + unsigned n = GET_UINT24(pc); 1.462 + JS_ASSERT(n < stackDepth); 1.463 + if (offsetStack) 1.464 + offsetStack[stackDepth] = offsetStack[stackDepth - 1 - n]; 1.465 + break; 1.466 + } 1.467 + 1.468 + case JSOP_SWAP: 1.469 + JS_ASSERT(ndefs == 2); 1.470 + if (offsetStack) { 1.471 + uint32_t tmp = offsetStack[stackDepth + 1]; 1.472 + offsetStack[stackDepth + 1] = offsetStack[stackDepth]; 1.473 + offsetStack[stackDepth] = tmp; 1.474 + } 1.475 + break; 1.476 + } 1.477 + stackDepth += ndefs; 1.478 + return stackDepth; 1.479 +} 1.480 + 1.481 +bool 1.482 +BytecodeParser::addJump(uint32_t offset, uint32_t *currentOffset, 1.483 + uint32_t stackDepth, const uint32_t *offsetStack) 1.484 +{ 1.485 + JS_ASSERT(offset < script_->length()); 1.486 + 1.487 + Bytecode *&code = codeArray_[offset]; 1.488 + if (!code) { 1.489 + code = alloc().new_<Bytecode>(); 1.490 + if (!code) 1.491 + return false; 1.492 + if (!code->captureOffsetStack(alloc(), offsetStack, stackDepth)) { 1.493 + reportOOM(); 1.494 + return false; 1.495 + } 1.496 + } else { 1.497 + code->mergeOffsetStack(offsetStack, stackDepth); 1.498 + } 1.499 + 1.500 + if (offset < *currentOffset && !code->parsed) { 1.501 + // Backedge in a while/for loop, whose body has not been parsed due 1.502 + // to a lack of fallthrough at the loop head. Roll back the offset 1.503 + // to analyze the body. 1.504 + *currentOffset = offset; 1.505 + } 1.506 + 1.507 + return true; 1.508 +} 1.509 + 1.510 +bool 1.511 +BytecodeParser::parse() 1.512 +{ 1.513 + JS_ASSERT(!codeArray_); 1.514 + 1.515 + uint32_t length = script_->length(); 1.516 + codeArray_ = alloc().newArray<Bytecode*>(length); 1.517 + 1.518 + if (!codeArray_) { 1.519 + reportOOM(); 1.520 + return false; 1.521 + } 1.522 + 1.523 + mozilla::PodZero(codeArray_, length); 1.524 + 1.525 + // Fill in stack depth and definitions at initial bytecode. 1.526 + Bytecode *startcode = alloc().new_<Bytecode>(); 1.527 + if (!startcode) { 1.528 + reportOOM(); 1.529 + return false; 1.530 + } 1.531 + 1.532 + // Fill in stack depth and definitions at initial bytecode. 1.533 + uint32_t *offsetStack = alloc().newArray<uint32_t>(maximumStackDepth()); 1.534 + if (maximumStackDepth() && !offsetStack) { 1.535 + reportOOM(); 1.536 + return false; 1.537 + } 1.538 + 1.539 + startcode->stackDepth = 0; 1.540 + codeArray_[0] = startcode; 1.541 + 1.542 + uint32_t offset, nextOffset = 0; 1.543 + while (nextOffset < length) { 1.544 + offset = nextOffset; 1.545 + 1.546 + Bytecode *code = maybeCode(offset); 1.547 + jsbytecode *pc = script_->offsetToPC(offset); 1.548 + 1.549 + JSOp op = (JSOp)*pc; 1.550 + JS_ASSERT(op < JSOP_LIMIT); 1.551 + 1.552 + // Immediate successor of this bytecode. 1.553 + uint32_t successorOffset = offset + GetBytecodeLength(pc); 1.554 + 1.555 + // Next bytecode to analyze. This is either the successor, or is an 1.556 + // earlier bytecode if this bytecode has a loop backedge. 1.557 + nextOffset = successorOffset; 1.558 + 1.559 + if (!code) { 1.560 + // Haven't found a path by which this bytecode is reachable. 1.561 + continue; 1.562 + } 1.563 + 1.564 + if (code->parsed) { 1.565 + // No need to reparse. 1.566 + continue; 1.567 + } 1.568 + 1.569 + code->parsed = true; 1.570 + 1.571 + uint32_t stackDepth = simulateOp(op, offset, offsetStack, code->stackDepth); 1.572 + 1.573 + switch (op) { 1.574 + case JSOP_TABLESWITCH: { 1.575 + uint32_t defaultOffset = offset + GET_JUMP_OFFSET(pc); 1.576 + jsbytecode *pc2 = pc + JUMP_OFFSET_LEN; 1.577 + int32_t low = GET_JUMP_OFFSET(pc2); 1.578 + pc2 += JUMP_OFFSET_LEN; 1.579 + int32_t high = GET_JUMP_OFFSET(pc2); 1.580 + pc2 += JUMP_OFFSET_LEN; 1.581 + 1.582 + if (!addJump(defaultOffset, &nextOffset, stackDepth, offsetStack)) 1.583 + return false; 1.584 + 1.585 + for (int32_t i = low; i <= high; i++) { 1.586 + uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc2); 1.587 + if (targetOffset != offset) { 1.588 + if (!addJump(targetOffset, &nextOffset, stackDepth, offsetStack)) 1.589 + return false; 1.590 + } 1.591 + pc2 += JUMP_OFFSET_LEN; 1.592 + } 1.593 + break; 1.594 + } 1.595 + 1.596 + case JSOP_TRY: { 1.597 + // Everything between a try and corresponding catch or finally is conditional. 1.598 + // Note that there is no problem with code which is skipped by a thrown 1.599 + // exception but is not caught by a later handler in the same function: 1.600 + // no more code will execute, and it does not matter what is defined. 1.601 + JSTryNote *tn = script_->trynotes()->vector; 1.602 + JSTryNote *tnlimit = tn + script_->trynotes()->length; 1.603 + for (; tn < tnlimit; tn++) { 1.604 + uint32_t startOffset = script_->mainOffset() + tn->start; 1.605 + if (startOffset == offset + 1) { 1.606 + uint32_t catchOffset = startOffset + tn->length; 1.607 + if (tn->kind != JSTRY_ITER && tn->kind != JSTRY_LOOP) { 1.608 + if (!addJump(catchOffset, &nextOffset, stackDepth, offsetStack)) 1.609 + return false; 1.610 + } 1.611 + } 1.612 + } 1.613 + break; 1.614 + } 1.615 + 1.616 + default: 1.617 + break; 1.618 + } 1.619 + 1.620 + // Check basic jump opcodes, which may or may not have a fallthrough. 1.621 + if (IsJumpOpcode(op)) { 1.622 + // Case instructions do not push the lvalue back when branching. 1.623 + uint32_t newStackDepth = stackDepth; 1.624 + if (op == JSOP_CASE) 1.625 + newStackDepth--; 1.626 + 1.627 + uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc); 1.628 + if (!addJump(targetOffset, &nextOffset, newStackDepth, offsetStack)) 1.629 + return false; 1.630 + } 1.631 + 1.632 + // Handle any fallthrough from this opcode. 1.633 + if (BytecodeFallsThrough(op)) { 1.634 + JS_ASSERT(successorOffset < script_->length()); 1.635 + 1.636 + Bytecode *&nextcode = codeArray_[successorOffset]; 1.637 + 1.638 + if (!nextcode) { 1.639 + nextcode = alloc().new_<Bytecode>(); 1.640 + if (!nextcode) { 1.641 + reportOOM(); 1.642 + return false; 1.643 + } 1.644 + if (!nextcode->captureOffsetStack(alloc(), offsetStack, stackDepth)) { 1.645 + reportOOM(); 1.646 + return false; 1.647 + } 1.648 + } else { 1.649 + nextcode->mergeOffsetStack(offsetStack, stackDepth); 1.650 + } 1.651 + } 1.652 + } 1.653 + 1.654 + return true; 1.655 +} 1.656 + 1.657 +#ifdef DEBUG 1.658 + 1.659 +bool 1.660 +js::ReconstructStackDepth(JSContext *cx, JSScript *script, jsbytecode *pc, uint32_t *depth, bool *reachablePC) 1.661 +{ 1.662 + BytecodeParser parser(cx, script); 1.663 + if (!parser.parse()) 1.664 + return false; 1.665 + 1.666 + *reachablePC = parser.isReachable(pc); 1.667 + 1.668 + if (*reachablePC) 1.669 + *depth = parser.stackDepthAtPC(pc); 1.670 + 1.671 + return true; 1.672 +} 1.673 + 1.674 +/* 1.675 + * If pc != nullptr, include a prefix indicating whether the PC is at the 1.676 + * current line. If showAll is true, include the source note type and the 1.677 + * entry stack depth. 1.678 + */ 1.679 +JS_FRIEND_API(bool) 1.680 +js_DisassembleAtPC(JSContext *cx, JSScript *scriptArg, bool lines, 1.681 + jsbytecode *pc, bool showAll, Sprinter *sp) 1.682 +{ 1.683 + RootedScript script(cx, scriptArg); 1.684 + BytecodeParser parser(cx, script); 1.685 + 1.686 + jsbytecode *next, *end; 1.687 + unsigned len; 1.688 + 1.689 + if (showAll && !parser.parse()) 1.690 + return false; 1.691 + 1.692 + if (showAll) 1.693 + Sprint(sp, "%s:%u\n", script->filename(), script->lineno()); 1.694 + 1.695 + if (pc != nullptr) 1.696 + sp->put(" "); 1.697 + if (showAll) 1.698 + sp->put("sn stack "); 1.699 + sp->put("loc "); 1.700 + if (lines) 1.701 + sp->put("line"); 1.702 + sp->put(" op\n"); 1.703 + 1.704 + if (pc != nullptr) 1.705 + sp->put(" "); 1.706 + if (showAll) 1.707 + sp->put("-- ----- "); 1.708 + sp->put("----- "); 1.709 + if (lines) 1.710 + sp->put("----"); 1.711 + sp->put(" --\n"); 1.712 + 1.713 + next = script->code(); 1.714 + end = script->codeEnd(); 1.715 + while (next < end) { 1.716 + if (next == script->main()) 1.717 + sp->put("main:\n"); 1.718 + if (pc != nullptr) { 1.719 + if (pc == next) 1.720 + sp->put("--> "); 1.721 + else 1.722 + sp->put(" "); 1.723 + } 1.724 + if (showAll) { 1.725 + jssrcnote *sn = js_GetSrcNote(cx, script, next); 1.726 + if (sn) { 1.727 + JS_ASSERT(!SN_IS_TERMINATOR(sn)); 1.728 + jssrcnote *next = SN_NEXT(sn); 1.729 + while (!SN_IS_TERMINATOR(next) && SN_DELTA(next) == 0) { 1.730 + Sprint(sp, "%02u\n ", SN_TYPE(sn)); 1.731 + sn = next; 1.732 + next = SN_NEXT(sn); 1.733 + } 1.734 + Sprint(sp, "%02u ", SN_TYPE(sn)); 1.735 + } 1.736 + else 1.737 + sp->put(" "); 1.738 + if (parser.isReachable(next)) 1.739 + Sprint(sp, "%05u ", parser.stackDepthAtPC(next)); 1.740 + else 1.741 + Sprint(sp, " "); 1.742 + } 1.743 + len = js_Disassemble1(cx, script, next, script->pcToOffset(next), lines, sp); 1.744 + if (!len) 1.745 + return false; 1.746 + next += len; 1.747 + } 1.748 + return true; 1.749 +} 1.750 + 1.751 +bool 1.752 +js_Disassemble(JSContext *cx, HandleScript script, bool lines, Sprinter *sp) 1.753 +{ 1.754 + return js_DisassembleAtPC(cx, script, lines, nullptr, false, sp); 1.755 +} 1.756 + 1.757 +JS_FRIEND_API(bool) 1.758 +js_DumpPC(JSContext *cx) 1.759 +{ 1.760 + js::gc::AutoSuppressGC suppressGC(cx); 1.761 + Sprinter sprinter(cx); 1.762 + if (!sprinter.init()) 1.763 + return false; 1.764 + ScriptFrameIter iter(cx); 1.765 + RootedScript script(cx, iter.script()); 1.766 + bool ok = js_DisassembleAtPC(cx, script, true, iter.pc(), false, &sprinter); 1.767 + fprintf(stdout, "%s", sprinter.string()); 1.768 + return ok; 1.769 +} 1.770 + 1.771 +JS_FRIEND_API(bool) 1.772 +js_DumpScript(JSContext *cx, JSScript *scriptArg) 1.773 +{ 1.774 + js::gc::AutoSuppressGC suppressGC(cx); 1.775 + Sprinter sprinter(cx); 1.776 + if (!sprinter.init()) 1.777 + return false; 1.778 + RootedScript script(cx, scriptArg); 1.779 + bool ok = js_Disassemble(cx, script, true, &sprinter); 1.780 + fprintf(stdout, "%s", sprinter.string()); 1.781 + return ok; 1.782 +} 1.783 + 1.784 +/* 1.785 + * Useful to debug ReconstructPCStack. 1.786 + */ 1.787 +JS_FRIEND_API(bool) 1.788 +js_DumpScriptDepth(JSContext *cx, JSScript *scriptArg, jsbytecode *pc) 1.789 +{ 1.790 + js::gc::AutoSuppressGC suppressGC(cx); 1.791 + Sprinter sprinter(cx); 1.792 + if (!sprinter.init()) 1.793 + return false; 1.794 + RootedScript script(cx, scriptArg); 1.795 + bool ok = js_DisassembleAtPC(cx, script, true, pc, true, &sprinter); 1.796 + fprintf(stdout, "%s", sprinter.string()); 1.797 + return ok; 1.798 +} 1.799 + 1.800 +static char * 1.801 +QuoteString(Sprinter *sp, JSString *str, uint32_t quote); 1.802 + 1.803 +static bool 1.804 +ToDisassemblySource(JSContext *cx, HandleValue v, JSAutoByteString *bytes) 1.805 +{ 1.806 + if (JSVAL_IS_STRING(v)) { 1.807 + Sprinter sprinter(cx); 1.808 + if (!sprinter.init()) 1.809 + return false; 1.810 + char *nbytes = QuoteString(&sprinter, JSVAL_TO_STRING(v), '"'); 1.811 + if (!nbytes) 1.812 + return false; 1.813 + nbytes = JS_sprintf_append(nullptr, "%s", nbytes); 1.814 + if (!nbytes) 1.815 + return false; 1.816 + bytes->initBytes(nbytes); 1.817 + return true; 1.818 + } 1.819 + 1.820 + if (cx->runtime()->isHeapBusy() || cx->runtime()->noGCOrAllocationCheck) { 1.821 + char *source = JS_sprintf_append(nullptr, "<value>"); 1.822 + if (!source) 1.823 + return false; 1.824 + bytes->initBytes(source); 1.825 + return true; 1.826 + } 1.827 + 1.828 + if (!JSVAL_IS_PRIMITIVE(v)) { 1.829 + JSObject *obj = JSVAL_TO_OBJECT(v); 1.830 + if (obj->is<StaticBlockObject>()) { 1.831 + Rooted<StaticBlockObject*> block(cx, &obj->as<StaticBlockObject>()); 1.832 + char *source = JS_sprintf_append(nullptr, "depth %d {", block->localOffset()); 1.833 + if (!source) 1.834 + return false; 1.835 + 1.836 + Shape::Range<CanGC> r(cx, block->lastProperty()); 1.837 + 1.838 + while (!r.empty()) { 1.839 + Rooted<Shape*> shape(cx, &r.front()); 1.840 + JSAtom *atom = JSID_IS_INT(shape->propid()) 1.841 + ? cx->names().empty 1.842 + : JSID_TO_ATOM(shape->propid()); 1.843 + 1.844 + JSAutoByteString bytes; 1.845 + if (!AtomToPrintableString(cx, atom, &bytes)) 1.846 + return false; 1.847 + 1.848 + r.popFront(); 1.849 + source = JS_sprintf_append(source, "%s: %d%s", 1.850 + bytes.ptr(), 1.851 + block->shapeToIndex(*shape), 1.852 + !r.empty() ? ", " : ""); 1.853 + if (!source) 1.854 + return false; 1.855 + } 1.856 + 1.857 + source = JS_sprintf_append(source, "}"); 1.858 + if (!source) 1.859 + return false; 1.860 + bytes->initBytes(source); 1.861 + return true; 1.862 + } 1.863 + 1.864 + if (obj->is<JSFunction>()) { 1.865 + RootedFunction fun(cx, &obj->as<JSFunction>()); 1.866 + JSString *str = JS_DecompileFunction(cx, fun, JS_DONT_PRETTY_PRINT); 1.867 + if (!str) 1.868 + return false; 1.869 + return bytes->encodeLatin1(cx, str); 1.870 + } 1.871 + 1.872 + if (obj->is<RegExpObject>()) { 1.873 + JSString *source = obj->as<RegExpObject>().toString(cx); 1.874 + if (!source) 1.875 + return false; 1.876 + JS::Anchor<JSString *> anchor(source); 1.877 + return bytes->encodeLatin1(cx, source); 1.878 + } 1.879 + } 1.880 + 1.881 + return !!js_ValueToPrintable(cx, v, bytes, true); 1.882 +} 1.883 + 1.884 +unsigned 1.885 +js_Disassemble1(JSContext *cx, HandleScript script, jsbytecode *pc, 1.886 + unsigned loc, bool lines, Sprinter *sp) 1.887 +{ 1.888 + JSOp op = (JSOp)*pc; 1.889 + if (op >= JSOP_LIMIT) { 1.890 + char numBuf1[12], numBuf2[12]; 1.891 + JS_snprintf(numBuf1, sizeof numBuf1, "%d", op); 1.892 + JS_snprintf(numBuf2, sizeof numBuf2, "%d", JSOP_LIMIT); 1.893 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, 1.894 + JSMSG_BYTECODE_TOO_BIG, numBuf1, numBuf2); 1.895 + return 0; 1.896 + } 1.897 + const JSCodeSpec *cs = &js_CodeSpec[op]; 1.898 + ptrdiff_t len = (ptrdiff_t) cs->length; 1.899 + Sprint(sp, "%05u:", loc); 1.900 + if (lines) 1.901 + Sprint(sp, "%4u", JS_PCToLineNumber(cx, script, pc)); 1.902 + Sprint(sp, " %s", js_CodeName[op]); 1.903 + 1.904 + switch (JOF_TYPE(cs->format)) { 1.905 + case JOF_BYTE: 1.906 + // Scan the trynotes to find the associated catch block 1.907 + // and make the try opcode look like a jump instruction 1.908 + // with an offset. This simplifies code coverage analysis 1.909 + // based on this disassembled output. 1.910 + if (op == JSOP_TRY) { 1.911 + TryNoteArray *trynotes = script->trynotes(); 1.912 + uint32_t i; 1.913 + for(i = 0; i < trynotes->length; i++) { 1.914 + JSTryNote note = trynotes->vector[i]; 1.915 + if (note.kind == JSTRY_CATCH && note.start == loc + 1) { 1.916 + Sprint(sp, " %u (%+d)", 1.917 + (unsigned int) (loc+note.length+1), 1.918 + (int) (note.length+1)); 1.919 + break; 1.920 + } 1.921 + } 1.922 + } 1.923 + break; 1.924 + 1.925 + case JOF_JUMP: { 1.926 + ptrdiff_t off = GET_JUMP_OFFSET(pc); 1.927 + Sprint(sp, " %u (%+d)", loc + (int) off, (int) off); 1.928 + break; 1.929 + } 1.930 + 1.931 + case JOF_SCOPECOORD: { 1.932 + RootedValue v(cx, 1.933 + StringValue(ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc))); 1.934 + JSAutoByteString bytes; 1.935 + if (!ToDisassemblySource(cx, v, &bytes)) 1.936 + return 0; 1.937 + ScopeCoordinate sc(pc); 1.938 + Sprint(sp, " %s (hops = %u, slot = %u)", bytes.ptr(), sc.hops(), sc.slot()); 1.939 + break; 1.940 + } 1.941 + 1.942 + case JOF_ATOM: { 1.943 + RootedValue v(cx, StringValue(script->getAtom(GET_UINT32_INDEX(pc)))); 1.944 + JSAutoByteString bytes; 1.945 + if (!ToDisassemblySource(cx, v, &bytes)) 1.946 + return 0; 1.947 + Sprint(sp, " %s", bytes.ptr()); 1.948 + break; 1.949 + } 1.950 + 1.951 + case JOF_DOUBLE: { 1.952 + RootedValue v(cx, script->getConst(GET_UINT32_INDEX(pc))); 1.953 + JSAutoByteString bytes; 1.954 + if (!ToDisassemblySource(cx, v, &bytes)) 1.955 + return 0; 1.956 + Sprint(sp, " %s", bytes.ptr()); 1.957 + break; 1.958 + } 1.959 + 1.960 + case JOF_OBJECT: { 1.961 + /* Don't call obj.toSource if analysis/inference is active. */ 1.962 + if (script->compartment()->activeAnalysis) { 1.963 + Sprint(sp, " object"); 1.964 + break; 1.965 + } 1.966 + 1.967 + JSObject *obj = script->getObject(GET_UINT32_INDEX(pc)); 1.968 + { 1.969 + JSAutoByteString bytes; 1.970 + RootedValue v(cx, ObjectValue(*obj)); 1.971 + if (!ToDisassemblySource(cx, v, &bytes)) 1.972 + return 0; 1.973 + Sprint(sp, " %s", bytes.ptr()); 1.974 + } 1.975 + break; 1.976 + } 1.977 + 1.978 + case JOF_REGEXP: { 1.979 + JSObject *obj = script->getRegExp(GET_UINT32_INDEX(pc)); 1.980 + JSAutoByteString bytes; 1.981 + RootedValue v(cx, ObjectValue(*obj)); 1.982 + if (!ToDisassemblySource(cx, v, &bytes)) 1.983 + return 0; 1.984 + Sprint(sp, " %s", bytes.ptr()); 1.985 + break; 1.986 + } 1.987 + 1.988 + case JOF_TABLESWITCH: 1.989 + { 1.990 + int32_t i, low, high; 1.991 + 1.992 + ptrdiff_t off = GET_JUMP_OFFSET(pc); 1.993 + jsbytecode *pc2 = pc + JUMP_OFFSET_LEN; 1.994 + low = GET_JUMP_OFFSET(pc2); 1.995 + pc2 += JUMP_OFFSET_LEN; 1.996 + high = GET_JUMP_OFFSET(pc2); 1.997 + pc2 += JUMP_OFFSET_LEN; 1.998 + Sprint(sp, " defaultOffset %d low %d high %d", int(off), low, high); 1.999 + for (i = low; i <= high; i++) { 1.1000 + off = GET_JUMP_OFFSET(pc2); 1.1001 + Sprint(sp, "\n\t%d: %d", i, int(off)); 1.1002 + pc2 += JUMP_OFFSET_LEN; 1.1003 + } 1.1004 + len = 1 + pc2 - pc; 1.1005 + break; 1.1006 + } 1.1007 + 1.1008 + case JOF_QARG: 1.1009 + Sprint(sp, " %u", GET_ARGNO(pc)); 1.1010 + break; 1.1011 + 1.1012 + case JOF_LOCAL: 1.1013 + Sprint(sp, " %u", GET_LOCALNO(pc)); 1.1014 + break; 1.1015 + 1.1016 + { 1.1017 + int i; 1.1018 + 1.1019 + case JOF_UINT16: 1.1020 + i = (int)GET_UINT16(pc); 1.1021 + goto print_int; 1.1022 + 1.1023 + case JOF_UINT24: 1.1024 + JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY || op == JSOP_INITELEM_ARRAY || 1.1025 + op == JSOP_DUPAT); 1.1026 + i = (int)GET_UINT24(pc); 1.1027 + goto print_int; 1.1028 + 1.1029 + case JOF_UINT8: 1.1030 + i = GET_UINT8(pc); 1.1031 + goto print_int; 1.1032 + 1.1033 + case JOF_INT8: 1.1034 + i = GET_INT8(pc); 1.1035 + goto print_int; 1.1036 + 1.1037 + case JOF_INT32: 1.1038 + JS_ASSERT(op == JSOP_INT32); 1.1039 + i = GET_INT32(pc); 1.1040 + print_int: 1.1041 + Sprint(sp, " %d", i); 1.1042 + break; 1.1043 + } 1.1044 + 1.1045 + default: { 1.1046 + char numBuf[12]; 1.1047 + JS_snprintf(numBuf, sizeof numBuf, "%lx", (unsigned long) cs->format); 1.1048 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, 1.1049 + JSMSG_UNKNOWN_FORMAT, numBuf); 1.1050 + return 0; 1.1051 + } 1.1052 + } 1.1053 + sp->put("\n"); 1.1054 + return len; 1.1055 +} 1.1056 + 1.1057 +#endif /* DEBUG */ 1.1058 + 1.1059 +/************************************************************************/ 1.1060 + 1.1061 +const size_t Sprinter::DefaultSize = 64; 1.1062 + 1.1063 +bool 1.1064 +Sprinter::realloc_(size_t newSize) 1.1065 +{ 1.1066 + JS_ASSERT(newSize > (size_t) offset); 1.1067 + char *newBuf = (char *) js_realloc(base, newSize); 1.1068 + if (!newBuf) { 1.1069 + reportOutOfMemory(); 1.1070 + return false; 1.1071 + } 1.1072 + base = newBuf; 1.1073 + size = newSize; 1.1074 + base[size - 1] = 0; 1.1075 + return true; 1.1076 +} 1.1077 + 1.1078 +Sprinter::Sprinter(ExclusiveContext *cx) 1.1079 + : context(cx), 1.1080 +#ifdef DEBUG 1.1081 + initialized(false), 1.1082 +#endif 1.1083 + base(nullptr), size(0), offset(0), reportedOOM(false) 1.1084 +{ } 1.1085 + 1.1086 +Sprinter::~Sprinter() 1.1087 +{ 1.1088 +#ifdef DEBUG 1.1089 + if (initialized) 1.1090 + checkInvariants(); 1.1091 +#endif 1.1092 + js_free(base); 1.1093 +} 1.1094 + 1.1095 +bool 1.1096 +Sprinter::init() 1.1097 +{ 1.1098 + JS_ASSERT(!initialized); 1.1099 + base = (char *) js_malloc(DefaultSize); 1.1100 + if (!base) { 1.1101 + reportOutOfMemory(); 1.1102 + return false; 1.1103 + } 1.1104 +#ifdef DEBUG 1.1105 + initialized = true; 1.1106 +#endif 1.1107 + *base = 0; 1.1108 + size = DefaultSize; 1.1109 + base[size - 1] = 0; 1.1110 + return true; 1.1111 +} 1.1112 + 1.1113 +void 1.1114 +Sprinter::checkInvariants() const 1.1115 +{ 1.1116 + JS_ASSERT(initialized); 1.1117 + JS_ASSERT((size_t) offset < size); 1.1118 + JS_ASSERT(base[size - 1] == 0); 1.1119 +} 1.1120 + 1.1121 +const char * 1.1122 +Sprinter::string() const 1.1123 +{ 1.1124 + return base; 1.1125 +} 1.1126 + 1.1127 +const char * 1.1128 +Sprinter::stringEnd() const 1.1129 +{ 1.1130 + return base + offset; 1.1131 +} 1.1132 + 1.1133 +char * 1.1134 +Sprinter::stringAt(ptrdiff_t off) const 1.1135 +{ 1.1136 + JS_ASSERT(off >= 0 && (size_t) off < size); 1.1137 + return base + off; 1.1138 +} 1.1139 + 1.1140 +char & 1.1141 +Sprinter::operator[](size_t off) 1.1142 +{ 1.1143 + JS_ASSERT(off < size); 1.1144 + return *(base + off); 1.1145 +} 1.1146 + 1.1147 +char * 1.1148 +Sprinter::reserve(size_t len) 1.1149 +{ 1.1150 + InvariantChecker ic(this); 1.1151 + 1.1152 + while (len + 1 > size - offset) { /* Include trailing \0 */ 1.1153 + if (!realloc_(size * 2)) 1.1154 + return nullptr; 1.1155 + } 1.1156 + 1.1157 + char *sb = base + offset; 1.1158 + offset += len; 1.1159 + return sb; 1.1160 +} 1.1161 + 1.1162 +ptrdiff_t 1.1163 +Sprinter::put(const char *s, size_t len) 1.1164 +{ 1.1165 + InvariantChecker ic(this); 1.1166 + 1.1167 + const char *oldBase = base; 1.1168 + const char *oldEnd = base + size; 1.1169 + 1.1170 + ptrdiff_t oldOffset = offset; 1.1171 + char *bp = reserve(len); 1.1172 + if (!bp) 1.1173 + return -1; 1.1174 + 1.1175 + /* s is within the buffer already */ 1.1176 + if (s >= oldBase && s < oldEnd) { 1.1177 + /* buffer was realloc'ed */ 1.1178 + if (base != oldBase) 1.1179 + s = stringAt(s - oldBase); /* this is where it lives now */ 1.1180 + memmove(bp, s, len); 1.1181 + } else { 1.1182 + js_memcpy(bp, s, len); 1.1183 + } 1.1184 + 1.1185 + bp[len] = 0; 1.1186 + return oldOffset; 1.1187 +} 1.1188 + 1.1189 +ptrdiff_t 1.1190 +Sprinter::put(const char *s) 1.1191 +{ 1.1192 + return put(s, strlen(s)); 1.1193 +} 1.1194 + 1.1195 +ptrdiff_t 1.1196 +Sprinter::putString(JSString *s) 1.1197 +{ 1.1198 + InvariantChecker ic(this); 1.1199 + 1.1200 + size_t length = s->length(); 1.1201 + const jschar *chars = s->getChars(context); 1.1202 + if (!chars) 1.1203 + return -1; 1.1204 + 1.1205 + size_t size = length; 1.1206 + if (size == (size_t) -1) 1.1207 + return -1; 1.1208 + 1.1209 + ptrdiff_t oldOffset = offset; 1.1210 + char *buffer = reserve(size); 1.1211 + if (!buffer) 1.1212 + return -1; 1.1213 + DeflateStringToBuffer(nullptr, chars, length, buffer, &size); 1.1214 + buffer[size] = 0; 1.1215 + 1.1216 + return oldOffset; 1.1217 +} 1.1218 + 1.1219 +int 1.1220 +Sprinter::printf(const char *fmt, ...) 1.1221 +{ 1.1222 + InvariantChecker ic(this); 1.1223 + 1.1224 + do { 1.1225 + va_list va; 1.1226 + va_start(va, fmt); 1.1227 + int i = vsnprintf(base + offset, size - offset, fmt, va); 1.1228 + va_end(va); 1.1229 + 1.1230 + if (i > -1 && (size_t) i < size - offset) { 1.1231 + offset += i; 1.1232 + return i; 1.1233 + } 1.1234 + } while (realloc_(size * 2)); 1.1235 + 1.1236 + return -1; 1.1237 +} 1.1238 + 1.1239 +ptrdiff_t 1.1240 +Sprinter::getOffset() const 1.1241 +{ 1.1242 + return offset; 1.1243 +} 1.1244 + 1.1245 +void 1.1246 +Sprinter::reportOutOfMemory() 1.1247 +{ 1.1248 + if (reportedOOM) 1.1249 + return; 1.1250 + if (context) 1.1251 + js_ReportOutOfMemory(context); 1.1252 + reportedOOM = true; 1.1253 +} 1.1254 + 1.1255 +bool 1.1256 +Sprinter::hadOutOfMemory() const 1.1257 +{ 1.1258 + return reportedOOM; 1.1259 +} 1.1260 + 1.1261 +ptrdiff_t 1.1262 +js::Sprint(Sprinter *sp, const char *format, ...) 1.1263 +{ 1.1264 + va_list ap; 1.1265 + char *bp; 1.1266 + ptrdiff_t offset; 1.1267 + 1.1268 + va_start(ap, format); 1.1269 + bp = JS_vsmprintf(format, ap); /* XXX vsaprintf */ 1.1270 + va_end(ap); 1.1271 + if (!bp) { 1.1272 + sp->reportOutOfMemory(); 1.1273 + return -1; 1.1274 + } 1.1275 + offset = sp->put(bp); 1.1276 + js_free(bp); 1.1277 + return offset; 1.1278 +} 1.1279 + 1.1280 +const char js_EscapeMap[] = { 1.1281 + '\b', 'b', 1.1282 + '\f', 'f', 1.1283 + '\n', 'n', 1.1284 + '\r', 'r', 1.1285 + '\t', 't', 1.1286 + '\v', 'v', 1.1287 + '"', '"', 1.1288 + '\'', '\'', 1.1289 + '\\', '\\', 1.1290 + '\0' 1.1291 +}; 1.1292 + 1.1293 +#define DONT_ESCAPE 0x10000 1.1294 + 1.1295 +static char * 1.1296 +QuoteString(Sprinter *sp, JSString *str, uint32_t quote) 1.1297 +{ 1.1298 + /* Sample off first for later return value pointer computation. */ 1.1299 + bool dontEscape = (quote & DONT_ESCAPE) != 0; 1.1300 + jschar qc = (jschar) quote; 1.1301 + ptrdiff_t offset = sp->getOffset(); 1.1302 + if (qc && Sprint(sp, "%c", (char)qc) < 0) 1.1303 + return nullptr; 1.1304 + 1.1305 + const jschar *s = str->getChars(sp->context); 1.1306 + if (!s) 1.1307 + return nullptr; 1.1308 + const jschar *z = s + str->length(); 1.1309 + 1.1310 + /* Loop control variables: z points at end of string sentinel. */ 1.1311 + for (const jschar *t = s; t < z; s = ++t) { 1.1312 + /* Move t forward from s past un-quote-worthy characters. */ 1.1313 + jschar c = *t; 1.1314 + while (c < 127 && isprint(c) && c != qc && c != '\\' && c != '\t') { 1.1315 + c = *++t; 1.1316 + if (t == z) 1.1317 + break; 1.1318 + } 1.1319 + 1.1320 + { 1.1321 + ptrdiff_t len = t - s; 1.1322 + ptrdiff_t base = sp->getOffset(); 1.1323 + char *bp = sp->reserve(len); 1.1324 + if (!bp) 1.1325 + return nullptr; 1.1326 + 1.1327 + for (ptrdiff_t i = 0; i < len; ++i) 1.1328 + (*sp)[base + i] = (char) *s++; 1.1329 + (*sp)[base + len] = 0; 1.1330 + } 1.1331 + 1.1332 + if (t == z) 1.1333 + break; 1.1334 + 1.1335 + /* Use js_EscapeMap, \u, or \x only if necessary. */ 1.1336 + bool ok; 1.1337 + const char *e; 1.1338 + if (!(c >> 8) && c != 0 && (e = strchr(js_EscapeMap, (int)c)) != nullptr) { 1.1339 + ok = dontEscape 1.1340 + ? Sprint(sp, "%c", (char)c) >= 0 1.1341 + : Sprint(sp, "\\%c", e[1]) >= 0; 1.1342 + } else { 1.1343 + /* 1.1344 + * Use \x only if the high byte is 0 and we're in a quoted string, 1.1345 + * because ECMA-262 allows only \u, not \x, in Unicode identifiers 1.1346 + * (see bug 621814). 1.1347 + */ 1.1348 + ok = Sprint(sp, (qc && !(c >> 8)) ? "\\x%02X" : "\\u%04X", c) >= 0; 1.1349 + } 1.1350 + if (!ok) 1.1351 + return nullptr; 1.1352 + } 1.1353 + 1.1354 + /* Sprint the closing quote and return the quoted string. */ 1.1355 + if (qc && Sprint(sp, "%c", (char)qc) < 0) 1.1356 + return nullptr; 1.1357 + 1.1358 + /* 1.1359 + * If we haven't Sprint'd anything yet, Sprint an empty string so that 1.1360 + * the return below gives a valid result. 1.1361 + */ 1.1362 + if (offset == sp->getOffset() && Sprint(sp, "") < 0) 1.1363 + return nullptr; 1.1364 + 1.1365 + return sp->stringAt(offset); 1.1366 +} 1.1367 + 1.1368 +JSString * 1.1369 +js_QuoteString(ExclusiveContext *cx, JSString *str, jschar quote) 1.1370 +{ 1.1371 + Sprinter sprinter(cx); 1.1372 + if (!sprinter.init()) 1.1373 + return nullptr; 1.1374 + char *bytes = QuoteString(&sprinter, str, quote); 1.1375 + if (!bytes) 1.1376 + return nullptr; 1.1377 + return js_NewStringCopyZ<CanGC>(cx, bytes); 1.1378 +} 1.1379 + 1.1380 +/************************************************************************/ 1.1381 + 1.1382 +namespace { 1.1383 +/* 1.1384 + * The expression decompiler is invoked by error handling code to produce a 1.1385 + * string representation of the erroring expression. As it's only a debugging 1.1386 + * tool, it only supports basic expressions. For anything complicated, it simply 1.1387 + * puts "(intermediate value)" into the error result. 1.1388 + * 1.1389 + * Here's the basic algorithm: 1.1390 + * 1.1391 + * 1. Find the stack location of the value whose expression we wish to 1.1392 + * decompile. The error handler can explicitly pass this as an 1.1393 + * argument. Otherwise, we search backwards down the stack for the offending 1.1394 + * value. 1.1395 + * 1.1396 + * 2. Instantiate and run a BytecodeParser for the current frame. This creates a 1.1397 + * stack of pcs parallel to the interpreter stack; given an interpreter stack 1.1398 + * location, the corresponding pc stack location contains the opcode that pushed 1.1399 + * the value in the interpreter. Now, with the result of step 1, we have the 1.1400 + * opcode responsible for pushing the value we want to decompile. 1.1401 + * 1.1402 + * 3. Pass the opcode to decompilePC. decompilePC is the main decompiler 1.1403 + * routine, responsible for a string representation of the expression that 1.1404 + * generated a certain stack location. decompilePC looks at one opcode and 1.1405 + * returns the JS source equivalent of that opcode. 1.1406 + * 1.1407 + * 4. Expressions can, of course, contain subexpressions. For example, the 1.1408 + * literals "4" and "5" are subexpressions of the addition operator in "4 + 1.1409 + * 5". If we need to decompile a subexpression, we call decompilePC (step 2) 1.1410 + * recursively on the operands' pcs. The result is a depth-first traversal of 1.1411 + * the expression tree. 1.1412 + * 1.1413 + */ 1.1414 +struct ExpressionDecompiler 1.1415 +{ 1.1416 + JSContext *cx; 1.1417 + InterpreterFrame *fp; 1.1418 + RootedScript script; 1.1419 + RootedFunction fun; 1.1420 + BindingVector *localNames; 1.1421 + BytecodeParser parser; 1.1422 + Sprinter sprinter; 1.1423 + 1.1424 + ExpressionDecompiler(JSContext *cx, JSScript *script, JSFunction *fun) 1.1425 + : cx(cx), 1.1426 + script(cx, script), 1.1427 + fun(cx, fun), 1.1428 + localNames(nullptr), 1.1429 + parser(cx, script), 1.1430 + sprinter(cx) 1.1431 + {} 1.1432 + ~ExpressionDecompiler(); 1.1433 + bool init(); 1.1434 + bool decompilePCForStackOperand(jsbytecode *pc, int i); 1.1435 + bool decompilePC(jsbytecode *pc); 1.1436 + JSAtom *getLocal(uint32_t local, jsbytecode *pc); 1.1437 + JSAtom *getArg(unsigned slot); 1.1438 + JSAtom *loadAtom(jsbytecode *pc); 1.1439 + bool quote(JSString *s, uint32_t quote); 1.1440 + bool write(const char *s); 1.1441 + bool write(JSString *str); 1.1442 + bool getOutput(char **out); 1.1443 +}; 1.1444 + 1.1445 +bool 1.1446 +ExpressionDecompiler::decompilePCForStackOperand(jsbytecode *pc, int i) 1.1447 +{ 1.1448 + pc = parser.pcForStackOperand(pc, i); 1.1449 + if (!pc) 1.1450 + return write("(intermediate value)"); 1.1451 + return decompilePC(pc); 1.1452 +} 1.1453 + 1.1454 +bool 1.1455 +ExpressionDecompiler::decompilePC(jsbytecode *pc) 1.1456 +{ 1.1457 + JS_ASSERT(script->containsPC(pc)); 1.1458 + 1.1459 + JSOp op = (JSOp)*pc; 1.1460 + 1.1461 + if (const char *token = CodeToken[op]) { 1.1462 + // Handle simple cases of binary and unary operators. 1.1463 + switch (js_CodeSpec[op].nuses) { 1.1464 + case 2: { 1.1465 + jssrcnote *sn = js_GetSrcNote(cx, script, pc); 1.1466 + if (!sn || SN_TYPE(sn) != SRC_ASSIGNOP) 1.1467 + return write("(") && 1.1468 + decompilePCForStackOperand(pc, -2) && 1.1469 + write(" ") && 1.1470 + write(token) && 1.1471 + write(" ") && 1.1472 + decompilePCForStackOperand(pc, -1) && 1.1473 + write(")"); 1.1474 + break; 1.1475 + } 1.1476 + case 1: 1.1477 + return write(token) && 1.1478 + write("(") && 1.1479 + decompilePCForStackOperand(pc, -1) && 1.1480 + write(")"); 1.1481 + default: 1.1482 + break; 1.1483 + } 1.1484 + } 1.1485 + 1.1486 + switch (op) { 1.1487 + case JSOP_GETGNAME: 1.1488 + case JSOP_NAME: 1.1489 + case JSOP_GETINTRINSIC: 1.1490 + return write(loadAtom(pc)); 1.1491 + case JSOP_GETARG: { 1.1492 + unsigned slot = GET_ARGNO(pc); 1.1493 + JSAtom *atom = getArg(slot); 1.1494 + return write(atom); 1.1495 + } 1.1496 + case JSOP_GETLOCAL: { 1.1497 + uint32_t i = GET_LOCALNO(pc); 1.1498 + if (JSAtom *atom = getLocal(i, pc)) 1.1499 + return write(atom); 1.1500 + return write("(intermediate value)"); 1.1501 + } 1.1502 + case JSOP_GETALIASEDVAR: { 1.1503 + JSAtom *atom = ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc); 1.1504 + JS_ASSERT(atom); 1.1505 + return write(atom); 1.1506 + } 1.1507 + case JSOP_LENGTH: 1.1508 + case JSOP_GETPROP: 1.1509 + case JSOP_CALLPROP: { 1.1510 + RootedAtom prop(cx, (op == JSOP_LENGTH) ? cx->names().length : loadAtom(pc)); 1.1511 + if (!decompilePCForStackOperand(pc, -1)) 1.1512 + return false; 1.1513 + if (IsIdentifier(prop)) { 1.1514 + return write(".") && 1.1515 + quote(prop, '\0'); 1.1516 + } 1.1517 + return write("[") && 1.1518 + quote(prop, '\'') && 1.1519 + write("]"); 1.1520 + } 1.1521 + case JSOP_GETELEM: 1.1522 + case JSOP_CALLELEM: 1.1523 + return decompilePCForStackOperand(pc, -2) && 1.1524 + write("[") && 1.1525 + decompilePCForStackOperand(pc, -1) && 1.1526 + write("]"); 1.1527 + case JSOP_NULL: 1.1528 + return write(js_null_str); 1.1529 + case JSOP_TRUE: 1.1530 + return write(js_true_str); 1.1531 + case JSOP_FALSE: 1.1532 + return write(js_false_str); 1.1533 + case JSOP_ZERO: 1.1534 + case JSOP_ONE: 1.1535 + case JSOP_INT8: 1.1536 + case JSOP_UINT16: 1.1537 + case JSOP_UINT24: 1.1538 + case JSOP_INT32: 1.1539 + return sprinter.printf("%d", GetBytecodeInteger(pc)) >= 0; 1.1540 + case JSOP_STRING: 1.1541 + return quote(loadAtom(pc), '"'); 1.1542 + case JSOP_UNDEFINED: 1.1543 + return write(js_undefined_str); 1.1544 + case JSOP_THIS: 1.1545 + // |this| could convert to a very long object initialiser, so cite it by 1.1546 + // its keyword name. 1.1547 + return write(js_this_str); 1.1548 + case JSOP_CALL: 1.1549 + case JSOP_FUNCALL: 1.1550 + return decompilePCForStackOperand(pc, -int32_t(GET_ARGC(pc) + 2)) && 1.1551 + write("(...)"); 1.1552 + case JSOP_SPREADCALL: 1.1553 + return decompilePCForStackOperand(pc, -int32_t(3)) && 1.1554 + write("(...)"); 1.1555 + case JSOP_NEWARRAY: 1.1556 + return write("[]"); 1.1557 + case JSOP_REGEXP: 1.1558 + case JSOP_OBJECT: { 1.1559 + JSObject *obj = (op == JSOP_REGEXP) 1.1560 + ? script->getRegExp(GET_UINT32_INDEX(pc)) 1.1561 + : script->getObject(GET_UINT32_INDEX(pc)); 1.1562 + RootedValue objv(cx, ObjectValue(*obj)); 1.1563 + JSString *str = ValueToSource(cx, objv); 1.1564 + if (!str) 1.1565 + return false; 1.1566 + return write(str); 1.1567 + } 1.1568 + default: 1.1569 + break; 1.1570 + } 1.1571 + return write("(intermediate value)"); 1.1572 +} 1.1573 + 1.1574 +ExpressionDecompiler::~ExpressionDecompiler() 1.1575 +{ 1.1576 + js_delete<BindingVector>(localNames); 1.1577 +} 1.1578 + 1.1579 +bool 1.1580 +ExpressionDecompiler::init() 1.1581 +{ 1.1582 + assertSameCompartment(cx, script); 1.1583 + 1.1584 + if (!sprinter.init()) 1.1585 + return false; 1.1586 + 1.1587 + localNames = cx->new_<BindingVector>(cx); 1.1588 + if (!localNames) 1.1589 + return false; 1.1590 + RootedScript script_(cx, script); 1.1591 + if (!FillBindingVector(script_, localNames)) 1.1592 + return false; 1.1593 + 1.1594 + if (!parser.parse()) 1.1595 + return false; 1.1596 + 1.1597 + return true; 1.1598 +} 1.1599 + 1.1600 +bool 1.1601 +ExpressionDecompiler::write(const char *s) 1.1602 +{ 1.1603 + return sprinter.put(s) >= 0; 1.1604 +} 1.1605 + 1.1606 +bool 1.1607 +ExpressionDecompiler::write(JSString *str) 1.1608 +{ 1.1609 + return sprinter.putString(str) >= 0; 1.1610 +} 1.1611 + 1.1612 +bool 1.1613 +ExpressionDecompiler::quote(JSString *s, uint32_t quote) 1.1614 +{ 1.1615 + return QuoteString(&sprinter, s, quote) >= 0; 1.1616 +} 1.1617 + 1.1618 +JSAtom * 1.1619 +ExpressionDecompiler::loadAtom(jsbytecode *pc) 1.1620 +{ 1.1621 + return script->getAtom(GET_UINT32_INDEX(pc)); 1.1622 +} 1.1623 + 1.1624 +JSAtom * 1.1625 +ExpressionDecompiler::getArg(unsigned slot) 1.1626 +{ 1.1627 + JS_ASSERT(fun); 1.1628 + JS_ASSERT(slot < script->bindings.count()); 1.1629 + return (*localNames)[slot].name(); 1.1630 +} 1.1631 + 1.1632 +JSAtom * 1.1633 +ExpressionDecompiler::getLocal(uint32_t local, jsbytecode *pc) 1.1634 +{ 1.1635 + JS_ASSERT(local < script->nfixed()); 1.1636 + if (local < script->nfixedvars()) { 1.1637 + JS_ASSERT(fun); 1.1638 + uint32_t slot = local + fun->nargs(); 1.1639 + JS_ASSERT(slot < script->bindings.count()); 1.1640 + return (*localNames)[slot].name(); 1.1641 + } 1.1642 + for (NestedScopeObject *chain = script->getStaticScope(pc); 1.1643 + chain; 1.1644 + chain = chain->enclosingNestedScope()) { 1.1645 + if (!chain->is<StaticBlockObject>()) 1.1646 + continue; 1.1647 + StaticBlockObject &block = chain->as<StaticBlockObject>(); 1.1648 + if (local < block.localOffset()) 1.1649 + continue; 1.1650 + local -= block.localOffset(); 1.1651 + if (local >= block.numVariables()) 1.1652 + return nullptr; 1.1653 + for (Shape::Range<NoGC> r(block.lastProperty()); !r.empty(); r.popFront()) { 1.1654 + const Shape &shape = r.front(); 1.1655 + if (block.shapeToIndex(shape) == local) 1.1656 + return JSID_TO_ATOM(shape.propid()); 1.1657 + } 1.1658 + break; 1.1659 + } 1.1660 + return nullptr; 1.1661 +} 1.1662 + 1.1663 +bool 1.1664 +ExpressionDecompiler::getOutput(char **res) 1.1665 +{ 1.1666 + ptrdiff_t len = sprinter.stringEnd() - sprinter.stringAt(0); 1.1667 + *res = cx->pod_malloc<char>(len + 1); 1.1668 + if (!*res) 1.1669 + return false; 1.1670 + js_memcpy(*res, sprinter.stringAt(0), len); 1.1671 + (*res)[len] = 0; 1.1672 + return true; 1.1673 +} 1.1674 + 1.1675 +} // anonymous namespace 1.1676 + 1.1677 +static bool 1.1678 +FindStartPC(JSContext *cx, const FrameIter &iter, int spindex, int skipStackHits, Value v, 1.1679 + jsbytecode **valuepc) 1.1680 +{ 1.1681 + jsbytecode *current = *valuepc; 1.1682 + 1.1683 + if (spindex == JSDVG_IGNORE_STACK) 1.1684 + return true; 1.1685 + 1.1686 + /* 1.1687 + * FIXME: Fall back if iter.isIon(), since the stack snapshot may be for the 1.1688 + * previous pc (see bug 831120). 1.1689 + */ 1.1690 + if (iter.isIon()) 1.1691 + return true; 1.1692 + 1.1693 + *valuepc = nullptr; 1.1694 + 1.1695 + BytecodeParser parser(cx, iter.script()); 1.1696 + if (!parser.parse()) 1.1697 + return false; 1.1698 + 1.1699 + if (spindex < 0 && spindex + int(parser.stackDepthAtPC(current)) < 0) 1.1700 + spindex = JSDVG_SEARCH_STACK; 1.1701 + 1.1702 + if (spindex == JSDVG_SEARCH_STACK) { 1.1703 + size_t index = iter.numFrameSlots(); 1.1704 + JS_ASSERT(index >= size_t(parser.stackDepthAtPC(current))); 1.1705 + 1.1706 + // We search from fp->sp to base to find the most recently calculated 1.1707 + // value matching v under assumption that it is the value that caused 1.1708 + // the exception. 1.1709 + int stackHits = 0; 1.1710 + Value s; 1.1711 + do { 1.1712 + if (!index) 1.1713 + return true; 1.1714 + s = iter.frameSlotValue(--index); 1.1715 + } while (s != v || stackHits++ != skipStackHits); 1.1716 + 1.1717 + // If the current PC has fewer values on the stack than the index we are 1.1718 + // looking for, the blamed value must be one pushed by the current 1.1719 + // bytecode, so restore *valuepc. 1.1720 + jsbytecode *pc = nullptr; 1.1721 + if (index < size_t(parser.stackDepthAtPC(current))) 1.1722 + pc = parser.pcForStackOperand(current, index); 1.1723 + *valuepc = pc ? pc : current; 1.1724 + } else { 1.1725 + jsbytecode *pc = parser.pcForStackOperand(current, spindex); 1.1726 + *valuepc = pc ? pc : current; 1.1727 + } 1.1728 + return true; 1.1729 +} 1.1730 + 1.1731 +static bool 1.1732 +DecompileExpressionFromStack(JSContext *cx, int spindex, int skipStackHits, HandleValue v, char **res) 1.1733 +{ 1.1734 + JS_ASSERT(spindex < 0 || 1.1735 + spindex == JSDVG_IGNORE_STACK || 1.1736 + spindex == JSDVG_SEARCH_STACK); 1.1737 + 1.1738 + *res = nullptr; 1.1739 + 1.1740 +#ifdef JS_MORE_DETERMINISTIC 1.1741 + /* 1.1742 + * Give up if we need deterministic behavior for differential testing. 1.1743 + * IonMonkey doesn't use InterpreterFrames and this ensures we get the same 1.1744 + * error messages. 1.1745 + */ 1.1746 + return true; 1.1747 +#endif 1.1748 + 1.1749 + FrameIter frameIter(cx); 1.1750 + 1.1751 + if (frameIter.done() || !frameIter.hasScript()) 1.1752 + return true; 1.1753 + 1.1754 + RootedScript script(cx, frameIter.script()); 1.1755 + AutoCompartment ac(cx, &script->global()); 1.1756 + jsbytecode *valuepc = frameIter.pc(); 1.1757 + RootedFunction fun(cx, frameIter.isFunctionFrame() 1.1758 + ? frameIter.callee() 1.1759 + : nullptr); 1.1760 + 1.1761 + JS_ASSERT(script->containsPC(valuepc)); 1.1762 + 1.1763 + // Give up if in prologue. 1.1764 + if (valuepc < script->main()) 1.1765 + return true; 1.1766 + 1.1767 + if (!FindStartPC(cx, frameIter, spindex, skipStackHits, v, &valuepc)) 1.1768 + return false; 1.1769 + if (!valuepc) 1.1770 + return true; 1.1771 + 1.1772 + ExpressionDecompiler ed(cx, script, fun); 1.1773 + if (!ed.init()) 1.1774 + return false; 1.1775 + if (!ed.decompilePC(valuepc)) 1.1776 + return false; 1.1777 + 1.1778 + return ed.getOutput(res); 1.1779 +} 1.1780 + 1.1781 +char * 1.1782 +js::DecompileValueGenerator(JSContext *cx, int spindex, HandleValue v, 1.1783 + HandleString fallbackArg, int skipStackHits) 1.1784 +{ 1.1785 + RootedString fallback(cx, fallbackArg); 1.1786 + { 1.1787 + char *result; 1.1788 + if (!DecompileExpressionFromStack(cx, spindex, skipStackHits, v, &result)) 1.1789 + return nullptr; 1.1790 + if (result) { 1.1791 + if (strcmp(result, "(intermediate value)")) 1.1792 + return result; 1.1793 + js_free(result); 1.1794 + } 1.1795 + } 1.1796 + if (!fallback) { 1.1797 + if (v.isUndefined()) 1.1798 + return JS_strdup(cx, js_undefined_str); // Prevent users from seeing "(void 0)" 1.1799 + fallback = ValueToSource(cx, v); 1.1800 + if (!fallback) 1.1801 + return nullptr; 1.1802 + } 1.1803 + 1.1804 + Rooted<JSLinearString *> linear(cx, fallback->ensureLinear(cx)); 1.1805 + if (!linear) 1.1806 + return nullptr; 1.1807 + TwoByteChars tbchars(linear->chars(), linear->length()); 1.1808 + return LossyTwoByteCharsToNewLatin1CharsZ(cx, tbchars).c_str(); 1.1809 +} 1.1810 + 1.1811 +static bool 1.1812 +DecompileArgumentFromStack(JSContext *cx, int formalIndex, char **res) 1.1813 +{ 1.1814 + JS_ASSERT(formalIndex >= 0); 1.1815 + 1.1816 + *res = nullptr; 1.1817 + 1.1818 +#ifdef JS_MORE_DETERMINISTIC 1.1819 + /* See note in DecompileExpressionFromStack. */ 1.1820 + return true; 1.1821 +#endif 1.1822 + 1.1823 + /* 1.1824 + * Settle on the nearest script frame, which should be the builtin that 1.1825 + * called the intrinsic. 1.1826 + */ 1.1827 + FrameIter frameIter(cx); 1.1828 + JS_ASSERT(!frameIter.done()); 1.1829 + 1.1830 + /* 1.1831 + * Get the second-to-top frame, the caller of the builtin that called the 1.1832 + * intrinsic. 1.1833 + */ 1.1834 + ++frameIter; 1.1835 + if (frameIter.done() || !frameIter.hasScript()) 1.1836 + return true; 1.1837 + 1.1838 + RootedScript script(cx, frameIter.script()); 1.1839 + AutoCompartment ac(cx, &script->global()); 1.1840 + jsbytecode *current = frameIter.pc(); 1.1841 + RootedFunction fun(cx, frameIter.isFunctionFrame() 1.1842 + ? frameIter.callee() 1.1843 + : nullptr); 1.1844 + 1.1845 + JS_ASSERT(script->containsPC(current)); 1.1846 + 1.1847 + if (current < script->main()) 1.1848 + return true; 1.1849 + 1.1850 + /* Don't handle getters, setters or calls from fun.call/fun.apply. */ 1.1851 + if (JSOp(*current) != JSOP_CALL || static_cast<unsigned>(formalIndex) >= GET_ARGC(current)) 1.1852 + return true; 1.1853 + 1.1854 + BytecodeParser parser(cx, script); 1.1855 + if (!parser.parse()) 1.1856 + return false; 1.1857 + 1.1858 + int formalStackIndex = parser.stackDepthAtPC(current) - GET_ARGC(current) + formalIndex; 1.1859 + JS_ASSERT(formalStackIndex >= 0); 1.1860 + if (uint32_t(formalStackIndex) >= parser.stackDepthAtPC(current)) 1.1861 + return true; 1.1862 + 1.1863 + ExpressionDecompiler ed(cx, script, fun); 1.1864 + if (!ed.init()) 1.1865 + return false; 1.1866 + if (!ed.decompilePCForStackOperand(current, formalStackIndex)) 1.1867 + return false; 1.1868 + 1.1869 + return ed.getOutput(res); 1.1870 +} 1.1871 + 1.1872 +char * 1.1873 +js::DecompileArgument(JSContext *cx, int formalIndex, HandleValue v) 1.1874 +{ 1.1875 + { 1.1876 + char *result; 1.1877 + if (!DecompileArgumentFromStack(cx, formalIndex, &result)) 1.1878 + return nullptr; 1.1879 + if (result) { 1.1880 + if (strcmp(result, "(intermediate value)")) 1.1881 + return result; 1.1882 + js_free(result); 1.1883 + } 1.1884 + } 1.1885 + if (v.isUndefined()) 1.1886 + return JS_strdup(cx, js_undefined_str); // Prevent users from seeing "(void 0)" 1.1887 + RootedString fallback(cx, ValueToSource(cx, v)); 1.1888 + if (!fallback) 1.1889 + return nullptr; 1.1890 + 1.1891 + Rooted<JSLinearString *> linear(cx, fallback->ensureLinear(cx)); 1.1892 + if (!linear) 1.1893 + return nullptr; 1.1894 + return LossyTwoByteCharsToNewLatin1CharsZ(cx, linear->range()).c_str(); 1.1895 +} 1.1896 + 1.1897 +bool 1.1898 +js::CallResultEscapes(jsbytecode *pc) 1.1899 +{ 1.1900 + /* 1.1901 + * If we see any of these sequences, the result is unused: 1.1902 + * - call / pop 1.1903 + * 1.1904 + * If we see any of these sequences, the result is only tested for nullness: 1.1905 + * - call / ifeq 1.1906 + * - call / not / ifeq 1.1907 + */ 1.1908 + 1.1909 + if (*pc == JSOP_CALL) 1.1910 + pc += JSOP_CALL_LENGTH; 1.1911 + else if (*pc == JSOP_SPREADCALL) 1.1912 + pc += JSOP_SPREADCALL_LENGTH; 1.1913 + else 1.1914 + return true; 1.1915 + 1.1916 + if (*pc == JSOP_POP) 1.1917 + return false; 1.1918 + 1.1919 + if (*pc == JSOP_NOT) 1.1920 + pc += JSOP_NOT_LENGTH; 1.1921 + 1.1922 + return *pc != JSOP_IFEQ; 1.1923 +} 1.1924 + 1.1925 +extern bool 1.1926 +js::IsValidBytecodeOffset(JSContext *cx, JSScript *script, size_t offset) 1.1927 +{ 1.1928 + // This could be faster (by following jump instructions if the target is <= offset). 1.1929 + for (BytecodeRange r(cx, script); !r.empty(); r.popFront()) { 1.1930 + size_t here = r.frontOffset(); 1.1931 + if (here >= offset) 1.1932 + return here == offset; 1.1933 + } 1.1934 + return false; 1.1935 +} 1.1936 + 1.1937 +JS_FRIEND_API(size_t) 1.1938 +js::GetPCCountScriptCount(JSContext *cx) 1.1939 +{ 1.1940 + JSRuntime *rt = cx->runtime(); 1.1941 + 1.1942 + if (!rt->scriptAndCountsVector) 1.1943 + return 0; 1.1944 + 1.1945 + return rt->scriptAndCountsVector->length(); 1.1946 +} 1.1947 + 1.1948 +enum MaybeComma {NO_COMMA, COMMA}; 1.1949 + 1.1950 +static void 1.1951 +AppendJSONProperty(StringBuffer &buf, const char *name, MaybeComma comma = COMMA) 1.1952 +{ 1.1953 + if (comma) 1.1954 + buf.append(','); 1.1955 + 1.1956 + buf.append('\"'); 1.1957 + buf.appendInflated(name, strlen(name)); 1.1958 + buf.appendInflated("\":", 2); 1.1959 +} 1.1960 + 1.1961 +static void 1.1962 +AppendArrayJSONProperties(JSContext *cx, StringBuffer &buf, 1.1963 + double *values, const char * const *names, unsigned count, 1.1964 + MaybeComma &comma) 1.1965 +{ 1.1966 + for (unsigned i = 0; i < count; i++) { 1.1967 + if (values[i]) { 1.1968 + AppendJSONProperty(buf, names[i], comma); 1.1969 + comma = COMMA; 1.1970 + NumberValueToStringBuffer(cx, DoubleValue(values[i]), buf); 1.1971 + } 1.1972 + } 1.1973 +} 1.1974 + 1.1975 +JS_FRIEND_API(JSString *) 1.1976 +js::GetPCCountScriptSummary(JSContext *cx, size_t index) 1.1977 +{ 1.1978 + JSRuntime *rt = cx->runtime(); 1.1979 + 1.1980 + if (!rt->scriptAndCountsVector || index >= rt->scriptAndCountsVector->length()) { 1.1981 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL); 1.1982 + return nullptr; 1.1983 + } 1.1984 + 1.1985 + const ScriptAndCounts &sac = (*rt->scriptAndCountsVector)[index]; 1.1986 + RootedScript script(cx, sac.script); 1.1987 + 1.1988 + /* 1.1989 + * OOM on buffer appends here will not be caught immediately, but since 1.1990 + * StringBuffer uses a ContextAllocPolicy will trigger an exception on the 1.1991 + * context if they occur, which we'll catch before returning. 1.1992 + */ 1.1993 + StringBuffer buf(cx); 1.1994 + 1.1995 + buf.append('{'); 1.1996 + 1.1997 + AppendJSONProperty(buf, "file", NO_COMMA); 1.1998 + JSString *str = JS_NewStringCopyZ(cx, script->filename()); 1.1999 + if (!str || !(str = StringToSource(cx, str))) 1.2000 + return nullptr; 1.2001 + buf.append(str); 1.2002 + 1.2003 + AppendJSONProperty(buf, "line"); 1.2004 + NumberValueToStringBuffer(cx, Int32Value(script->lineno()), buf); 1.2005 + 1.2006 + if (script->functionNonDelazifying()) { 1.2007 + JSAtom *atom = script->functionNonDelazifying()->displayAtom(); 1.2008 + if (atom) { 1.2009 + AppendJSONProperty(buf, "name"); 1.2010 + if (!(str = StringToSource(cx, atom))) 1.2011 + return nullptr; 1.2012 + buf.append(str); 1.2013 + } 1.2014 + } 1.2015 + 1.2016 + double baseTotals[PCCounts::BASE_LIMIT] = {0.0}; 1.2017 + double accessTotals[PCCounts::ACCESS_LIMIT - PCCounts::BASE_LIMIT] = {0.0}; 1.2018 + double elementTotals[PCCounts::ELEM_LIMIT - PCCounts::ACCESS_LIMIT] = {0.0}; 1.2019 + double propertyTotals[PCCounts::PROP_LIMIT - PCCounts::ACCESS_LIMIT] = {0.0}; 1.2020 + double arithTotals[PCCounts::ARITH_LIMIT - PCCounts::BASE_LIMIT] = {0.0}; 1.2021 + 1.2022 + for (unsigned i = 0; i < script->length(); i++) { 1.2023 + PCCounts &counts = sac.getPCCounts(script->offsetToPC(i)); 1.2024 + if (!counts) 1.2025 + continue; 1.2026 + 1.2027 + JSOp op = (JSOp)script->code()[i]; 1.2028 + unsigned numCounts = PCCounts::numCounts(op); 1.2029 + 1.2030 + for (unsigned j = 0; j < numCounts; j++) { 1.2031 + double value = counts.get(j); 1.2032 + if (j < PCCounts::BASE_LIMIT) { 1.2033 + baseTotals[j] += value; 1.2034 + } else if (PCCounts::accessOp(op)) { 1.2035 + if (j < PCCounts::ACCESS_LIMIT) 1.2036 + accessTotals[j - PCCounts::BASE_LIMIT] += value; 1.2037 + else if (PCCounts::elementOp(op)) 1.2038 + elementTotals[j - PCCounts::ACCESS_LIMIT] += value; 1.2039 + else if (PCCounts::propertyOp(op)) 1.2040 + propertyTotals[j - PCCounts::ACCESS_LIMIT] += value; 1.2041 + else 1.2042 + MOZ_ASSUME_UNREACHABLE("Bad opcode"); 1.2043 + } else if (PCCounts::arithOp(op)) { 1.2044 + arithTotals[j - PCCounts::BASE_LIMIT] += value; 1.2045 + } else { 1.2046 + MOZ_ASSUME_UNREACHABLE("Bad opcode"); 1.2047 + } 1.2048 + } 1.2049 + } 1.2050 + 1.2051 + AppendJSONProperty(buf, "totals"); 1.2052 + buf.append('{'); 1.2053 + 1.2054 + MaybeComma comma = NO_COMMA; 1.2055 + 1.2056 + AppendArrayJSONProperties(cx, buf, baseTotals, countBaseNames, 1.2057 + JS_ARRAY_LENGTH(baseTotals), comma); 1.2058 + AppendArrayJSONProperties(cx, buf, accessTotals, countAccessNames, 1.2059 + JS_ARRAY_LENGTH(accessTotals), comma); 1.2060 + AppendArrayJSONProperties(cx, buf, elementTotals, countElementNames, 1.2061 + JS_ARRAY_LENGTH(elementTotals), comma); 1.2062 + AppendArrayJSONProperties(cx, buf, propertyTotals, countPropertyNames, 1.2063 + JS_ARRAY_LENGTH(propertyTotals), comma); 1.2064 + AppendArrayJSONProperties(cx, buf, arithTotals, countArithNames, 1.2065 + JS_ARRAY_LENGTH(arithTotals), comma); 1.2066 + 1.2067 + uint64_t ionActivity = 0; 1.2068 + jit::IonScriptCounts *ionCounts = sac.getIonCounts(); 1.2069 + while (ionCounts) { 1.2070 + for (size_t i = 0; i < ionCounts->numBlocks(); i++) 1.2071 + ionActivity += ionCounts->block(i).hitCount(); 1.2072 + ionCounts = ionCounts->previous(); 1.2073 + } 1.2074 + if (ionActivity) { 1.2075 + AppendJSONProperty(buf, "ion", comma); 1.2076 + NumberValueToStringBuffer(cx, DoubleValue(ionActivity), buf); 1.2077 + } 1.2078 + 1.2079 + buf.append('}'); 1.2080 + buf.append('}'); 1.2081 + 1.2082 + if (cx->isExceptionPending()) 1.2083 + return nullptr; 1.2084 + 1.2085 + return buf.finishString(); 1.2086 +} 1.2087 + 1.2088 +static bool 1.2089 +GetPCCountJSON(JSContext *cx, const ScriptAndCounts &sac, StringBuffer &buf) 1.2090 +{ 1.2091 + RootedScript script(cx, sac.script); 1.2092 + 1.2093 + buf.append('{'); 1.2094 + AppendJSONProperty(buf, "text", NO_COMMA); 1.2095 + 1.2096 + JSString *str = JS_DecompileScript(cx, script, nullptr, 0); 1.2097 + if (!str || !(str = StringToSource(cx, str))) 1.2098 + return false; 1.2099 + 1.2100 + buf.append(str); 1.2101 + 1.2102 + AppendJSONProperty(buf, "line"); 1.2103 + NumberValueToStringBuffer(cx, Int32Value(script->lineno()), buf); 1.2104 + 1.2105 + AppendJSONProperty(buf, "opcodes"); 1.2106 + buf.append('['); 1.2107 + bool comma = false; 1.2108 + 1.2109 + SrcNoteLineScanner scanner(script->notes(), script->lineno()); 1.2110 + 1.2111 + for (jsbytecode *pc = script->code(); pc < script->codeEnd(); pc += GetBytecodeLength(pc)) { 1.2112 + size_t offset = script->pcToOffset(pc); 1.2113 + 1.2114 + JSOp op = (JSOp) *pc; 1.2115 + 1.2116 + if (comma) 1.2117 + buf.append(','); 1.2118 + comma = true; 1.2119 + 1.2120 + buf.append('{'); 1.2121 + 1.2122 + AppendJSONProperty(buf, "id", NO_COMMA); 1.2123 + NumberValueToStringBuffer(cx, Int32Value(offset), buf); 1.2124 + 1.2125 + scanner.advanceTo(offset); 1.2126 + 1.2127 + AppendJSONProperty(buf, "line"); 1.2128 + NumberValueToStringBuffer(cx, Int32Value(scanner.getLine()), buf); 1.2129 + 1.2130 + { 1.2131 + const char *name = js_CodeName[op]; 1.2132 + AppendJSONProperty(buf, "name"); 1.2133 + buf.append('\"'); 1.2134 + buf.appendInflated(name, strlen(name)); 1.2135 + buf.append('\"'); 1.2136 + } 1.2137 + 1.2138 + { 1.2139 + ExpressionDecompiler ed(cx, script, script->functionDelazifying()); 1.2140 + if (!ed.init()) 1.2141 + return false; 1.2142 + if (!ed.decompilePC(pc)) 1.2143 + return false; 1.2144 + char *text; 1.2145 + if (!ed.getOutput(&text)) 1.2146 + return false; 1.2147 + AppendJSONProperty(buf, "text"); 1.2148 + JSString *str = JS_NewStringCopyZ(cx, text); 1.2149 + js_free(text); 1.2150 + if (!str || !(str = StringToSource(cx, str))) 1.2151 + return false; 1.2152 + buf.append(str); 1.2153 + } 1.2154 + 1.2155 + PCCounts &counts = sac.getPCCounts(pc); 1.2156 + unsigned numCounts = PCCounts::numCounts(op); 1.2157 + 1.2158 + AppendJSONProperty(buf, "counts"); 1.2159 + buf.append('{'); 1.2160 + 1.2161 + MaybeComma comma = NO_COMMA; 1.2162 + for (unsigned i = 0; i < numCounts; i++) { 1.2163 + double value = counts.get(i); 1.2164 + if (value > 0) { 1.2165 + AppendJSONProperty(buf, PCCounts::countName(op, i), comma); 1.2166 + comma = COMMA; 1.2167 + NumberValueToStringBuffer(cx, DoubleValue(value), buf); 1.2168 + } 1.2169 + } 1.2170 + 1.2171 + buf.append('}'); 1.2172 + buf.append('}'); 1.2173 + } 1.2174 + 1.2175 + buf.append(']'); 1.2176 + 1.2177 + jit::IonScriptCounts *ionCounts = sac.getIonCounts(); 1.2178 + if (ionCounts) { 1.2179 + AppendJSONProperty(buf, "ion"); 1.2180 + buf.append('['); 1.2181 + bool comma = false; 1.2182 + while (ionCounts) { 1.2183 + if (comma) 1.2184 + buf.append(','); 1.2185 + comma = true; 1.2186 + 1.2187 + buf.append('['); 1.2188 + for (size_t i = 0; i < ionCounts->numBlocks(); i++) { 1.2189 + if (i) 1.2190 + buf.append(','); 1.2191 + const jit::IonBlockCounts &block = ionCounts->block(i); 1.2192 + 1.2193 + buf.append('{'); 1.2194 + AppendJSONProperty(buf, "id", NO_COMMA); 1.2195 + NumberValueToStringBuffer(cx, Int32Value(block.id()), buf); 1.2196 + AppendJSONProperty(buf, "offset"); 1.2197 + NumberValueToStringBuffer(cx, Int32Value(block.offset()), buf); 1.2198 + AppendJSONProperty(buf, "successors"); 1.2199 + buf.append('['); 1.2200 + for (size_t j = 0; j < block.numSuccessors(); j++) { 1.2201 + if (j) 1.2202 + buf.append(','); 1.2203 + NumberValueToStringBuffer(cx, Int32Value(block.successor(j)), buf); 1.2204 + } 1.2205 + buf.append(']'); 1.2206 + AppendJSONProperty(buf, "hits"); 1.2207 + NumberValueToStringBuffer(cx, DoubleValue(block.hitCount()), buf); 1.2208 + 1.2209 + AppendJSONProperty(buf, "code"); 1.2210 + JSString *str = JS_NewStringCopyZ(cx, block.code()); 1.2211 + if (!str || !(str = StringToSource(cx, str))) 1.2212 + return false; 1.2213 + buf.append(str); 1.2214 + buf.append('}'); 1.2215 + } 1.2216 + buf.append(']'); 1.2217 + 1.2218 + ionCounts = ionCounts->previous(); 1.2219 + } 1.2220 + buf.append(']'); 1.2221 + } 1.2222 + 1.2223 + buf.append('}'); 1.2224 + 1.2225 + return !cx->isExceptionPending(); 1.2226 +} 1.2227 + 1.2228 +JS_FRIEND_API(JSString *) 1.2229 +js::GetPCCountScriptContents(JSContext *cx, size_t index) 1.2230 +{ 1.2231 + JSRuntime *rt = cx->runtime(); 1.2232 + 1.2233 + if (!rt->scriptAndCountsVector || index >= rt->scriptAndCountsVector->length()) { 1.2234 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL); 1.2235 + return nullptr; 1.2236 + } 1.2237 + 1.2238 + const ScriptAndCounts &sac = (*rt->scriptAndCountsVector)[index]; 1.2239 + JSScript *script = sac.script; 1.2240 + 1.2241 + StringBuffer buf(cx); 1.2242 + 1.2243 + if (!script->functionNonDelazifying() && !script->compileAndGo()) 1.2244 + return buf.finishString(); 1.2245 + 1.2246 + { 1.2247 + AutoCompartment ac(cx, &script->global()); 1.2248 + if (!GetPCCountJSON(cx, sac, buf)) 1.2249 + return nullptr; 1.2250 + } 1.2251 + 1.2252 + return buf.finishString(); 1.2253 +}