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 :
52 : using namespace js;
53 : using namespace js::frontend;
54 :
55 : bool
56 120672 : DefineGlobals(JSContext *cx, GlobalScope &globalScope, JSScript *script)
57 : {
58 120672 : JSObject *globalObj = globalScope.globalObj;
59 :
60 : /* Define and update global properties. */
61 188613 : for (size_t i = 0; i < globalScope.defs.length(); i++) {
62 67941 : GlobalScope::GlobalDef &def = globalScope.defs[i];
63 :
64 : /* Names that could be resolved ahead of time can be skipped. */
65 67941 : if (!def.atom)
66 39 : continue;
67 :
68 67902 : jsid id = ATOM_TO_JSID(def.atom);
69 : Value rval;
70 :
71 67902 : if (def.funbox) {
72 22026 : 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 22026 : rval.setObject(*fun);
79 22026 : types::AddTypePropertyId(cx, globalObj, id, rval);
80 : } else {
81 45876 : 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 67902 : JSPROP_ENUMERATE | JSPROP_PERMANENT, 0, 0, DNP_SKIP_TYPE);
94 67902 : if (!shape)
95 0 : return false;
96 67902 : def.knownSlot = shape->slot();
97 : }
98 :
99 241344 : Vector<JSScript *, 16> worklist(cx);
100 120672 : if (!worklist.append(script))
101 0 : 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 664910 : while (worklist.length()) {
110 423566 : JSScript *outer = worklist.back();
111 423566 : worklist.popBack();
112 :
113 423566 : if (JSScript::isValidOffset(outer->objectsOffset)) {
114 382186 : 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 382186 : size_t start = outer->savedCallerFun ? 1 : 0;
121 :
122 1765907 : for (size_t i = start; i < arr->length; i++) {
123 1383721 : JSObject *obj = arr->vector[i];
124 1383721 : if (!obj->isFunction())
125 405819 : continue;
126 977902 : JSFunction *fun = obj->toFunction();
127 977902 : JS_ASSERT(fun->isInterpreted());
128 977902 : JSScript *inner = fun->script();
129 977902 : if (outer->function() && outer->function()->isHeavyweight()) {
130 134868 : outer->isOuterFunction = true;
131 134868 : inner->isInnerFunction = true;
132 : }
133 1954624 : if (!JSScript::isValidOffset(inner->globalsOffset) &&
134 976722 : !JSScript::isValidOffset(inner->objectsOffset)) {
135 675008 : continue;
136 : }
137 302894 : if (!worklist.append(inner))
138 0 : return false;
139 : }
140 : }
141 :
142 423566 : if (!JSScript::isValidOffset(outer->globalsOffset))
143 410767 : continue;
144 :
145 12799 : GlobalSlotArray *globalUses = outer->globals();
146 12799 : uint32_t nGlobalUses = globalUses->length;
147 36527 : for (uint32_t i = 0; i < nGlobalUses; i++) {
148 23728 : uint32_t index = globalUses->vector[i].slot;
149 23728 : JS_ASSERT(index < globalScope.defs.length());
150 23728 : globalUses->vector[i].slot = globalScope.defs[index].knownSlot;
151 : }
152 : }
153 :
154 120672 : return true;
155 : }
156 :
157 : JSScript *
158 120957 : frontend::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, unsigned lineno, JSVersion version,
163 : JSString *source /* = NULL */,
164 : unsigned staticLevel /* = 0 */)
165 : {
166 : TokenKind tt;
167 : ParseNode *pn;
168 : JSScript *script;
169 : bool inDirectivePrologue;
170 :
171 0 : JS_ASSERT(!(tcflags & ~(TCF_COMPILE_N_GO | TCF_NO_SCRIPT_RVAL | TCF_COMPILE_FOR_EVAL
172 120957 : | 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 120957 : JS_ASSERT_IF(callerFrame, tcflags & TCF_COMPILE_N_GO);
179 120957 : JS_ASSERT_IF(staticLevel != 0, callerFrame);
180 :
181 241914 : Parser parser(cx, principals, originPrincipals, callerFrame);
182 120957 : if (!parser.init(chars, length, filename, lineno, version))
183 0 : return NULL;
184 :
185 120957 : TokenStream &tokenStream = parser.tokenStream;
186 :
187 241914 : BytecodeEmitter bce(&parser, tokenStream.getLineno());
188 120957 : if (!bce.init(cx, TreeContext::USED_AS_TREE_CONTEXT))
189 0 : return NULL;
190 :
191 120957 : 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 120957 : JSObject *globalObj = scopeChain && scopeChain == &scopeChain->global()
196 : ? &scopeChain->global()
197 241914 : : NULL;
198 :
199 120957 : JS_ASSERT_IF(globalObj, globalObj->isNative());
200 120957 : 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 120957 : script = NULL;
204 :
205 241914 : GlobalScope globalScope(cx, globalObj, &bce);
206 120957 : bce.flags |= tcflags;
207 120957 : bce.setScopeChain(scopeChain);
208 120957 : bce.globalScope = &globalScope;
209 120957 : if (!SetStaticLevel(&bce, staticLevel))
210 0 : goto out;
211 :
212 : /* If this is a direct call to eval, inherit the caller's strictness. */
213 167823 : if (callerFrame &&
214 23433 : callerFrame->isScriptFrame() &&
215 23433 : callerFrame->script()->strictModeCode) {
216 659 : bce.flags |= TCF_STRICT_MODE_CODE;
217 659 : tokenStream.setStrictMode();
218 : }
219 :
220 : #ifdef DEBUG
221 : bool savedCallerFun;
222 120957 : savedCallerFun = false;
223 : #endif
224 120957 : if (tcflags & TCF_COMPILE_N_GO) {
225 104305 : if (source) {
226 : /*
227 : * Save eval program source in script->atoms[0] for the
228 : * eval cache (see EvalCacheLookup in jsobj.cpp).
229 : */
230 34313 : JSAtom *atom = js_AtomizeString(cx, source);
231 : jsatomid _;
232 34313 : if (!atom || !bce.makeAtomIndex(atom, &_))
233 0 : goto out;
234 : }
235 :
236 104305 : 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 21204 : ObjectBox *funbox = parser.newObjectBox(callerFrame->fun());
243 21204 : if (!funbox)
244 0 : goto out;
245 21204 : funbox->emitLink = bce.objectList.lastbox;
246 21204 : bce.objectList.lastbox = funbox;
247 21204 : bce.objectList.length++;
248 : #ifdef DEBUG
249 21204 : 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 120957 : if (!GenerateBlockId(&bce, bodyid))
260 0 : goto out;
261 120957 : bce.bodyid = bodyid;
262 :
263 : #if JS_HAS_XML_SUPPORT
264 120957 : pn = NULL;
265 : bool onlyXML;
266 120957 : onlyXML = true;
267 : #endif
268 :
269 120957 : inDirectivePrologue = true;
270 120957 : tokenStream.setOctalCharacterEscape(false);
271 891367 : for (;;) {
272 1012324 : tt = tokenStream.peekToken(TSF_OPERAND);
273 1012324 : if (tt <= TOK_EOF) {
274 120681 : if (tt == TOK_EOF)
275 : break;
276 9 : JS_ASSERT(tt == TOK_ERROR);
277 9 : goto out;
278 : }
279 :
280 891643 : pn = parser.statement();
281 891643 : if (!pn)
282 276 : goto out;
283 891367 : JS_ASSERT(!bce.blockNode);
284 :
285 891367 : if (inDirectivePrologue && !parser.recognizeDirectivePrologue(pn, &inDirectivePrologue))
286 0 : goto out;
287 :
288 891367 : if (!FoldConstants(cx, pn, &bce))
289 0 : goto out;
290 :
291 891367 : if (!AnalyzeFunctions(&bce))
292 0 : goto out;
293 891367 : bce.functionList = NULL;
294 :
295 891367 : if (!EmitTree(cx, &bce, pn))
296 0 : goto out;
297 :
298 : #if JS_HAS_XML_SUPPORT
299 891367 : if (!pn->isKind(PNK_SEMI) || !pn->pn_kid || !pn->pn_kid->isXMLItem())
300 891367 : onlyXML = false;
301 : #endif
302 891367 : 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 241041 : if (pn && onlyXML && !callerFrame) {
313 0 : parser.reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_XML_WHOLE_PROGRAM);
314 0 : 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 120672 : if (Emit1(cx, &bce, JSOP_STOP) < 0)
323 0 : goto out;
324 :
325 120672 : JS_ASSERT(bce.version() == version);
326 :
327 120672 : script = JSScript::NewScriptFromEmitter(cx, &bce);
328 120672 : if (!script)
329 0 : goto out;
330 :
331 120672 : JS_ASSERT(script->savedCallerFun == savedCallerFun);
332 :
333 120672 : if (!DefineGlobals(cx, globalScope, script))
334 0 : script = NULL;
335 :
336 : out:
337 120957 : Probes::compileScriptEnd(cx, script, filename, lineno);
338 120957 : 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 : */
345 : bool
346 10413 : frontend::CompileFunctionBody(JSContext *cx, JSFunction *fun,
347 : JSPrincipals *principals, JSPrincipals *originPrincipals,
348 : Bindings *bindings, const jschar *chars, size_t length,
349 : const char *filename, unsigned lineno, JSVersion version)
350 : {
351 20826 : Parser parser(cx, principals, originPrincipals);
352 10413 : if (!parser.init(chars, length, filename, lineno, version))
353 0 : return false;
354 :
355 10413 : TokenStream &tokenStream = parser.tokenStream;
356 :
357 20826 : BytecodeEmitter funbce(&parser, tokenStream.getLineno());
358 10413 : if (!funbce.init(cx, TreeContext::USED_AS_TREE_CONTEXT))
359 0 : return false;
360 :
361 10413 : funbce.flags |= TCF_IN_FUNCTION;
362 10413 : funbce.setFunction(fun);
363 10413 : funbce.bindings.transfer(cx, bindings);
364 10413 : fun->setArgCount(funbce.bindings.countArgs());
365 10413 : if (!GenerateBlockId(&funbce, funbce.bodyid))
366 0 : return false;
367 :
368 : /* FIXME: make Function format the source for a function definition. */
369 10413 : ParseNode *fn = FunctionNode::create(PNK_NAME, &funbce);
370 10413 : if (fn) {
371 10413 : fn->pn_body = NULL;
372 10413 : fn->pn_cookie.makeFree();
373 :
374 10413 : unsigned nargs = fun->nargs;
375 10413 : if (nargs) {
376 : /*
377 : * NB: do not use AutoLocalNameArray because it will release space
378 : * allocated from cx->tempLifoAlloc by DefineArg.
379 : */
380 9448 : Vector<JSAtom *> names(cx);
381 4724 : if (!funbce.bindings.getLocalNameArray(cx, &names)) {
382 0 : fn = NULL;
383 : } else {
384 9457 : for (unsigned i = 0; i < nargs; i++) {
385 4733 : if (!DefineArg(fn, names[i], i, &funbce)) {
386 0 : fn = NULL;
387 0 : 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 10413 : ParseNode *pn = fn ? parser.functionBody(Parser::StatementListBody) : NULL;
400 10413 : if (pn) {
401 10105 : if (!CheckStrictParameters(cx, &funbce)) {
402 0 : pn = NULL;
403 10105 : } else if (!tokenStream.matchToken(TOK_EOF)) {
404 0 : parser.reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_SYNTAX_ERROR);
405 0 : pn = NULL;
406 10105 : } else if (!FoldConstants(cx, pn, &funbce)) {
407 : /* FoldConstants reported the error already. */
408 0 : pn = NULL;
409 10105 : } else if (!AnalyzeFunctions(&funbce)) {
410 0 : pn = NULL;
411 : } else {
412 10105 : if (fn->pn_body) {
413 4722 : JS_ASSERT(fn->pn_body->isKind(PNK_ARGSBODY));
414 4722 : fn->pn_body->append(pn);
415 4722 : fn->pn_body->pn_pos = pn->pn_pos;
416 4722 : pn = fn->pn_body;
417 : }
418 :
419 10105 : if (!EmitFunctionScript(cx, &funbce, pn))
420 0 : pn = NULL;
421 : }
422 : }
423 :
424 10413 : return pn != NULL;
425 : }
|