Merge MC -> JM

This commit is contained in:
Brian Hackett
2011-11-02 09:23:25 -07:00
1808 changed files with 245517 additions and 14954 deletions

View File

@@ -42,8 +42,9 @@
#include "jsprobes.h"
#include "frontend/BytecodeGenerator.h"
#include "frontend/BytecodeEmitter.h"
#include "frontend/FoldConstants.h"
#include "frontend/SemanticAnalysis.h"
#include "vm/GlobalObject.h"
#include "jsinferinlines.h"
@@ -51,239 +52,8 @@
using namespace js;
using namespace js::frontend;
/*
* Compile a top-level script.
*/
BytecodeCompiler::BytecodeCompiler(JSContext *cx, JSPrincipals *prin, StackFrame *cfp)
: parser(cx, prin, cfp), globalScope(NULL)
{}
JSScript *
BytecodeCompiler::compileScript(JSContext *cx, JSObject *scopeChain, StackFrame *callerFrame,
JSPrincipals *principals, uint32 tcflags,
const jschar *chars, size_t length,
const char *filename, uintN lineno, JSVersion version,
JSString *source /* = NULL */,
uintN staticLevel /* = 0 */)
{
TokenKind tt;
ParseNode *pn;
JSScript *script;
bool inDirectivePrologue;
JS_ASSERT(!(tcflags & ~(TCF_COMPILE_N_GO | TCF_NO_SCRIPT_RVAL | TCF_NEED_MUTABLE_SCRIPT |
TCF_COMPILE_FOR_EVAL | TCF_NEED_SCRIPT_GLOBAL)));
/*
* The scripted callerFrame can only be given for compile-and-go scripts
* and non-zero static level requires callerFrame.
*/
JS_ASSERT_IF(callerFrame, tcflags & TCF_COMPILE_N_GO);
JS_ASSERT_IF(staticLevel != 0, callerFrame);
BytecodeCompiler compiler(cx, principals, callerFrame);
if (!compiler.init(chars, length, filename, lineno, version))
return NULL;
Parser &parser = compiler.parser;
TokenStream &tokenStream = parser.tokenStream;
CodeGenerator cg(&parser, tokenStream.getLineno());
if (!cg.init(cx, TreeContext::USED_AS_TREE_CONTEXT))
return NULL;
Probes::compileScriptBegin(cx, filename, lineno);
MUST_FLOW_THROUGH("out");
// We can specialize a bit for the given scope chain if that scope chain is the global object.
JSObject *globalObj = scopeChain && scopeChain == scopeChain->getGlobal()
? scopeChain->getGlobal()
: NULL;
JS_ASSERT_IF(globalObj, globalObj->isNative());
JS_ASSERT_IF(globalObj, JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(globalObj->getClass()));
/* Null script early in case of error, to reduce our code footprint. */
script = NULL;
GlobalScope globalScope(cx, globalObj, &cg);
cg.flags |= tcflags;
cg.setScopeChain(scopeChain);
compiler.globalScope = &globalScope;
if (!SetStaticLevel(&cg, staticLevel))
goto out;
/* If this is a direct call to eval, inherit the caller's strictness. */
if (callerFrame &&
callerFrame->isScriptFrame() &&
callerFrame->script()->strictModeCode) {
cg.flags |= TCF_STRICT_MODE_CODE;
tokenStream.setStrictMode();
}
#ifdef DEBUG
bool savedCallerFun;
savedCallerFun = false;
#endif
if (tcflags & TCF_COMPILE_N_GO) {
if (source) {
/*
* Save eval program source in script->atoms[0] for the
* eval cache (see EvalCacheLookup in jsobj.cpp).
*/
JSAtom *atom = js_AtomizeString(cx, source);
jsatomid _;
if (!atom || !cg.makeAtomIndex(atom, &_))
goto out;
}
if (callerFrame && callerFrame->isFunctionFrame()) {
/*
* An eval script in a caller frame needs to have its enclosing
* function captured in case it refers to an upvar, and someone
* wishes to decompile it while it's running.
*/
ObjectBox *funbox = parser.newObjectBox(callerFrame->fun());
if (!funbox)
goto out;
funbox->emitLink = cg.objectList.lastbox;
cg.objectList.lastbox = funbox;
cg.objectList.length++;
#ifdef DEBUG
savedCallerFun = true;
#endif
}
}
/*
* Inline this->statements to emit as we go to save AST space. We must
* generate our script-body blockid since we aren't calling Statements.
*/
uint32 bodyid;
if (!GenerateBlockId(&cg, bodyid))
goto out;
cg.bodyid = bodyid;
#if JS_HAS_XML_SUPPORT
pn = NULL;
bool onlyXML;
onlyXML = true;
#endif
inDirectivePrologue = true;
tokenStream.setOctalCharacterEscape(false);
for (;;) {
tt = tokenStream.peekToken(TSF_OPERAND);
if (tt <= TOK_EOF) {
if (tt == TOK_EOF)
break;
JS_ASSERT(tt == TOK_ERROR);
goto out;
}
pn = parser.statement();
if (!pn)
goto out;
JS_ASSERT(!cg.blockNode);
if (inDirectivePrologue && !parser.recognizeDirectivePrologue(pn, &inDirectivePrologue))
goto out;
if (!FoldConstants(cx, pn, &cg))
goto out;
if (!parser.analyzeFunctions(&cg))
goto out;
cg.functionList = NULL;
if (!EmitTree(cx, &cg, pn))
goto out;
#if JS_HAS_XML_SUPPORT
if (!pn->isKind(TOK_SEMI) || !pn->pn_kid || !TreeTypeIsXML(pn->pn_kid->getKind()))
onlyXML = false;
#endif
cg.freeTree(pn);
}
#if JS_HAS_XML_SUPPORT
/*
* Prevent XML data theft via <script src="http://victim.com/foo.xml">.
* For background, see:
*
* https://bugzilla.mozilla.org/show_bug.cgi?id=336551
*/
if (pn && onlyXML && !callerFrame) {
parser.reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_XML_WHOLE_PROGRAM);
goto out;
}
#endif
/*
* Global variables (gvars) share the atom index space with locals. Due to
* incremental code generation we need to patch the bytecode to adjust the
* local references to skip the globals.
*/
if (cg.hasSharps()) {
jsbytecode *code, *end;
JSOp op;
const JSCodeSpec *cs;
uintN len, slot;
code = CG_BASE(&cg);
for (end = code + CG_OFFSET(&cg); code != end; code += len) {
JS_ASSERT(code < end);
op = (JSOp) *code;
cs = &js_CodeSpec[op];
len = (cs->length > 0)
? (uintN) cs->length
: js_GetVariableBytecodeLength(code);
if ((cs->format & JOF_SHARPSLOT) ||
JOF_TYPE(cs->format) == JOF_LOCAL ||
(JOF_TYPE(cs->format) == JOF_SLOTATOM)) {
JS_ASSERT_IF(!(cs->format & JOF_SHARPSLOT),
JOF_TYPE(cs->format) != JOF_SLOTATOM);
slot = GET_SLOTNO(code);
if (!(cs->format & JOF_SHARPSLOT))
slot += cg.sharpSlots();
if (slot >= SLOTNO_LIMIT)
goto too_many_slots;
SET_SLOTNO(code, slot);
}
}
}
/*
* Nowadays the threaded interpreter needs a stop instruction, so we
* do have to emit that here.
*/
if (Emit1(cx, &cg, JSOP_STOP) < 0)
goto out;
JS_ASSERT(cg.version() == version);
script = JSScript::NewScriptFromCG(cx, &cg);
if (!script)
goto out;
JS_ASSERT(script->savedCallerFun == savedCallerFun);
if (!defineGlobals(cx, globalScope, script))
script = NULL;
out:
Probes::compileScriptEnd(cx, script, filename, lineno);
return script;
too_many_slots:
parser.reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_TOO_MANY_LOCALS);
script = NULL;
goto out;
}
bool
BytecodeCompiler::defineGlobals(JSContext *cx, GlobalScope &globalScope, JSScript *script)
DefineGlobals(JSContext *cx, GlobalScope &globalScope, JSScript *script)
{
JSObject *globalObj = globalScope.globalObj;
@@ -384,37 +154,257 @@ BytecodeCompiler::defineGlobals(JSContext *cx, GlobalScope &globalScope, JSScrip
return true;
}
JSScript *
frontend::CompileScript(JSContext *cx, JSObject *scopeChain, StackFrame *callerFrame,
JSPrincipals *principals, uint32 tcflags,
const jschar *chars, size_t length,
const char *filename, uintN lineno, JSVersion version,
JSString *source /* = NULL */,
uintN staticLevel /* = 0 */)
{
TokenKind tt;
ParseNode *pn;
JSScript *script;
bool inDirectivePrologue;
JS_ASSERT(!(tcflags & ~(TCF_COMPILE_N_GO | TCF_NO_SCRIPT_RVAL | TCF_COMPILE_FOR_EVAL
| TCF_NEED_SCRIPT_GLOBAL)));
/*
* The scripted callerFrame can only be given for compile-and-go scripts
* and non-zero static level requires callerFrame.
*/
JS_ASSERT_IF(callerFrame, tcflags & TCF_COMPILE_N_GO);
JS_ASSERT_IF(staticLevel != 0, callerFrame);
Parser parser(cx, principals, callerFrame);
if (!parser.init(chars, length, filename, lineno, version))
return NULL;
TokenStream &tokenStream = parser.tokenStream;
BytecodeEmitter bce(&parser, tokenStream.getLineno());
if (!bce.init(cx, TreeContext::USED_AS_TREE_CONTEXT))
return NULL;
Probes::compileScriptBegin(cx, filename, lineno);
MUST_FLOW_THROUGH("out");
// We can specialize a bit for the given scope chain if that scope chain is the global object.
JSObject *globalObj = scopeChain && scopeChain == scopeChain->getGlobal()
? scopeChain->getGlobal()
: NULL;
JS_ASSERT_IF(globalObj, globalObj->isNative());
JS_ASSERT_IF(globalObj, JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(globalObj->getClass()));
/* Null script early in case of error, to reduce our code footprint. */
script = NULL;
GlobalScope globalScope(cx, globalObj, &bce);
bce.flags |= tcflags;
bce.setScopeChain(scopeChain);
bce.globalScope = &globalScope;
if (!SetStaticLevel(&bce, staticLevel))
goto out;
/* If this is a direct call to eval, inherit the caller's strictness. */
if (callerFrame &&
callerFrame->isScriptFrame() &&
callerFrame->script()->strictModeCode) {
bce.flags |= TCF_STRICT_MODE_CODE;
tokenStream.setStrictMode();
}
#ifdef DEBUG
bool savedCallerFun;
savedCallerFun = false;
#endif
if (tcflags & TCF_COMPILE_N_GO) {
if (source) {
/*
* Save eval program source in script->atoms[0] for the
* eval cache (see EvalCacheLookup in jsobj.cpp).
*/
JSAtom *atom = js_AtomizeString(cx, source);
jsatomid _;
if (!atom || !bce.makeAtomIndex(atom, &_))
goto out;
}
if (callerFrame && callerFrame->isFunctionFrame()) {
/*
* An eval script in a caller frame needs to have its enclosing
* function captured in case it refers to an upvar, and someone
* wishes to decompile it while it's running.
*/
ObjectBox *funbox = parser.newObjectBox(callerFrame->fun());
if (!funbox)
goto out;
funbox->emitLink = bce.objectList.lastbox;
bce.objectList.lastbox = funbox;
bce.objectList.length++;
#ifdef DEBUG
savedCallerFun = true;
#endif
}
}
/*
* Inline this->statements to emit as we go to save AST space. We must
* generate our script-body blockid since we aren't calling Statements.
*/
uint32 bodyid;
if (!GenerateBlockId(&bce, bodyid))
goto out;
bce.bodyid = bodyid;
#if JS_HAS_XML_SUPPORT
pn = NULL;
bool onlyXML;
onlyXML = true;
#endif
inDirectivePrologue = true;
tokenStream.setOctalCharacterEscape(false);
for (;;) {
tt = tokenStream.peekToken(TSF_OPERAND);
if (tt <= TOK_EOF) {
if (tt == TOK_EOF)
break;
JS_ASSERT(tt == TOK_ERROR);
goto out;
}
pn = parser.statement();
if (!pn)
goto out;
JS_ASSERT(!bce.blockNode);
if (inDirectivePrologue && !parser.recognizeDirectivePrologue(pn, &inDirectivePrologue))
goto out;
if (!FoldConstants(cx, pn, &bce))
goto out;
if (!AnalyzeFunctions(&bce))
goto out;
bce.functionList = NULL;
if (!EmitTree(cx, &bce, pn))
goto out;
#if JS_HAS_XML_SUPPORT
if (!pn->isKind(TOK_SEMI) || !pn->pn_kid || !TreeTypeIsXML(pn->pn_kid->getKind()))
onlyXML = false;
#endif
bce.freeTree(pn);
}
#if JS_HAS_XML_SUPPORT
/*
* Prevent XML data theft via <script src="http://victim.com/foo.xml">.
* For background, see:
*
* https://bugzilla.mozilla.org/show_bug.cgi?id=336551
*/
if (pn && onlyXML && !callerFrame) {
parser.reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_XML_WHOLE_PROGRAM);
goto out;
}
#endif
/*
* Global variables (gvars) share the atom index space with locals. Due to
* incremental code generation we need to patch the bytecode to adjust the
* local references to skip the globals.
*/
if (bce.hasSharps()) {
jsbytecode *code, *end;
JSOp op;
const JSCodeSpec *cs;
uintN len, slot;
code = bce.base();
for (end = code + bce.offset(); code != end; code += len) {
JS_ASSERT(code < end);
op = (JSOp) *code;
cs = &js_CodeSpec[op];
len = (cs->length > 0)
? (uintN) cs->length
: js_GetVariableBytecodeLength(code);
if ((cs->format & JOF_SHARPSLOT) ||
JOF_TYPE(cs->format) == JOF_LOCAL ||
(JOF_TYPE(cs->format) == JOF_SLOTATOM)) {
JS_ASSERT_IF(!(cs->format & JOF_SHARPSLOT),
JOF_TYPE(cs->format) != JOF_SLOTATOM);
slot = GET_SLOTNO(code);
if (!(cs->format & JOF_SHARPSLOT))
slot += bce.sharpSlots();
if (slot >= SLOTNO_LIMIT)
goto too_many_slots;
SET_SLOTNO(code, slot);
}
}
}
/*
* Nowadays the threaded interpreter needs a stop instruction, so we
* do have to emit that here.
*/
if (Emit1(cx, &bce, JSOP_STOP) < 0)
goto out;
JS_ASSERT(bce.version() == version);
script = JSScript::NewScriptFromEmitter(cx, &bce);
if (!script)
goto out;
JS_ASSERT(script->savedCallerFun == savedCallerFun);
if (!DefineGlobals(cx, globalScope, script))
script = NULL;
out:
Probes::compileScriptEnd(cx, script, filename, lineno);
return script;
too_many_slots:
parser.reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_TOO_MANY_LOCALS);
script = NULL;
goto out;
}
/*
* Compile a JS function body, which might appear as the value of an event
* handler attribute in an HTML <INPUT> tag.
*/
bool
BytecodeCompiler::compileFunctionBody(JSContext *cx, JSFunction *fun, JSPrincipals *principals,
Bindings *bindings, const jschar *chars, size_t length,
const char *filename, uintN lineno, JSVersion version)
frontend::CompileFunctionBody(JSContext *cx, JSFunction *fun, JSPrincipals *principals,
Bindings *bindings, const jschar *chars, size_t length,
const char *filename, uintN lineno, JSVersion version)
{
BytecodeCompiler compiler(cx, principals);
if (!compiler.init(chars, length, filename, lineno, version))
Parser parser(cx, principals);
if (!parser.init(chars, length, filename, lineno, version))
return false;
Parser &parser = compiler.parser;
TokenStream &tokenStream = parser.tokenStream;
CodeGenerator funcg(&parser, tokenStream.getLineno());
if (!funcg.init(cx, TreeContext::USED_AS_TREE_CONTEXT))
BytecodeEmitter funbce(&parser, tokenStream.getLineno());
if (!funbce.init(cx, TreeContext::USED_AS_TREE_CONTEXT))
return false;
funcg.flags |= TCF_IN_FUNCTION;
funcg.setFunction(fun);
funcg.bindings.transfer(cx, bindings);
fun->setArgCount(funcg.bindings.countArgs());
if (!GenerateBlockId(&funcg, funcg.bodyid))
funbce.flags |= TCF_IN_FUNCTION;
funbce.setFunction(fun);
funbce.bindings.transfer(cx, bindings);
fun->setArgCount(funbce.bindings.countArgs());
if (!GenerateBlockId(&funbce, funbce.bodyid))
return false;
/* FIXME: make Function format the source for a function definition. */
tokenStream.mungeCurrentToken(TOK_NAME);
ParseNode *fn = FunctionNode::create(&funcg);
ParseNode *fn = FunctionNode::create(&funbce);
if (fn) {
fn->pn_body = NULL;
fn->pn_cookie.makeFree();
@@ -426,11 +416,11 @@ BytecodeCompiler::compileFunctionBody(JSContext *cx, JSFunction *fun, JSPrincipa
* allocated from cx->tempLifoAlloc by DefineArg.
*/
Vector<JSAtom *> names(cx);
if (!funcg.bindings.getLocalNameArray(cx, &names)) {
if (!funbce.bindings.getLocalNameArray(cx, &names)) {
fn = NULL;
} else {
for (uintN i = 0; i < nargs; i++) {
if (!DefineArg(fn, names[i], i, &funcg)) {
if (!DefineArg(fn, names[i], i, &funbce)) {
fn = NULL;
break;
}
@@ -441,7 +431,7 @@ BytecodeCompiler::compileFunctionBody(JSContext *cx, JSFunction *fun, JSPrincipa
/*
* Farble the body so that it looks like a block statement to EmitTree,
* which is called from EmitFunctionBody (see BytecodeGenerator.cpp).
* which is called from EmitFunctionBody (see BytecodeEmitter.cpp).
* After we're done parsing, we must fold constants, analyze any nested
* functions, and generate code for this function, including a stop opcode
* at the end.
@@ -449,15 +439,15 @@ BytecodeCompiler::compileFunctionBody(JSContext *cx, JSFunction *fun, JSPrincipa
tokenStream.mungeCurrentToken(TOK_LC);
ParseNode *pn = fn ? parser.functionBody() : NULL;
if (pn) {
if (!CheckStrictParameters(cx, &funcg)) {
if (!CheckStrictParameters(cx, &funbce)) {
pn = NULL;
} else if (!tokenStream.matchToken(TOK_EOF)) {
parser.reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_SYNTAX_ERROR);
pn = NULL;
} else if (!FoldConstants(cx, pn, &funcg)) {
} else if (!FoldConstants(cx, pn, &funbce)) {
/* FoldConstants reported the error already. */
pn = NULL;
} else if (!parser.analyzeFunctions(&funcg)) {
} else if (!AnalyzeFunctions(&funbce)) {
pn = NULL;
} else {
if (fn->pn_body) {
@@ -467,7 +457,7 @@ BytecodeCompiler::compileFunctionBody(JSContext *cx, JSFunction *fun, JSPrincipa
pn = fn->pn_body;
}
if (!EmitFunctionScript(cx, &funcg, pn))
if (!EmitFunctionScript(cx, &funbce, pn))
pn = NULL;
}
}