js/src/frontend/NameFunctions.cpp

changeset 0
6474c204b198
     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 +}

mercurial