|
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/. */ |
|
6 |
|
7 #include "frontend/NameFunctions.h" |
|
8 |
|
9 #include "jsfun.h" |
|
10 #include "jsprf.h" |
|
11 |
|
12 #include "frontend/BytecodeCompiler.h" |
|
13 #include "frontend/ParseNode.h" |
|
14 #include "frontend/SharedContext.h" |
|
15 #include "vm/StringBuffer.h" |
|
16 |
|
17 using namespace js; |
|
18 using namespace js::frontend; |
|
19 |
|
20 namespace { |
|
21 |
|
22 class NameResolver |
|
23 { |
|
24 static const size_t MaxParents = 100; |
|
25 |
|
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 */ |
|
30 |
|
31 /* Test whether a ParseNode represents a function invocation */ |
|
32 bool call(ParseNode *pn) { |
|
33 return pn && pn->isKind(PNK_CALL); |
|
34 } |
|
35 |
|
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); |
|
49 |
|
50 /* Quote the string as needed. */ |
|
51 JSString *source = js_QuoteString(cx, name, '"'); |
|
52 return source && buf->append("[") && buf->append(source) && buf->append("]"); |
|
53 } |
|
54 |
|
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 } |
|
61 |
|
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 } |
|
66 |
|
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); |
|
75 |
|
76 case PNK_NAME: |
|
77 return buf->append(n->pn_atom); |
|
78 |
|
79 case PNK_THIS: |
|
80 return buf->append("this"); |
|
81 |
|
82 case PNK_ELEM: |
|
83 return nameExpression(n->pn_left) && |
|
84 buf->append("[") && |
|
85 nameExpression(n->pn_right) && |
|
86 buf->append("]"); |
|
87 |
|
88 case PNK_NUMBER: |
|
89 return appendNumber(n->pn_dval); |
|
90 |
|
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 } |
|
100 |
|
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; |
|
116 |
|
117 for (int pos = nparents - 1; pos >= 0; pos--) { |
|
118 ParseNode *cur = parents[pos]; |
|
119 if (cur->isAssignment()) |
|
120 return cur; |
|
121 |
|
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 */ |
|
126 |
|
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; |
|
151 |
|
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 */ |
|
159 |
|
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 } |
|
167 |
|
168 return nullptr; |
|
169 } |
|
170 |
|
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()); |
|
179 |
|
180 StringBuffer buf(cx); |
|
181 this->buf = &buf; |
|
182 |
|
183 retAtom.set(nullptr); |
|
184 |
|
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 } |
|
198 |
|
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; |
|
202 |
|
203 /* Gather all nodes relevant to naming */ |
|
204 ParseNode *toName[MaxParents]; |
|
205 size_t size; |
|
206 ParseNode *assignment = gatherNameable(toName, &size); |
|
207 |
|
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 } |
|
215 |
|
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]; |
|
223 |
|
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 } |
|
242 |
|
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; |
|
250 |
|
251 if (buf.empty()) |
|
252 return true; |
|
253 |
|
254 retAtom.set(buf.finishAtom()); |
|
255 if (!retAtom) |
|
256 return false; |
|
257 fun->setGuessedAtom(retAtom); |
|
258 return true; |
|
259 } |
|
260 |
|
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 } |
|
269 |
|
270 public: |
|
271 explicit NameResolver(ExclusiveContext *cx) : cx(cx), nparents(0), buf(nullptr) {} |
|
272 |
|
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; |
|
282 |
|
283 if (cur->isKind(PNK_FUNCTION) && cur->isArity(PN_CODE)) { |
|
284 RootedAtom prefix2(cx); |
|
285 if (!resolveFun(cur, prefix, &prefix2)) |
|
286 return false; |
|
287 |
|
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; |
|
300 |
|
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; |
|
316 |
|
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 }; |
|
350 |
|
351 } /* anonymous namespace */ |
|
352 |
|
353 bool |
|
354 frontend::NameFunctions(ExclusiveContext *cx, ParseNode *pn) |
|
355 { |
|
356 NameResolver nr(cx); |
|
357 return nr.resolve(pn); |
|
358 } |