2010-10-26 12:00:54 -07:00
|
|
|
/* -*- Mode: c++; c-basic-offset: 4; tab-width: 40; indent-tabs-mode: nil -*- */
|
|
|
|
/* vim: set ts=40 sw=4 et tw=99: */
|
|
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
|
|
*
|
|
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
|
|
* the License. You may obtain a copy of the License at
|
|
|
|
* http://www.mozilla.org/MPL/
|
|
|
|
*
|
|
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
|
|
* for the specific language governing rights and limitations under the
|
|
|
|
* License.
|
|
|
|
*
|
|
|
|
* The Original Code is the Mozilla SpiderMonkey bytecode analysis
|
|
|
|
*
|
|
|
|
* The Initial Developer of the Original Code is
|
|
|
|
* Mozilla Foundation
|
|
|
|
* Portions created by the Initial Developer are Copyright (C) 2010
|
|
|
|
* the Initial Developer. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Contributor(s):
|
|
|
|
* Brian Hackett <bhackett@mozilla.com>
|
|
|
|
*
|
|
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
|
|
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
|
|
|
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
|
|
* the provisions above, a recipient may use your version of this file under
|
|
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
|
|
*
|
|
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
|
|
|
|
#include "jsanalyze.h"
|
|
|
|
#include "jsautooplen.h"
|
|
|
|
#include "jscompartment.h"
|
|
|
|
#include "jscntxt.h"
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
#include "jsinferinlines.h"
|
|
|
|
|
2010-10-26 12:00:54 -07:00
|
|
|
namespace js {
|
|
|
|
namespace analyze {
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
// Script
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
void
|
|
|
|
Script::destroy()
|
|
|
|
{
|
|
|
|
JS_FinishArenaPool(&pool);
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
// Bytecode
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
bool
|
2010-10-29 08:05:55 -07:00
|
|
|
Bytecode::mergeDefines(JSContext *cx, Script *script, bool initial,
|
|
|
|
unsigned newDepth, types::TypeStack *newStack,
|
2010-10-26 12:00:54 -07:00
|
|
|
uint32 *newArray, unsigned newCount)
|
|
|
|
{
|
|
|
|
if (initial) {
|
|
|
|
/*
|
|
|
|
* Haven't handled any incoming edges to this bytecode before.
|
|
|
|
* Define arrays are copy on write, so just reuse the array for this bytecode.
|
|
|
|
*/
|
|
|
|
stackDepth = newDepth;
|
|
|
|
defineArray = newArray;
|
|
|
|
defineCount = newCount;
|
2010-10-29 08:05:55 -07:00
|
|
|
#ifdef JS_TYPE_INFERENCE
|
|
|
|
inStack = newStack;
|
|
|
|
#endif
|
2010-10-26 12:00:54 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
#ifdef JS_TYPE_INFERENCE
|
|
|
|
types::TypeStack::merge(cx, newStack, inStack);
|
|
|
|
#endif
|
|
|
|
|
2010-10-26 12:00:54 -07:00
|
|
|
/*
|
|
|
|
* This bytecode has multiple incoming edges, intersect the new array with any
|
|
|
|
* variables known to be defined along other incoming edges.
|
|
|
|
*/
|
|
|
|
if (analyzed) {
|
|
|
|
#ifdef DEBUG
|
|
|
|
/*
|
|
|
|
* Once analyzed, a bytecode has its full set of definitions. There are two
|
|
|
|
* properties we depend on to ensure this. First, bytecode for a function
|
|
|
|
* is emitted in topological order, and since we analyze bytecodes in the
|
|
|
|
* order they were emitted we will have seen all incoming jumps except
|
|
|
|
* for any loop back edges. Second, javascript has structured control flow,
|
|
|
|
* so loop heads dominate their bodies; the set of variables defined
|
|
|
|
* on a back edge will be at least as large as at the head of the loop,
|
|
|
|
* so no intersection or further analysis needs to be done.
|
|
|
|
*/
|
|
|
|
JS_ASSERT(stackDepth == newDepth);
|
|
|
|
for (unsigned i = 0; i < defineCount; i++) {
|
|
|
|
bool found = false;
|
|
|
|
for (unsigned j = 0; j < newCount; j++) {
|
|
|
|
if (newArray[j] == defineArray[i])
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
JS_ASSERT(found);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
} else {
|
|
|
|
JS_ASSERT(stackDepth == newDepth);
|
|
|
|
bool owned = false;
|
|
|
|
for (unsigned i = 0; i < defineCount; i++) {
|
|
|
|
bool found = false;
|
|
|
|
for (unsigned j = 0; j < newCount; j++) {
|
|
|
|
if (newArray[j] == defineArray[i])
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
if (!found) {
|
|
|
|
/*
|
|
|
|
* Get a mutable copy of the defines. This can end up making
|
|
|
|
* several copies for a bytecode if it has many incoming edges
|
|
|
|
* with progressively smaller sets of defined variables.
|
|
|
|
*/
|
|
|
|
if (!owned) {
|
|
|
|
uint32 *reallocArray = ArenaArray<uint32>(script->pool, defineCount);
|
|
|
|
if (!reallocArray) {
|
|
|
|
script->setOOM(cx);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
memcpy(reallocArray, defineArray, defineCount * sizeof(uint32));
|
|
|
|
defineArray = reallocArray;
|
|
|
|
owned = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Swap with the last element and pop the array. */
|
|
|
|
defineArray[i--] = defineArray[--defineCount];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
// Analysis
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
inline bool
|
|
|
|
Script::addJump(JSContext *cx, unsigned offset,
|
|
|
|
unsigned *currentOffset, unsigned *forwardJump,
|
2010-10-29 08:05:55 -07:00
|
|
|
unsigned stackDepth, types::TypeStack *stack,
|
|
|
|
uint32 *defineArray, unsigned defineCount)
|
2010-10-26 12:00:54 -07:00
|
|
|
{
|
|
|
|
JS_ASSERT(offset < script->length);
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
Bytecode *&code = codeArray[offset];
|
|
|
|
bool initial = (code == NULL);
|
2010-10-26 12:00:54 -07:00
|
|
|
if (initial) {
|
2010-10-29 08:05:55 -07:00
|
|
|
code = ArenaNew<Bytecode>(pool, this, offset);
|
|
|
|
if (!code) {
|
2010-10-26 12:00:54 -07:00
|
|
|
setOOM(cx);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
if (!code->mergeDefines(cx, this, initial, stackDepth, stack, defineArray, defineCount))
|
2010-10-26 12:00:54 -07:00
|
|
|
return false;
|
2010-10-29 08:05:55 -07:00
|
|
|
code->jumpTarget = true;
|
2010-10-26 12:00:54 -07:00
|
|
|
|
|
|
|
if (offset < *currentOffset) {
|
|
|
|
/* Don't follow back edges to bytecode which has already been analyzed. */
|
2010-10-29 08:05:55 -07:00
|
|
|
if (!code->analyzed) {
|
2010-10-26 12:00:54 -07:00
|
|
|
if (*forwardJump == 0)
|
|
|
|
*forwardJump = *currentOffset;
|
|
|
|
*currentOffset = offset;
|
|
|
|
}
|
|
|
|
} else if (offset > *forwardJump) {
|
|
|
|
*forwardJump = offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void
|
|
|
|
Script::setLocal(uint32 local, uint32 offset)
|
|
|
|
{
|
|
|
|
JS_ASSERT(local < localCount());
|
|
|
|
JS_ASSERT(offset != LOCAL_CONDITIONALLY_DEFINED);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* It isn't possible to change the point when a variable becomes unconditionally
|
|
|
|
* defined, or to mark it as unconditionally defined after it has already been
|
|
|
|
* marked as having a use before def. It *is* possible to mark it as having
|
|
|
|
* a use before def after marking it as unconditionally defined. In a loop such as:
|
|
|
|
*
|
|
|
|
* while ((a = b) != 0) { x = a; }
|
|
|
|
*
|
|
|
|
* When walking through the body of this loop, we will first analyze the test
|
|
|
|
* (which comes after the body in the bytecode stream) as unconditional code,
|
|
|
|
* and mark a as definitely defined. a is not in the define array when taking
|
|
|
|
* the loop's back edge, so it is treated as possibly undefined when written to x.
|
|
|
|
*/
|
|
|
|
JS_ASSERT(locals[local] == LOCAL_CONDITIONALLY_DEFINED ||
|
|
|
|
locals[local] == offset || offset == LOCAL_USE_BEFORE_DEF);
|
|
|
|
|
|
|
|
locals[local] = offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline ptrdiff_t
|
|
|
|
GetJumpOffset(jsbytecode *pc, jsbytecode *pc2)
|
|
|
|
{
|
|
|
|
uint32 type = JOF_OPTYPE(*pc);
|
|
|
|
if (JOF_TYPE_IS_EXTENDED_JUMP(type))
|
|
|
|
return GET_JUMPX_OFFSET(pc2);
|
|
|
|
return GET_JUMP_OFFSET(pc2);
|
|
|
|
}
|
|
|
|
|
|
|
|
// return whether op bytecodes do not fallthrough (they may do a jump).
|
|
|
|
static inline bool
|
|
|
|
BytecodeNoFallThrough(JSOp op)
|
|
|
|
{
|
|
|
|
switch (op) {
|
|
|
|
case JSOP_GOTO:
|
|
|
|
case JSOP_GOTOX:
|
|
|
|
case JSOP_DEFAULT:
|
|
|
|
case JSOP_DEFAULTX:
|
|
|
|
case JSOP_RETURN:
|
|
|
|
case JSOP_STOP:
|
|
|
|
case JSOP_RETRVAL:
|
|
|
|
case JSOP_THROW:
|
|
|
|
case JSOP_TABLESWITCH:
|
|
|
|
case JSOP_TABLESWITCHX:
|
|
|
|
case JSOP_LOOKUPSWITCH:
|
|
|
|
case JSOP_LOOKUPSWITCHX:
|
|
|
|
case JSOP_FILTER:
|
|
|
|
return true;
|
|
|
|
case JSOP_GOSUB:
|
|
|
|
case JSOP_GOSUBX:
|
|
|
|
// these fall through indirectly, after executing a 'finally'.
|
|
|
|
return false;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Untrap a single PC, and retrap it at scope exit. */
|
|
|
|
struct UntrapOpcode
|
|
|
|
{
|
|
|
|
jsbytecode *pc;
|
|
|
|
bool trap;
|
|
|
|
|
|
|
|
UntrapOpcode(JSContext *cx, JSScript *script, jsbytecode *pc)
|
|
|
|
: pc(pc), trap(JSOp(*pc) == JSOP_TRAP)
|
|
|
|
{
|
|
|
|
if (trap)
|
|
|
|
*pc = JS_GetTrapOpcode(cx, script, pc);
|
|
|
|
}
|
|
|
|
|
|
|
|
~UntrapOpcode()
|
|
|
|
{
|
|
|
|
if (trap)
|
|
|
|
*pc = JSOP_TRAP;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
#ifdef JS_TYPE_INFERENCE
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Information about a currently active static initializer. We keep the stack
|
|
|
|
* of initializers around during analysis so we can reuse objects when
|
|
|
|
* processing arrays of arrays or arrays of objects.
|
|
|
|
*/
|
|
|
|
struct InitializerInfo
|
|
|
|
{
|
|
|
|
/* Object being initialized. */
|
|
|
|
types::TypeObject *object;
|
|
|
|
|
|
|
|
/* Whether this object is an array. */
|
|
|
|
bool isArray;
|
|
|
|
|
|
|
|
/* Any object to use for initializers appearing in the array's elements. */
|
|
|
|
types::TypeObject *initObject;
|
|
|
|
|
|
|
|
/* Whether initObject is an array vs. object. */
|
|
|
|
bool initArray;
|
|
|
|
|
|
|
|
/* Outer initializer, the one this initializer is nested in. */
|
|
|
|
InitializerInfo *outer;
|
|
|
|
|
|
|
|
InitializerInfo() { PodZero(this); }
|
|
|
|
};
|
|
|
|
|
|
|
|
#endif /* JS_TYPE_INFERENCE */
|
|
|
|
|
2010-10-26 12:00:54 -07:00
|
|
|
void
|
2010-10-29 08:05:55 -07:00
|
|
|
Script::init(JSScript *script)
|
2010-10-26 12:00:54 -07:00
|
|
|
{
|
|
|
|
this->script = script;
|
|
|
|
JS_InitArenaPool(&pool, "script_analyze", 256, 8, NULL);
|
2010-10-29 08:05:55 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Script::analyze(JSContext *cx)
|
|
|
|
{
|
|
|
|
JS_ASSERT(script && !codeArray && !locals);
|
2010-10-26 12:00:54 -07:00
|
|
|
|
|
|
|
unsigned length = script->length;
|
|
|
|
unsigned nfixed = localCount();
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
codeArray = ArenaArray<Bytecode*>(pool, length);
|
2010-10-26 12:00:54 -07:00
|
|
|
locals = ArenaArray<uint32>(pool, nfixed);
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
if (!codeArray || !locals) {
|
2010-10-26 12:00:54 -07:00
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
PodZero(codeArray, length);
|
2010-10-26 12:00:54 -07:00
|
|
|
|
|
|
|
for (unsigned i = 0; i < nfixed; i++)
|
|
|
|
locals[i] = LOCAL_CONDITIONALLY_DEFINED;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Treat locals as having a possible use-before-def if they could be accessed
|
|
|
|
* by debug code or by eval, or if they could be accessed by an inner script.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (script->usesEval || cx->compartment->debugMode) {
|
|
|
|
for (uint32 i = 0; i < nfixed; i++)
|
|
|
|
setLocal(i, LOCAL_USE_BEFORE_DEF);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (uint32 i = 0; i < script->nClosedVars; i++) {
|
|
|
|
uint32 slot = script->getClosedVar(i);
|
|
|
|
if (slot < nfixed)
|
|
|
|
setLocal(slot, LOCAL_USE_BEFORE_DEF);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we are in the middle of one or more jumps, the offset of the highest
|
|
|
|
* target jumping over this bytecode. Includes implicit jumps from
|
|
|
|
* try/catch/finally blocks.
|
|
|
|
*/
|
|
|
|
unsigned forwardJump = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we are in the middle of a try block, the offset of the highest
|
|
|
|
* catch/finally/enditer.
|
|
|
|
*/
|
|
|
|
unsigned forwardCatch = 0;
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
#ifdef JS_TYPE_INFERENCE
|
|
|
|
/* Stack of active initializers. */
|
|
|
|
InitializerInfo *initializerStack = NULL;
|
|
|
|
#endif
|
|
|
|
|
2010-10-26 12:00:54 -07:00
|
|
|
/* Fill in stack depth and definitions at initial bytecode. */
|
2010-10-29 08:05:55 -07:00
|
|
|
Bytecode *startcode = ArenaNew<Bytecode>(pool, this, 0);
|
2010-10-26 12:00:54 -07:00
|
|
|
if (!startcode) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
startcode->stackDepth = 0;
|
2010-10-29 08:05:55 -07:00
|
|
|
codeArray[0] = startcode;
|
2010-10-26 12:00:54 -07:00
|
|
|
|
|
|
|
unsigned offset, nextOffset = 0;
|
|
|
|
while (nextOffset < length) {
|
|
|
|
offset = nextOffset;
|
|
|
|
|
|
|
|
JS_ASSERT(forwardCatch <= forwardJump);
|
|
|
|
|
|
|
|
/* Check if the current forward jump/try-block has finished. */
|
|
|
|
if (forwardJump && forwardJump == offset)
|
|
|
|
forwardJump = 0;
|
|
|
|
if (forwardCatch && forwardCatch == offset)
|
|
|
|
forwardCatch = 0;
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
Bytecode *code = maybeCode(offset);
|
2010-10-26 12:00:54 -07:00
|
|
|
jsbytecode *pc = script->code + offset;
|
|
|
|
|
|
|
|
UntrapOpcode untrap(cx, script, pc);
|
|
|
|
|
|
|
|
JSOp op = (JSOp)*pc;
|
|
|
|
JS_ASSERT(op < JSOP_LIMIT);
|
|
|
|
|
|
|
|
/* Immediate successor of this bytecode. */
|
|
|
|
unsigned successorOffset = offset + GetBytecodeLength(pc);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Next bytecode to analyze. This is either the successor, or is an
|
|
|
|
* earlier bytecode if this bytecode has a loop backedge.
|
|
|
|
*/
|
|
|
|
nextOffset = successorOffset;
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
if (!code) {
|
2010-10-26 12:00:54 -07:00
|
|
|
/* Haven't found a path by which this bytecode is reachable. */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
if (code->analyzed) {
|
2010-10-26 12:00:54 -07:00
|
|
|
/* No need to reanalyze, see Bytecode::mergeDefines. */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
code->analyzed = true;
|
2010-10-26 12:00:54 -07:00
|
|
|
|
|
|
|
if (forwardCatch)
|
2010-10-29 08:05:55 -07:00
|
|
|
code->inTryBlock = true;
|
2010-10-26 12:00:54 -07:00
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
unsigned stackDepth = code->stackDepth;
|
|
|
|
uint32 *defineArray = code->defineArray;
|
|
|
|
unsigned defineCount = code->defineCount;
|
2010-10-26 12:00:54 -07:00
|
|
|
|
|
|
|
if (!forwardJump) {
|
|
|
|
/*
|
|
|
|
* There is no jump over this bytecode, nor a containing try block.
|
|
|
|
* Either this bytecode will definitely be executed, or an exception
|
|
|
|
* will be thrown which the script does not catch. Either way,
|
|
|
|
* any variables definitely defined at this bytecode will stay
|
|
|
|
* defined throughout the rest of the script. We just need to
|
|
|
|
* remember the offset where the variable became unconditionally
|
|
|
|
* defined, rather than continue to maintain it in define arrays.
|
|
|
|
*/
|
|
|
|
for (unsigned i = 0; i < defineCount; i++) {
|
|
|
|
uint32 local = defineArray[i];
|
|
|
|
JS_ASSERT_IF(locals[local] != LOCAL_CONDITIONALLY_DEFINED &&
|
|
|
|
locals[local] != LOCAL_USE_BEFORE_DEF,
|
|
|
|
locals[local] <= offset);
|
|
|
|
if (locals[local] == LOCAL_CONDITIONALLY_DEFINED)
|
|
|
|
setLocal(local, offset);
|
|
|
|
}
|
|
|
|
defineArray = NULL;
|
|
|
|
defineCount = 0;
|
|
|
|
}
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
unsigned nuses = GetUseCount(script, offset);
|
|
|
|
unsigned ndefs = GetDefCount(script, offset);
|
2010-10-26 12:00:54 -07:00
|
|
|
|
|
|
|
JS_ASSERT(stackDepth >= nuses);
|
|
|
|
stackDepth -= nuses;
|
|
|
|
stackDepth += ndefs;
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
types::TypeStack *stack = NULL;
|
|
|
|
|
|
|
|
#ifdef JS_TYPE_INFERENCE
|
|
|
|
|
|
|
|
stack = code->inStack;
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < nuses; i++)
|
|
|
|
stack = stack->group()->innerStack;
|
|
|
|
|
|
|
|
code->pushedArray = ArenaArray<types::TypeStack>(pool, ndefs);
|
|
|
|
PodZero(code->pushedArray, ndefs);
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < ndefs; i++) {
|
|
|
|
code->pushedArray[i].types.setPool(&pool);
|
|
|
|
code->pushedArray[i].setInnerStack(stack);
|
|
|
|
stack = &code->pushedArray[i];
|
|
|
|
|
|
|
|
#ifdef JS_TYPES_DEBUG_SPEW
|
|
|
|
fprintf(cx->typeOut(), "pushed #%u:%05u %u T%u\n",
|
|
|
|
id, offset, i, stack->types.id);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Track the initializer stack and compute new objects for encountered initializers. */
|
|
|
|
if (op == JSOP_NEWINIT) {
|
|
|
|
int i = GET_UINT16(pc);
|
|
|
|
JS_ASSERT(i == JSProto_Array || i == JSProto_Object);
|
|
|
|
bool newArray = (i == JSProto_Array);
|
|
|
|
|
|
|
|
types::TypeObject *object;
|
|
|
|
if (initializerStack && initializerStack->initObject &&
|
|
|
|
initializerStack->initArray == newArray) {
|
|
|
|
object = code->initObject = initializerStack->initObject;
|
|
|
|
} else {
|
|
|
|
object = code->getInitObject(cx, newArray);
|
|
|
|
|
|
|
|
if (initializerStack && initializerStack->isArray) {
|
|
|
|
initializerStack->initObject = object;
|
|
|
|
initializerStack->initArray = newArray;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
InitializerInfo *info = (InitializerInfo *) cx->calloc(sizeof(InitializerInfo));
|
|
|
|
info->outer = initializerStack;
|
|
|
|
info->object = object;
|
|
|
|
info->isArray = newArray;
|
|
|
|
initializerStack = info;
|
|
|
|
} else if (op == JSOP_INITELEM || op == JSOP_INITPROP || op == JSOP_INITMETHOD) {
|
|
|
|
JS_ASSERT(initializerStack);
|
|
|
|
code->initObject = initializerStack->object;
|
|
|
|
} else if (op == JSOP_NEWARRAY) {
|
|
|
|
if (initializerStack && initializerStack->initObject &&
|
|
|
|
initializerStack->initArray) {
|
|
|
|
code->initObject = initializerStack->initObject;
|
|
|
|
} else {
|
|
|
|
code->getInitObject(cx, true);
|
|
|
|
}
|
|
|
|
} else if (op == JSOP_ENDINIT) {
|
|
|
|
JS_ASSERT(initializerStack);
|
|
|
|
InitializerInfo *info = initializerStack;
|
|
|
|
initializerStack = initializerStack->outer;
|
|
|
|
cx->free(info);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* JS_TYPE_INFERENCE */
|
|
|
|
|
2010-10-26 12:00:54 -07:00
|
|
|
switch (op) {
|
|
|
|
|
|
|
|
case JSOP_SETRVAL:
|
|
|
|
case JSOP_POPV:
|
|
|
|
usesRval = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case JSOP_NAME:
|
|
|
|
case JSOP_CALLNAME:
|
|
|
|
case JSOP_BINDNAME:
|
|
|
|
case JSOP_SETNAME:
|
|
|
|
case JSOP_DELNAME:
|
|
|
|
case JSOP_INCNAME:
|
|
|
|
case JSOP_DECNAME:
|
|
|
|
case JSOP_NAMEINC:
|
|
|
|
case JSOP_NAMEDEC:
|
|
|
|
case JSOP_FORNAME:
|
|
|
|
usesScope = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* Watch for opcodes the method JIT doesn't compile. */
|
|
|
|
case JSOP_GOSUB:
|
|
|
|
case JSOP_GOSUBX:
|
|
|
|
case JSOP_IFPRIMTOP:
|
|
|
|
case JSOP_FILTER:
|
|
|
|
case JSOP_ENDFILTER:
|
|
|
|
case JSOP_TABLESWITCHX:
|
|
|
|
case JSOP_LOOKUPSWITCHX:
|
|
|
|
hadFailure = true;
|
2010-11-09 14:40:10 -08:00
|
|
|
#ifdef JS_TYPE_INFERENCE
|
|
|
|
if (function)
|
|
|
|
function->returnTypes.addType(cx, types::TYPE_UNKNOWN);
|
|
|
|
#endif
|
2010-10-26 12:00:54 -07:00
|
|
|
return;
|
|
|
|
|
|
|
|
case JSOP_TABLESWITCH: {
|
|
|
|
jsbytecode *pc2 = pc;
|
|
|
|
unsigned defaultOffset = offset + GetJumpOffset(pc, pc2);
|
|
|
|
pc2 += JUMP_OFFSET_LEN;
|
|
|
|
jsint low = GET_JUMP_OFFSET(pc2);
|
|
|
|
pc2 += JUMP_OFFSET_LEN;
|
|
|
|
jsint high = GET_JUMP_OFFSET(pc2);
|
|
|
|
pc2 += JUMP_OFFSET_LEN;
|
|
|
|
|
|
|
|
if (!addJump(cx, defaultOffset, &nextOffset, &forwardJump,
|
2010-10-29 08:05:55 -07:00
|
|
|
stackDepth, stack, defineArray, defineCount)) {
|
2010-10-26 12:00:54 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (jsint i = low; i <= high; i++) {
|
|
|
|
unsigned targetOffset = offset + GetJumpOffset(pc, pc2);
|
|
|
|
if (targetOffset != offset) {
|
|
|
|
if (!addJump(cx, targetOffset, &nextOffset, &forwardJump,
|
2010-10-29 08:05:55 -07:00
|
|
|
stackDepth, stack, defineArray, defineCount)) {
|
2010-10-26 12:00:54 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pc2 += JUMP_OFFSET_LEN;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case JSOP_LOOKUPSWITCH: {
|
|
|
|
jsbytecode *pc2 = pc;
|
|
|
|
unsigned defaultOffset = offset + GetJumpOffset(pc, pc2);
|
|
|
|
pc2 += JUMP_OFFSET_LEN;
|
|
|
|
unsigned npairs = GET_UINT16(pc2);
|
|
|
|
pc2 += UINT16_LEN;
|
|
|
|
|
|
|
|
if (!addJump(cx, defaultOffset, &nextOffset, &forwardJump,
|
2010-10-29 08:05:55 -07:00
|
|
|
stackDepth, stack, defineArray, defineCount)) {
|
2010-10-26 12:00:54 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (npairs) {
|
|
|
|
pc2 += INDEX_LEN;
|
|
|
|
unsigned targetOffset = offset + GetJumpOffset(pc, pc2);
|
|
|
|
if (!addJump(cx, targetOffset, &nextOffset, &forwardJump,
|
2010-10-29 08:05:55 -07:00
|
|
|
stackDepth, stack, defineArray, defineCount)) {
|
2010-10-26 12:00:54 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
pc2 += JUMP_OFFSET_LEN;
|
|
|
|
npairs--;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case JSOP_TRY: {
|
|
|
|
/*
|
|
|
|
* Everything between a try and corresponding catch or finally is conditional.
|
|
|
|
* Note that there is no problem with code which is skipped by a thrown
|
|
|
|
* exception but is not caught by a later handler in the same function:
|
|
|
|
* no more code will execute, and it does not matter what is defined.
|
|
|
|
*/
|
|
|
|
JSTryNote *tn = script->trynotes()->vector;
|
|
|
|
JSTryNote *tnlimit = tn + script->trynotes()->length;
|
|
|
|
for (; tn < tnlimit; tn++) {
|
|
|
|
unsigned startOffset = script->main - script->code + tn->start;
|
|
|
|
if (startOffset == offset + 1) {
|
|
|
|
unsigned catchOffset = startOffset + tn->length;
|
|
|
|
|
|
|
|
/* This will overestimate try block code, for multiple catch/finally. */
|
|
|
|
if (catchOffset > forwardCatch)
|
|
|
|
forwardCatch = catchOffset;
|
|
|
|
|
|
|
|
if (tn->kind != JSTRY_ITER) {
|
|
|
|
if (!addJump(cx, catchOffset, &nextOffset, &forwardJump,
|
2010-10-29 08:05:55 -07:00
|
|
|
stackDepth, stack, defineArray, defineCount)) {
|
2010-10-26 12:00:54 -07:00
|
|
|
return;
|
|
|
|
}
|
2010-10-29 08:05:55 -07:00
|
|
|
getCode(catchOffset).exceptionEntry = true;
|
2010-10-26 12:00:54 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case JSOP_GETLOCAL:
|
|
|
|
/*
|
|
|
|
* Watch for uses of variables not known to be defined, and mark
|
|
|
|
* them as having possible uses before definitions. Ignore GETLOCAL
|
|
|
|
* followed by a POP, these are generated for, e.g. 'var x;'
|
|
|
|
*/
|
|
|
|
if (pc[JSOP_GETLOCAL_LENGTH] != JSOP_POP) {
|
|
|
|
uint32 local = GET_SLOTNO(pc);
|
|
|
|
if (local < nfixed && !localDefined(local, offset))
|
|
|
|
setLocal(local, LOCAL_USE_BEFORE_DEF);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case JSOP_CALLLOCAL:
|
|
|
|
case JSOP_INCLOCAL:
|
|
|
|
case JSOP_DECLOCAL:
|
|
|
|
case JSOP_LOCALINC:
|
|
|
|
case JSOP_LOCALDEC: {
|
|
|
|
uint32 local = GET_SLOTNO(pc);
|
|
|
|
if (local < nfixed && !localDefined(local, offset))
|
|
|
|
setLocal(local, LOCAL_USE_BEFORE_DEF);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case JSOP_SETLOCAL:
|
|
|
|
case JSOP_FORLOCAL: {
|
|
|
|
uint32 local = GET_SLOTNO(pc);
|
|
|
|
JS_ASSERT_IF(local < nfixed &&
|
|
|
|
locals[local] != LOCAL_CONDITIONALLY_DEFINED &&
|
|
|
|
locals[local] != LOCAL_USE_BEFORE_DEF,
|
|
|
|
locals[local] <= offset);
|
|
|
|
if (local < nfixed && locals[local] == LOCAL_CONDITIONALLY_DEFINED) {
|
|
|
|
if (forwardJump) {
|
|
|
|
/* Add this local to the variables defined after this bytecode. */
|
|
|
|
uint32 *newArray = ArenaArray<uint32>(pool, defineCount + 1);
|
|
|
|
if (!newArray) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (defineCount)
|
|
|
|
memcpy(newArray, defineArray, defineCount * sizeof(uint32));
|
|
|
|
defineArray = newArray;
|
|
|
|
defineArray[defineCount++] = local;
|
|
|
|
} else {
|
|
|
|
/* This local is unconditionally defined by this bytecode. */
|
|
|
|
setLocal(local, offset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32 type = JOF_TYPE(js_CodeSpec[op].format);
|
|
|
|
|
|
|
|
/* Check basic jump opcodes, which may or may not have a fallthrough. */
|
|
|
|
if (type == JOF_JUMP || type == JOF_JUMPX) {
|
|
|
|
/* Some opcodes behave differently on their branching path. */
|
2010-10-29 08:05:55 -07:00
|
|
|
unsigned newStackDepth = stackDepth;
|
|
|
|
types::TypeStack *newStack = stack;
|
2010-10-26 12:00:54 -07:00
|
|
|
|
|
|
|
switch (op) {
|
|
|
|
case JSOP_OR:
|
|
|
|
case JSOP_AND:
|
|
|
|
case JSOP_ORX:
|
|
|
|
case JSOP_ANDX:
|
2010-10-29 08:05:55 -07:00
|
|
|
/*
|
|
|
|
* OR/AND instructions push the operation result when branching.
|
|
|
|
* We accounted for this in GetDefCount, so subtract the pushed value
|
|
|
|
* for the fallthrough case.
|
|
|
|
*/
|
|
|
|
stackDepth--;
|
|
|
|
#ifdef JS_TYPE_INFERENCE
|
|
|
|
stack = stack->group()->innerStack;
|
|
|
|
#endif
|
2010-10-26 12:00:54 -07:00
|
|
|
break;
|
|
|
|
|
|
|
|
case JSOP_CASE:
|
|
|
|
case JSOP_CASEX:
|
|
|
|
/* Case instructions do not push the lvalue back when branching. */
|
2010-10-29 08:05:55 -07:00
|
|
|
newStackDepth--;
|
|
|
|
#ifdef JS_TYPE_INFERENCE
|
|
|
|
newStack = newStack->group()->innerStack;
|
|
|
|
#endif
|
2010-10-26 12:00:54 -07:00
|
|
|
break;
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
default:;
|
2010-10-26 12:00:54 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
unsigned targetOffset = offset + GetJumpOffset(pc, pc);
|
|
|
|
if (!addJump(cx, targetOffset, &nextOffset, &forwardJump,
|
2010-10-29 08:05:55 -07:00
|
|
|
newStackDepth, newStack, defineArray, defineCount)) {
|
2010-10-26 12:00:54 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Handle any fallthrough from this opcode. */
|
|
|
|
if (!BytecodeNoFallThrough(op)) {
|
|
|
|
JS_ASSERT(successorOffset < script->length);
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
Bytecode *&nextcode = codeArray[successorOffset];
|
2010-10-26 12:00:54 -07:00
|
|
|
bool initial = (nextcode == NULL);
|
|
|
|
|
|
|
|
if (initial) {
|
2010-10-29 08:05:55 -07:00
|
|
|
nextcode = ArenaNew<Bytecode>(pool, this, successorOffset);
|
2010-10-26 12:00:54 -07:00
|
|
|
if (!nextcode) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
if (!nextcode->mergeDefines(cx, this, initial, stackDepth, stack,
|
|
|
|
defineArray, defineCount)) {
|
2010-10-26 12:00:54 -07:00
|
|
|
return;
|
2010-10-29 08:05:55 -07:00
|
|
|
}
|
2010-10-26 12:00:54 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
JS_ASSERT(!failed());
|
|
|
|
JS_ASSERT(forwardJump == 0 && forwardCatch == 0);
|
2010-10-29 08:05:55 -07:00
|
|
|
|
|
|
|
#ifdef JS_TYPE_INFERENCE
|
|
|
|
/* Generate type constraints for the script. */
|
|
|
|
offset = 0;
|
2010-11-09 14:40:10 -08:00
|
|
|
TypeState state;
|
2010-10-29 08:05:55 -07:00
|
|
|
while (offset < script->length) {
|
|
|
|
analyze::Bytecode *code = maybeCode(offset);
|
|
|
|
|
|
|
|
jsbytecode *pc = script->code + offset;
|
|
|
|
offset += GetBytecodeLength(pc);
|
|
|
|
|
|
|
|
if (code && code->analyzed)
|
2010-11-09 14:40:10 -08:00
|
|
|
analyzeTypes(cx, code, state);
|
2010-10-29 08:05:55 -07:00
|
|
|
}
|
|
|
|
#endif
|
2010-10-26 12:00:54 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
} /* namespace analyze */
|
|
|
|
} /* namespace js */
|