PageRenderTime 42ms CodeModel.GetById 12ms app.highlight 26ms RepoModel.GetById 1ms app.codeStats 0ms

/js/src/frontend/BytecodeCompiler.cpp

http://github.com/zpao/v8monkey
C++ | 425 lines | 259 code | 59 blank | 107 comment | 74 complexity | e41053f080b7590c10da649d7792301a MD5 | raw file
  1/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  2 * vim: set ts=8 sw=4 et tw=99:
  3 *
  4 * ***** BEGIN LICENSE BLOCK *****
  5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  6 *
  7 * The contents of this file are subject to the Mozilla Public License Version
  8 * 1.1 (the "License"); you may not use this file except in compliance with
  9 * the License. You may obtain a copy of the License at
 10 * http://www.mozilla.org/MPL/
 11 *
 12 * Software distributed under the License is distributed on an "AS IS" basis,
 13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 14 * for the specific language governing rights and limitations under the
 15 * License.
 16 *
 17 * The Original Code is Mozilla Communicator client code, released
 18 * March 31, 1998.
 19 *
 20 * The Initial Developer of the Original Code is
 21 * Netscape Communications Corporation.
 22 * Portions created by the Initial Developer are Copyright (C) 1998-2011
 23 * the Initial Developer. All Rights Reserved.
 24 *
 25 * Contributor(s):
 26 *
 27 * Alternatively, the contents of this file may be used under the terms of
 28 * either the GNU General Public License Version 2 or later (the "GPL"), or
 29 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 30 * in which case the provisions of the GPL or the LGPL are applicable instead
 31 * of those above. If you wish to allow use of your version of this file only
 32 * under the terms of either the GPL or the LGPL, and not to allow others to
 33 * use your version of this file under the terms of the MPL, indicate your
 34 * decision by deleting the provisions above and replace them with the notice
 35 * and other provisions required by the GPL or the LGPL. If you do not delete
 36 * the provisions above, a recipient may use your version of this file under
 37 * the terms of any one of the MPL, the GPL or the LGPL.
 38 *
 39 * ***** END LICENSE BLOCK ***** */
 40
 41#include "frontend/BytecodeCompiler.h"
 42
 43#include "jsprobes.h"
 44
 45#include "frontend/BytecodeEmitter.h"
 46#include "frontend/FoldConstants.h"
 47#include "frontend/SemanticAnalysis.h"
 48#include "vm/GlobalObject.h"
 49
 50#include "jsinferinlines.h"
 51
 52using namespace js;
 53using namespace js::frontend;
 54
 55bool
 56DefineGlobals(JSContext *cx, GlobalScope &globalScope, JSScript *script)
 57{
 58    JSObject *globalObj = globalScope.globalObj;
 59
 60    /* Define and update global properties. */
 61    for (size_t i = 0; i < globalScope.defs.length(); i++) {
 62        GlobalScope::GlobalDef &def = globalScope.defs[i];
 63
 64        /* Names that could be resolved ahead of time can be skipped. */
 65        if (!def.atom)
 66            continue;
 67
 68        jsid id = ATOM_TO_JSID(def.atom);
 69        Value rval;
 70
 71        if (def.funbox) {
 72            JSFunction *fun = def.funbox->function();
 73
 74            /*
 75             * No need to check for redeclarations or anything, global
 76             * optimizations only take place if the property is not defined.
 77             */
 78            rval.setObject(*fun);
 79            types::AddTypePropertyId(cx, globalObj, id, rval);
 80        } else {
 81            rval.setUndefined();
 82        }
 83
 84        /*
 85         * Don't update the type information when defining the property for the
 86         * global object, per the consistency rules for type properties. If the
 87         * property is only undefined before it is ever written, we can check
 88         * the global directly during compilation and avoid having to emit type
 89         * checks every time it is accessed in the script.
 90         */
 91        const Shape *shape =
 92            DefineNativeProperty(cx, globalObj, id, rval, JS_PropertyStub, JS_StrictPropertyStub,
 93                                 JSPROP_ENUMERATE | JSPROP_PERMANENT, 0, 0, DNP_SKIP_TYPE);
 94        if (!shape)
 95            return false;
 96        def.knownSlot = shape->slot();
 97    }
 98
 99    Vector<JSScript *, 16> worklist(cx);
100    if (!worklist.append(script))
101        return false;
102
103    /*
104     * Recursively walk through all scripts we just compiled. For each script,
105     * go through all global uses. Each global use indexes into globalScope->defs.
106     * Use this information to repoint each use to the correct slot in the global
107     * object.
108     */
109    while (worklist.length()) {
110        JSScript *outer = worklist.back();
111        worklist.popBack();
112
113        if (JSScript::isValidOffset(outer->objectsOffset)) {
114            JSObjectArray *arr = outer->objects();
115
116            /*
117             * If this is an eval script, don't treat the saved caller function
118             * stored in the first object slot as an inner function.
119             */
120            size_t start = outer->savedCallerFun ? 1 : 0;
121
122            for (size_t i = start; i < arr->length; i++) {
123                JSObject *obj = arr->vector[i];
124                if (!obj->isFunction())
125                    continue;
126                JSFunction *fun = obj->toFunction();
127                JS_ASSERT(fun->isInterpreted());
128                JSScript *inner = fun->script();
129                if (outer->function() && outer->function()->isHeavyweight()) {
130                    outer->isOuterFunction = true;
131                    inner->isInnerFunction = true;
132                }
133                if (!JSScript::isValidOffset(inner->globalsOffset) &&
134                    !JSScript::isValidOffset(inner->objectsOffset)) {
135                    continue;
136                }
137                if (!worklist.append(inner))
138                    return false;
139            }
140        }
141
142        if (!JSScript::isValidOffset(outer->globalsOffset))
143            continue;
144
145        GlobalSlotArray *globalUses = outer->globals();
146        uint32_t nGlobalUses = globalUses->length;
147        for (uint32_t i = 0; i < nGlobalUses; i++) {
148            uint32_t index = globalUses->vector[i].slot;
149            JS_ASSERT(index < globalScope.defs.length());
150            globalUses->vector[i].slot = globalScope.defs[index].knownSlot;
151        }
152    }
153
154    return true;
155}
156
157JSScript *
158frontend::CompileScript(JSContext *cx, JSObject *scopeChain, StackFrame *callerFrame,
159                        JSPrincipals *principals, JSPrincipals *originPrincipals,
160                        uint32_t tcflags,
161                        const jschar *chars, size_t length,
162                        const char *filename, uintN lineno, JSVersion version,
163                        JSString *source /* = NULL */,
164                        uintN staticLevel /* = 0 */)
165{
166    TokenKind tt;
167    ParseNode *pn;
168    JSScript *script;
169    bool inDirectivePrologue;
170
171    JS_ASSERT(!(tcflags & ~(TCF_COMPILE_N_GO | TCF_NO_SCRIPT_RVAL | TCF_COMPILE_FOR_EVAL
172                            | TCF_NEED_SCRIPT_GLOBAL)));
173
174    /*
175     * The scripted callerFrame can only be given for compile-and-go scripts
176     * and non-zero static level requires callerFrame.
177     */
178    JS_ASSERT_IF(callerFrame, tcflags & TCF_COMPILE_N_GO);
179    JS_ASSERT_IF(staticLevel != 0, callerFrame);
180
181    Parser parser(cx, principals, originPrincipals, callerFrame);
182    if (!parser.init(chars, length, filename, lineno, version))
183        return NULL;
184
185    TokenStream &tokenStream = parser.tokenStream;
186
187    BytecodeEmitter bce(&parser, tokenStream.getLineno());
188    if (!bce.init(cx, TreeContext::USED_AS_TREE_CONTEXT))
189        return NULL;
190
191    Probes::compileScriptBegin(cx, filename, lineno);
192    MUST_FLOW_THROUGH("out");
193
194    // We can specialize a bit for the given scope chain if that scope chain is the global object.
195    JSObject *globalObj = scopeChain && scopeChain == &scopeChain->global()
196                          ? &scopeChain->global()
197                          : NULL;
198
199    JS_ASSERT_IF(globalObj, globalObj->isNative());
200    JS_ASSERT_IF(globalObj, JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(globalObj->getClass()));
201
202    /* Null script early in case of error, to reduce our code footprint. */
203    script = NULL;
204
205    GlobalScope globalScope(cx, globalObj, &bce);
206    bce.flags |= tcflags;
207    bce.setScopeChain(scopeChain);
208    bce.globalScope = &globalScope;
209    if (!SetStaticLevel(&bce, staticLevel))
210        goto out;
211
212    /* If this is a direct call to eval, inherit the caller's strictness.  */
213    if (callerFrame &&
214        callerFrame->isScriptFrame() &&
215        callerFrame->script()->strictModeCode) {
216        bce.flags |= TCF_STRICT_MODE_CODE;
217        tokenStream.setStrictMode();
218    }
219
220#ifdef DEBUG
221    bool savedCallerFun;
222    savedCallerFun = false;
223#endif
224    if (tcflags & TCF_COMPILE_N_GO) {
225        if (source) {
226            /*
227             * Save eval program source in script->atoms[0] for the
228             * eval cache (see EvalCacheLookup in jsobj.cpp).
229             */
230            JSAtom *atom = js_AtomizeString(cx, source);
231            jsatomid _;
232            if (!atom || !bce.makeAtomIndex(atom, &_))
233                goto out;
234        }
235
236        if (callerFrame && callerFrame->isFunctionFrame()) {
237            /*
238             * An eval script in a caller frame needs to have its enclosing
239             * function captured in case it refers to an upvar, and someone
240             * wishes to decompile it while it's running.
241             */
242            ObjectBox *funbox = parser.newObjectBox(callerFrame->fun());
243            if (!funbox)
244                goto out;
245            funbox->emitLink = bce.objectList.lastbox;
246            bce.objectList.lastbox = funbox;
247            bce.objectList.length++;
248#ifdef DEBUG
249            savedCallerFun = true;
250#endif
251        }
252    }
253
254    /*
255     * Inline this->statements to emit as we go to save AST space. We must
256     * generate our script-body blockid since we aren't calling Statements.
257     */
258    uint32_t bodyid;
259    if (!GenerateBlockId(&bce, bodyid))
260        goto out;
261    bce.bodyid = bodyid;
262
263#if JS_HAS_XML_SUPPORT
264    pn = NULL;
265    bool onlyXML;
266    onlyXML = true;
267#endif
268
269    inDirectivePrologue = true;
270    tokenStream.setOctalCharacterEscape(false);
271    for (;;) {
272        tt = tokenStream.peekToken(TSF_OPERAND);
273        if (tt <= TOK_EOF) {
274            if (tt == TOK_EOF)
275                break;
276            JS_ASSERT(tt == TOK_ERROR);
277            goto out;
278        }
279
280        pn = parser.statement();
281        if (!pn)
282            goto out;
283        JS_ASSERT(!bce.blockNode);
284
285        if (inDirectivePrologue && !parser.recognizeDirectivePrologue(pn, &inDirectivePrologue))
286            goto out;
287
288        if (!FoldConstants(cx, pn, &bce))
289            goto out;
290
291        if (!AnalyzeFunctions(&bce))
292            goto out;
293        bce.functionList = NULL;
294
295        if (!EmitTree(cx, &bce, pn))
296            goto out;
297
298#if JS_HAS_XML_SUPPORT
299        if (!pn->isKind(PNK_SEMI) || !pn->pn_kid || !pn->pn_kid->isXMLItem())
300            onlyXML = false;
301#endif
302        bce.freeTree(pn);
303    }
304
305#if JS_HAS_XML_SUPPORT
306    /*
307     * Prevent XML data theft via <script src="http://victim.com/foo.xml">.
308     * For background, see:
309     *
310     * https://bugzilla.mozilla.org/show_bug.cgi?id=336551
311     */
312    if (pn && onlyXML && !callerFrame) {
313        parser.reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_XML_WHOLE_PROGRAM);
314        goto out;
315    }
316#endif
317
318    /*
319     * Nowadays the threaded interpreter needs a stop instruction, so we
320     * do have to emit that here.
321     */
322    if (Emit1(cx, &bce, JSOP_STOP) < 0)
323        goto out;
324
325    JS_ASSERT(bce.version() == version);
326
327    script = JSScript::NewScriptFromEmitter(cx, &bce);
328    if (!script)
329        goto out;
330
331    JS_ASSERT(script->savedCallerFun == savedCallerFun);
332
333    if (!DefineGlobals(cx, globalScope, script))
334        script = NULL;
335
336  out:
337    Probes::compileScriptEnd(cx, script, filename, lineno);
338    return script;
339}
340
341/*
342 * Compile a JS function body, which might appear as the value of an event
343 * handler attribute in an HTML <INPUT> tag.
344 */
345bool
346frontend::CompileFunctionBody(JSContext *cx, JSFunction *fun,
347                              JSPrincipals *principals, JSPrincipals *originPrincipals,
348                              Bindings *bindings, const jschar *chars, size_t length,
349                              const char *filename, uintN lineno, JSVersion version)
350{
351    Parser parser(cx, principals, originPrincipals);
352    if (!parser.init(chars, length, filename, lineno, version))
353        return false;
354
355    TokenStream &tokenStream = parser.tokenStream;
356
357    BytecodeEmitter funbce(&parser, tokenStream.getLineno());
358    if (!funbce.init(cx, TreeContext::USED_AS_TREE_CONTEXT))
359        return false;
360
361    funbce.flags |= TCF_IN_FUNCTION;
362    funbce.setFunction(fun);
363    funbce.bindings.transfer(cx, bindings);
364    fun->setArgCount(funbce.bindings.countArgs());
365    if (!GenerateBlockId(&funbce, funbce.bodyid))
366        return false;
367
368    /* FIXME: make Function format the source for a function definition. */
369    ParseNode *fn = FunctionNode::create(PNK_NAME, &funbce);
370    if (fn) {
371        fn->pn_body = NULL;
372        fn->pn_cookie.makeFree();
373
374        uintN nargs = fun->nargs;
375        if (nargs) {
376            /*
377             * NB: do not use AutoLocalNameArray because it will release space
378             * allocated from cx->tempLifoAlloc by DefineArg.
379             */
380            Vector<JSAtom *> names(cx);
381            if (!funbce.bindings.getLocalNameArray(cx, &names)) {
382                fn = NULL;
383            } else {
384                for (uintN i = 0; i < nargs; i++) {
385                    if (!DefineArg(fn, names[i], i, &funbce)) {
386                        fn = NULL;
387                        break;
388                    }
389                }
390            }
391        }
392    }
393
394    /*
395     * After we're done parsing, we must fold constants, analyze any nested
396     * functions, and generate code for this function, including a stop opcode
397     * at the end.
398     */
399    ParseNode *pn = fn ? parser.functionBody(Parser::StatementListBody) : NULL;
400    if (pn) {
401        if (!CheckStrictParameters(cx, &funbce)) {
402            pn = NULL;
403        } else if (!tokenStream.matchToken(TOK_EOF)) {
404            parser.reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_SYNTAX_ERROR);
405            pn = NULL;
406        } else if (!FoldConstants(cx, pn, &funbce)) {
407            /* FoldConstants reported the error already. */
408            pn = NULL;
409        } else if (!AnalyzeFunctions(&funbce)) {
410            pn = NULL;
411        } else {
412            if (fn->pn_body) {
413                JS_ASSERT(fn->pn_body->isKind(PNK_ARGSBODY));
414                fn->pn_body->append(pn);
415                fn->pn_body->pn_pos = pn->pn_pos;
416                pn = fn->pn_body;
417            }
418
419            if (!EmitFunctionScript(cx, &funbce, pn))
420                pn = NULL;
421        }
422    }
423
424    return pn != NULL;
425}