1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/src/frontend/NameFunctions.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,358 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99: 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "frontend/NameFunctions.h" 1.11 + 1.12 +#include "jsfun.h" 1.13 +#include "jsprf.h" 1.14 + 1.15 +#include "frontend/BytecodeCompiler.h" 1.16 +#include "frontend/ParseNode.h" 1.17 +#include "frontend/SharedContext.h" 1.18 +#include "vm/StringBuffer.h" 1.19 + 1.20 +using namespace js; 1.21 +using namespace js::frontend; 1.22 + 1.23 +namespace { 1.24 + 1.25 +class NameResolver 1.26 +{ 1.27 + static const size_t MaxParents = 100; 1.28 + 1.29 + ExclusiveContext *cx; 1.30 + size_t nparents; /* number of parents in the parents array */ 1.31 + ParseNode *parents[MaxParents]; /* history of ParseNodes we've been looking at */ 1.32 + StringBuffer *buf; /* when resolving, buffer to append to */ 1.33 + 1.34 + /* Test whether a ParseNode represents a function invocation */ 1.35 + bool call(ParseNode *pn) { 1.36 + return pn && pn->isKind(PNK_CALL); 1.37 + } 1.38 + 1.39 + /* 1.40 + * Append a reference to a property named |name| to |buf|. If |name| is 1.41 + * a proper identifier name, then we append '.name'; otherwise, we 1.42 + * append '["name"]'. 1.43 + * 1.44 + * Note that we need the IsIdentifier check for atoms from both 1.45 + * PNK_NAME nodes and PNK_STRING nodes: given code like a["b c"], the 1.46 + * front end will produce a PNK_DOT with a PNK_NAME child whose name 1.47 + * contains spaces. 1.48 + */ 1.49 + bool appendPropertyReference(JSAtom *name) { 1.50 + if (IsIdentifier(name)) 1.51 + return buf->append(".") && buf->append(name); 1.52 + 1.53 + /* Quote the string as needed. */ 1.54 + JSString *source = js_QuoteString(cx, name, '"'); 1.55 + return source && buf->append("[") && buf->append(source) && buf->append("]"); 1.56 + } 1.57 + 1.58 + /* Append a number to buf. */ 1.59 + bool appendNumber(double n) { 1.60 + char number[30]; 1.61 + int digits = JS_snprintf(number, sizeof(number), "%g", n); 1.62 + return buf->appendInflated(number, digits); 1.63 + } 1.64 + 1.65 + /* Append "[<n>]" to buf, referencing a property named by a numeric literal. */ 1.66 + bool appendNumericPropertyReference(double n) { 1.67 + return buf->append("[") && appendNumber(n) && buf->append("]"); 1.68 + } 1.69 + 1.70 + /* 1.71 + * Walk over the given ParseNode, converting it to a stringified name that 1.72 + * respresents where the function is being assigned to. 1.73 + */ 1.74 + bool nameExpression(ParseNode *n) { 1.75 + switch (n->getKind()) { 1.76 + case PNK_DOT: 1.77 + return nameExpression(n->expr()) && appendPropertyReference(n->pn_atom); 1.78 + 1.79 + case PNK_NAME: 1.80 + return buf->append(n->pn_atom); 1.81 + 1.82 + case PNK_THIS: 1.83 + return buf->append("this"); 1.84 + 1.85 + case PNK_ELEM: 1.86 + return nameExpression(n->pn_left) && 1.87 + buf->append("[") && 1.88 + nameExpression(n->pn_right) && 1.89 + buf->append("]"); 1.90 + 1.91 + case PNK_NUMBER: 1.92 + return appendNumber(n->pn_dval); 1.93 + 1.94 + default: 1.95 + /* 1.96 + * Technically this isn't an "abort" situation, we're just confused 1.97 + * on what to call this function, but failures in naming aren't 1.98 + * treated as fatal. 1.99 + */ 1.100 + return false; 1.101 + } 1.102 + } 1.103 + 1.104 + /* 1.105 + * When naming an anonymous function, the process works loosely by walking 1.106 + * up the AST and then translating that to a string. The stringification 1.107 + * happens from some far-up assignment and then going back down the parse 1.108 + * tree to the function definition point. 1.109 + * 1.110 + * This function will walk up the parse tree, gathering relevant nodes used 1.111 + * for naming, and return the assignment node if there is one. The provided 1.112 + * array and size will be filled in, and the returned node could be nullptr 1.113 + * if no assignment is found. The first element of the array will be the 1.114 + * innermost node relevant to naming, and the last element will be the 1.115 + * outermost node. 1.116 + */ 1.117 + ParseNode *gatherNameable(ParseNode **nameable, size_t *size) { 1.118 + *size = 0; 1.119 + 1.120 + for (int pos = nparents - 1; pos >= 0; pos--) { 1.121 + ParseNode *cur = parents[pos]; 1.122 + if (cur->isAssignment()) 1.123 + return cur; 1.124 + 1.125 + switch (cur->getKind()) { 1.126 + case PNK_NAME: return cur; /* found the initialized declaration */ 1.127 + case PNK_THIS: return cur; /* Setting a property of 'this'. */ 1.128 + case PNK_FUNCTION: return nullptr; /* won't find an assignment or declaration */ 1.129 + 1.130 + case PNK_RETURN: 1.131 + /* 1.132 + * Normally the relevant parent of a node is its direct parent, but 1.133 + * sometimes with code like: 1.134 + * 1.135 + * var foo = (function() { return function() {}; })(); 1.136 + * 1.137 + * the outer function is just a helper to create a scope for the 1.138 + * returned function. Hence the name of the returned function should 1.139 + * actually be 'foo'. This loop sees if the current node is a 1.140 + * PNK_RETURN, and if there is a direct function call we skip to 1.141 + * that. 1.142 + */ 1.143 + for (int tmp = pos - 1; tmp > 0; tmp--) { 1.144 + if (isDirectCall(tmp, cur)) { 1.145 + pos = tmp; 1.146 + break; 1.147 + } else if (call(cur)) { 1.148 + /* Don't skip too high in the tree */ 1.149 + break; 1.150 + } 1.151 + cur = parents[tmp]; 1.152 + } 1.153 + break; 1.154 + 1.155 + case PNK_COLON: 1.156 + /* 1.157 + * Record the PNK_COLON but skip the PNK_OBJECT so we're not 1.158 + * flagged as a contributor. 1.159 + */ 1.160 + pos--; 1.161 + /* fallthrough */ 1.162 + 1.163 + default: 1.164 + /* Save any other nodes we encounter on the way up. */ 1.165 + JS_ASSERT(*size < MaxParents); 1.166 + nameable[(*size)++] = cur; 1.167 + break; 1.168 + } 1.169 + } 1.170 + 1.171 + return nullptr; 1.172 + } 1.173 + 1.174 + /* 1.175 + * Resolve the name of a function. If the function already has a name 1.176 + * listed, then it is skipped. Otherwise an intelligent name is guessed to 1.177 + * assign to the function's displayAtom field 1.178 + */ 1.179 + bool resolveFun(ParseNode *pn, HandleAtom prefix, MutableHandleAtom retAtom) { 1.180 + JS_ASSERT(pn != nullptr && pn->isKind(PNK_FUNCTION)); 1.181 + RootedFunction fun(cx, pn->pn_funbox->function()); 1.182 + 1.183 + StringBuffer buf(cx); 1.184 + this->buf = &buf; 1.185 + 1.186 + retAtom.set(nullptr); 1.187 + 1.188 + /* If the function already has a name, use that */ 1.189 + if (fun->displayAtom() != nullptr) { 1.190 + if (prefix == nullptr) { 1.191 + retAtom.set(fun->displayAtom()); 1.192 + return true; 1.193 + } 1.194 + if (!buf.append(prefix) || 1.195 + !buf.append("/") || 1.196 + !buf.append(fun->displayAtom())) 1.197 + return false; 1.198 + retAtom.set(buf.finishAtom()); 1.199 + return !!retAtom; 1.200 + } 1.201 + 1.202 + /* If a prefix is specified, then it is a form of namespace */ 1.203 + if (prefix != nullptr && (!buf.append(prefix) || !buf.append("/"))) 1.204 + return false; 1.205 + 1.206 + /* Gather all nodes relevant to naming */ 1.207 + ParseNode *toName[MaxParents]; 1.208 + size_t size; 1.209 + ParseNode *assignment = gatherNameable(toName, &size); 1.210 + 1.211 + /* If the function is assigned to something, then that is very relevant */ 1.212 + if (assignment) { 1.213 + if (assignment->isAssignment()) 1.214 + assignment = assignment->pn_left; 1.215 + if (!nameExpression(assignment)) 1.216 + return true; 1.217 + } 1.218 + 1.219 + /* 1.220 + * Other than the actual assignment, other relevant nodes to naming are 1.221 + * those in object initializers and then particular nodes marking a 1.222 + * contribution. 1.223 + */ 1.224 + for (int pos = size - 1; pos >= 0; pos--) { 1.225 + ParseNode *node = toName[pos]; 1.226 + 1.227 + if (node->isKind(PNK_COLON)) { 1.228 + ParseNode *left = node->pn_left; 1.229 + if (left->isKind(PNK_NAME) || left->isKind(PNK_STRING)) { 1.230 + if (!appendPropertyReference(left->pn_atom)) 1.231 + return false; 1.232 + } else if (left->isKind(PNK_NUMBER)) { 1.233 + if (!appendNumericPropertyReference(left->pn_dval)) 1.234 + return false; 1.235 + } 1.236 + } else { 1.237 + /* 1.238 + * Don't have consecutive '<' characters, and also don't start 1.239 + * with a '<' character. 1.240 + */ 1.241 + if (!buf.empty() && *(buf.end() - 1) != '<' && !buf.append("<")) 1.242 + return false; 1.243 + } 1.244 + } 1.245 + 1.246 + /* 1.247 + * functions which are "genuinely anonymous" but are contained in some 1.248 + * other namespace are rather considered as "contributing" to the outer 1.249 + * function, so give them a contribution symbol here. 1.250 + */ 1.251 + if (!buf.empty() && *(buf.end() - 1) == '/' && !buf.append("<")) 1.252 + return false; 1.253 + 1.254 + if (buf.empty()) 1.255 + return true; 1.256 + 1.257 + retAtom.set(buf.finishAtom()); 1.258 + if (!retAtom) 1.259 + return false; 1.260 + fun->setGuessedAtom(retAtom); 1.261 + return true; 1.262 + } 1.263 + 1.264 + /* 1.265 + * Tests whether parents[pos] is a function call whose callee is cur. 1.266 + * This is the case for functions which do things like simply create a scope 1.267 + * for new variables and then return an anonymous function using this scope. 1.268 + */ 1.269 + bool isDirectCall(int pos, ParseNode *cur) { 1.270 + return pos >= 0 && call(parents[pos]) && parents[pos]->pn_head == cur; 1.271 + } 1.272 + 1.273 + public: 1.274 + explicit NameResolver(ExclusiveContext *cx) : cx(cx), nparents(0), buf(nullptr) {} 1.275 + 1.276 + /* 1.277 + * Resolve all names for anonymous functions recursively within the 1.278 + * ParseNode instance given. The prefix is for each subsequent name, and 1.279 + * should initially be nullptr. 1.280 + */ 1.281 + bool resolve(ParseNode *cur, HandleAtom prefixArg = js::NullPtr()) { 1.282 + RootedAtom prefix(cx, prefixArg); 1.283 + if (cur == nullptr) 1.284 + return true; 1.285 + 1.286 + if (cur->isKind(PNK_FUNCTION) && cur->isArity(PN_CODE)) { 1.287 + RootedAtom prefix2(cx); 1.288 + if (!resolveFun(cur, prefix, &prefix2)) 1.289 + return false; 1.290 + 1.291 + /* 1.292 + * If a function looks like (function(){})() where the parent node 1.293 + * of the definition of the function is a call, then it shouldn't 1.294 + * contribute anything to the namespace, so don't bother updating 1.295 + * the prefix to whatever was returned. 1.296 + */ 1.297 + if (!isDirectCall(nparents - 1, cur)) 1.298 + prefix = prefix2; 1.299 + } 1.300 + if (nparents >= MaxParents) 1.301 + return true; 1.302 + parents[nparents++] = cur; 1.303 + 1.304 + switch (cur->getArity()) { 1.305 + case PN_NULLARY: 1.306 + break; 1.307 + case PN_NAME: 1.308 + if (!resolve(cur->maybeExpr(), prefix)) 1.309 + return false; 1.310 + break; 1.311 + case PN_UNARY: 1.312 + if (!resolve(cur->pn_kid, prefix)) 1.313 + return false; 1.314 + break; 1.315 + case PN_BINARY: 1.316 + case PN_BINARY_OBJ: 1.317 + if (!resolve(cur->pn_left, prefix)) 1.318 + return false; 1.319 + 1.320 + /* 1.321 + * FIXME? Occasionally pn_left == pn_right for something like 1.322 + * destructuring assignment in (function({foo}){}), so skip the 1.323 + * duplicate here if this is the case because we want to traverse 1.324 + * everything at most once. 1.325 + */ 1.326 + if (cur->pn_left != cur->pn_right) 1.327 + if (!resolve(cur->pn_right, prefix)) 1.328 + return false; 1.329 + break; 1.330 + case PN_TERNARY: 1.331 + if (!resolve(cur->pn_kid1, prefix)) 1.332 + return false; 1.333 + if (!resolve(cur->pn_kid2, prefix)) 1.334 + return false; 1.335 + if (!resolve(cur->pn_kid3, prefix)) 1.336 + return false; 1.337 + break; 1.338 + case PN_CODE: 1.339 + JS_ASSERT(cur->isKind(PNK_FUNCTION)); 1.340 + if (!resolve(cur->pn_body, prefix)) 1.341 + return false; 1.342 + break; 1.343 + case PN_LIST: 1.344 + for (ParseNode *nxt = cur->pn_head; nxt; nxt = nxt->pn_next) 1.345 + if (!resolve(nxt, prefix)) 1.346 + return false; 1.347 + break; 1.348 + } 1.349 + nparents--; 1.350 + return true; 1.351 + } 1.352 +}; 1.353 + 1.354 +} /* anonymous namespace */ 1.355 + 1.356 +bool 1.357 +frontend::NameFunctions(ExclusiveContext *cx, ParseNode *pn) 1.358 +{ 1.359 + NameResolver nr(cx); 1.360 + return nr.resolve(pn); 1.361 +}