Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "frontend/NameFunctions.h"
9 #include "jsfun.h"
10 #include "jsprf.h"
12 #include "frontend/BytecodeCompiler.h"
13 #include "frontend/ParseNode.h"
14 #include "frontend/SharedContext.h"
15 #include "vm/StringBuffer.h"
17 using namespace js;
18 using namespace js::frontend;
20 namespace {
22 class NameResolver
23 {
24 static const size_t MaxParents = 100;
26 ExclusiveContext *cx;
27 size_t nparents; /* number of parents in the parents array */
28 ParseNode *parents[MaxParents]; /* history of ParseNodes we've been looking at */
29 StringBuffer *buf; /* when resolving, buffer to append to */
31 /* Test whether a ParseNode represents a function invocation */
32 bool call(ParseNode *pn) {
33 return pn && pn->isKind(PNK_CALL);
34 }
36 /*
37 * Append a reference to a property named |name| to |buf|. If |name| is
38 * a proper identifier name, then we append '.name'; otherwise, we
39 * append '["name"]'.
40 *
41 * Note that we need the IsIdentifier check for atoms from both
42 * PNK_NAME nodes and PNK_STRING nodes: given code like a["b c"], the
43 * front end will produce a PNK_DOT with a PNK_NAME child whose name
44 * contains spaces.
45 */
46 bool appendPropertyReference(JSAtom *name) {
47 if (IsIdentifier(name))
48 return buf->append(".") && buf->append(name);
50 /* Quote the string as needed. */
51 JSString *source = js_QuoteString(cx, name, '"');
52 return source && buf->append("[") && buf->append(source) && buf->append("]");
53 }
55 /* Append a number to buf. */
56 bool appendNumber(double n) {
57 char number[30];
58 int digits = JS_snprintf(number, sizeof(number), "%g", n);
59 return buf->appendInflated(number, digits);
60 }
62 /* Append "[<n>]" to buf, referencing a property named by a numeric literal. */
63 bool appendNumericPropertyReference(double n) {
64 return buf->append("[") && appendNumber(n) && buf->append("]");
65 }
67 /*
68 * Walk over the given ParseNode, converting it to a stringified name that
69 * respresents where the function is being assigned to.
70 */
71 bool nameExpression(ParseNode *n) {
72 switch (n->getKind()) {
73 case PNK_DOT:
74 return nameExpression(n->expr()) && appendPropertyReference(n->pn_atom);
76 case PNK_NAME:
77 return buf->append(n->pn_atom);
79 case PNK_THIS:
80 return buf->append("this");
82 case PNK_ELEM:
83 return nameExpression(n->pn_left) &&
84 buf->append("[") &&
85 nameExpression(n->pn_right) &&
86 buf->append("]");
88 case PNK_NUMBER:
89 return appendNumber(n->pn_dval);
91 default:
92 /*
93 * Technically this isn't an "abort" situation, we're just confused
94 * on what to call this function, but failures in naming aren't
95 * treated as fatal.
96 */
97 return false;
98 }
99 }
101 /*
102 * When naming an anonymous function, the process works loosely by walking
103 * up the AST and then translating that to a string. The stringification
104 * happens from some far-up assignment and then going back down the parse
105 * tree to the function definition point.
106 *
107 * This function will walk up the parse tree, gathering relevant nodes used
108 * for naming, and return the assignment node if there is one. The provided
109 * array and size will be filled in, and the returned node could be nullptr
110 * if no assignment is found. The first element of the array will be the
111 * innermost node relevant to naming, and the last element will be the
112 * outermost node.
113 */
114 ParseNode *gatherNameable(ParseNode **nameable, size_t *size) {
115 *size = 0;
117 for (int pos = nparents - 1; pos >= 0; pos--) {
118 ParseNode *cur = parents[pos];
119 if (cur->isAssignment())
120 return cur;
122 switch (cur->getKind()) {
123 case PNK_NAME: return cur; /* found the initialized declaration */
124 case PNK_THIS: return cur; /* Setting a property of 'this'. */
125 case PNK_FUNCTION: return nullptr; /* won't find an assignment or declaration */
127 case PNK_RETURN:
128 /*
129 * Normally the relevant parent of a node is its direct parent, but
130 * sometimes with code like:
131 *
132 * var foo = (function() { return function() {}; })();
133 *
134 * the outer function is just a helper to create a scope for the
135 * returned function. Hence the name of the returned function should
136 * actually be 'foo'. This loop sees if the current node is a
137 * PNK_RETURN, and if there is a direct function call we skip to
138 * that.
139 */
140 for (int tmp = pos - 1; tmp > 0; tmp--) {
141 if (isDirectCall(tmp, cur)) {
142 pos = tmp;
143 break;
144 } else if (call(cur)) {
145 /* Don't skip too high in the tree */
146 break;
147 }
148 cur = parents[tmp];
149 }
150 break;
152 case PNK_COLON:
153 /*
154 * Record the PNK_COLON but skip the PNK_OBJECT so we're not
155 * flagged as a contributor.
156 */
157 pos--;
158 /* fallthrough */
160 default:
161 /* Save any other nodes we encounter on the way up. */
162 JS_ASSERT(*size < MaxParents);
163 nameable[(*size)++] = cur;
164 break;
165 }
166 }
168 return nullptr;
169 }
171 /*
172 * Resolve the name of a function. If the function already has a name
173 * listed, then it is skipped. Otherwise an intelligent name is guessed to
174 * assign to the function's displayAtom field
175 */
176 bool resolveFun(ParseNode *pn, HandleAtom prefix, MutableHandleAtom retAtom) {
177 JS_ASSERT(pn != nullptr && pn->isKind(PNK_FUNCTION));
178 RootedFunction fun(cx, pn->pn_funbox->function());
180 StringBuffer buf(cx);
181 this->buf = &buf;
183 retAtom.set(nullptr);
185 /* If the function already has a name, use that */
186 if (fun->displayAtom() != nullptr) {
187 if (prefix == nullptr) {
188 retAtom.set(fun->displayAtom());
189 return true;
190 }
191 if (!buf.append(prefix) ||
192 !buf.append("/") ||
193 !buf.append(fun->displayAtom()))
194 return false;
195 retAtom.set(buf.finishAtom());
196 return !!retAtom;
197 }
199 /* If a prefix is specified, then it is a form of namespace */
200 if (prefix != nullptr && (!buf.append(prefix) || !buf.append("/")))
201 return false;
203 /* Gather all nodes relevant to naming */
204 ParseNode *toName[MaxParents];
205 size_t size;
206 ParseNode *assignment = gatherNameable(toName, &size);
208 /* If the function is assigned to something, then that is very relevant */
209 if (assignment) {
210 if (assignment->isAssignment())
211 assignment = assignment->pn_left;
212 if (!nameExpression(assignment))
213 return true;
214 }
216 /*
217 * Other than the actual assignment, other relevant nodes to naming are
218 * those in object initializers and then particular nodes marking a
219 * contribution.
220 */
221 for (int pos = size - 1; pos >= 0; pos--) {
222 ParseNode *node = toName[pos];
224 if (node->isKind(PNK_COLON)) {
225 ParseNode *left = node->pn_left;
226 if (left->isKind(PNK_NAME) || left->isKind(PNK_STRING)) {
227 if (!appendPropertyReference(left->pn_atom))
228 return false;
229 } else if (left->isKind(PNK_NUMBER)) {
230 if (!appendNumericPropertyReference(left->pn_dval))
231 return false;
232 }
233 } else {
234 /*
235 * Don't have consecutive '<' characters, and also don't start
236 * with a '<' character.
237 */
238 if (!buf.empty() && *(buf.end() - 1) != '<' && !buf.append("<"))
239 return false;
240 }
241 }
243 /*
244 * functions which are "genuinely anonymous" but are contained in some
245 * other namespace are rather considered as "contributing" to the outer
246 * function, so give them a contribution symbol here.
247 */
248 if (!buf.empty() && *(buf.end() - 1) == '/' && !buf.append("<"))
249 return false;
251 if (buf.empty())
252 return true;
254 retAtom.set(buf.finishAtom());
255 if (!retAtom)
256 return false;
257 fun->setGuessedAtom(retAtom);
258 return true;
259 }
261 /*
262 * Tests whether parents[pos] is a function call whose callee is cur.
263 * This is the case for functions which do things like simply create a scope
264 * for new variables and then return an anonymous function using this scope.
265 */
266 bool isDirectCall(int pos, ParseNode *cur) {
267 return pos >= 0 && call(parents[pos]) && parents[pos]->pn_head == cur;
268 }
270 public:
271 explicit NameResolver(ExclusiveContext *cx) : cx(cx), nparents(0), buf(nullptr) {}
273 /*
274 * Resolve all names for anonymous functions recursively within the
275 * ParseNode instance given. The prefix is for each subsequent name, and
276 * should initially be nullptr.
277 */
278 bool resolve(ParseNode *cur, HandleAtom prefixArg = js::NullPtr()) {
279 RootedAtom prefix(cx, prefixArg);
280 if (cur == nullptr)
281 return true;
283 if (cur->isKind(PNK_FUNCTION) && cur->isArity(PN_CODE)) {
284 RootedAtom prefix2(cx);
285 if (!resolveFun(cur, prefix, &prefix2))
286 return false;
288 /*
289 * If a function looks like (function(){})() where the parent node
290 * of the definition of the function is a call, then it shouldn't
291 * contribute anything to the namespace, so don't bother updating
292 * the prefix to whatever was returned.
293 */
294 if (!isDirectCall(nparents - 1, cur))
295 prefix = prefix2;
296 }
297 if (nparents >= MaxParents)
298 return true;
299 parents[nparents++] = cur;
301 switch (cur->getArity()) {
302 case PN_NULLARY:
303 break;
304 case PN_NAME:
305 if (!resolve(cur->maybeExpr(), prefix))
306 return false;
307 break;
308 case PN_UNARY:
309 if (!resolve(cur->pn_kid, prefix))
310 return false;
311 break;
312 case PN_BINARY:
313 case PN_BINARY_OBJ:
314 if (!resolve(cur->pn_left, prefix))
315 return false;
317 /*
318 * FIXME? Occasionally pn_left == pn_right for something like
319 * destructuring assignment in (function({foo}){}), so skip the
320 * duplicate here if this is the case because we want to traverse
321 * everything at most once.
322 */
323 if (cur->pn_left != cur->pn_right)
324 if (!resolve(cur->pn_right, prefix))
325 return false;
326 break;
327 case PN_TERNARY:
328 if (!resolve(cur->pn_kid1, prefix))
329 return false;
330 if (!resolve(cur->pn_kid2, prefix))
331 return false;
332 if (!resolve(cur->pn_kid3, prefix))
333 return false;
334 break;
335 case PN_CODE:
336 JS_ASSERT(cur->isKind(PNK_FUNCTION));
337 if (!resolve(cur->pn_body, prefix))
338 return false;
339 break;
340 case PN_LIST:
341 for (ParseNode *nxt = cur->pn_head; nxt; nxt = nxt->pn_next)
342 if (!resolve(nxt, prefix))
343 return false;
344 break;
345 }
346 nparents--;
347 return true;
348 }
349 };
351 } /* anonymous namespace */
353 bool
354 frontend::NameFunctions(ExclusiveContext *cx, ParseNode *pn)
355 {
356 NameResolver nr(cx);
357 return nr.resolve(pn);
358 }