bool ModuleBuilder::buildAndInit(frontend::ParseNode* moduleNode, HandleModuleObject module) { MOZ_ASSERT(moduleNode->isKind(PNK_MODULE)); ParseNode* stmtsNode = moduleNode->pn_expr; MOZ_ASSERT(stmtsNode->isKind(PNK_STATEMENTLIST)); MOZ_ASSERT(stmtsNode->isArity(PN_LIST)); for (ParseNode* pn = stmtsNode->pn_head; pn; pn = pn->pn_next) { switch (pn->getKind()) { case PNK_IMPORT: if (!processImport(pn)) return false; break; case PNK_EXPORT: case PNK_EXPORT_DEFAULT: if (!processExport(pn)) return false; break; case PNK_EXPORT_FROM: if (!processExportFrom(pn)) return false; break; default: break; } } for (const auto& e : exportEntries_) { RootedExportEntry exp(cx_, e); if (!exp->moduleRequest()) { RootedImportEntry importEntry(cx_, importEntryFor(exp->localName())); if (!importEntry) { if (!localExportEntries_.append(exp)) return false; } else { if (importEntry->importName() == cx_->names().star) { if (!localExportEntries_.append(exp)) return false; } else { RootedAtom exportName(cx_, exp->exportName()); RootedAtom moduleRequest(cx_, importEntry->moduleRequest()); RootedAtom importName(cx_, importEntry->importName()); RootedExportEntry exportEntry(cx_); exportEntry = ExportEntryObject::create(cx_, exportName, moduleRequest, importName, nullptr); if (!exportEntry || !indirectExportEntries_.append(exportEntry)) return false; } } } else if (exp->importName() == cx_->names().star) { if (!starExportEntries_.append(exp)) return false; } else { if (!indirectExportEntries_.append(exp)) return false; } } RootedArrayObject requestedModules(cx_, createArray<JSAtom*>(requestedModules_)); if (!requestedModules) return false; RootedArrayObject importEntries(cx_, createArray<ImportEntryObject*>(importEntries_)); if (!importEntries) return false; RootedArrayObject localExportEntries(cx_, createArray<ExportEntryObject*>(localExportEntries_)); if (!localExportEntries) return false; RootedArrayObject indirectExportEntries(cx_); indirectExportEntries = createArray<ExportEntryObject*>(indirectExportEntries_); if (!indirectExportEntries) return false; RootedArrayObject starExportEntries(cx_, createArray<ExportEntryObject*>(starExportEntries_)); if (!starExportEntries) return false; module->initImportExportData(requestedModules, importEntries, localExportEntries, indirectExportEntries, starExportEntries); return true; }
bool ModuleBuilder::processExport(frontend::ParseNode* pn) { MOZ_ASSERT(pn->isArity(PN_UNARY)); ParseNode* kid = pn->pn_kid; bool isDefault = pn->getKind() == PNK_EXPORT_DEFAULT; switch (kid->getKind()) { case PNK_EXPORT_SPEC_LIST: MOZ_ASSERT(!isDefault); for (ParseNode* spec = kid->pn_head; spec; spec = spec->pn_next) { MOZ_ASSERT(spec->isKind(PNK_EXPORT_SPEC)); RootedAtom localName(cx_, spec->pn_left->pn_atom); RootedAtom exportName(cx_, spec->pn_right->pn_atom); if (!appendLocalExportEntry(exportName, localName)) return false; } break; case PNK_FUNCTION: { RootedFunction func(cx_, kid->pn_funbox->function()); RootedAtom localName(cx_, func->atom()); RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get()); if (!appendLocalExportEntry(exportName, localName)) return false; break; } case PNK_CLASS: { const ClassNode& cls = kid->as<ClassNode>(); MOZ_ASSERT(cls.names()); RootedAtom localName(cx_, cls.names()->innerBinding()->pn_atom); RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get()); if (!appendLocalExportEntry(exportName, localName)) return false; break; } case PNK_VAR: case PNK_CONST: case PNK_GLOBALCONST: case PNK_LET: { MOZ_ASSERT(kid->isArity(PN_LIST)); for (ParseNode* var = kid->pn_head; var; var = var->pn_next) { if (var->isKind(PNK_ASSIGN)) var = var->pn_left; MOZ_ASSERT(var->isKind(PNK_NAME)); RootedAtom localName(cx_, var->pn_atom); RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get()); if (!appendLocalExportEntry(exportName, localName)) return false; } break; } default: MOZ_ASSERT(isDefault); RootedAtom localName(cx_, cx_->names().starDefaultStar); RootedAtom exportName(cx_, cx_->names().default_); if (!appendLocalExportEntry(exportName, localName)) return false; break; } return true; }
/* * Mark as funargs any functions that reach up to one or more upvars across an * already-known funarg. The parser will flag the o_m lambda as a funarg in: * * function f(o, p) { * o.m = function o_m(a) { * function g() { return p; } * function h() { return a; } * return g() + h(); * } * } * * but without this extra marking phase, function g will not be marked as a * funarg since it is called from within its parent scope. But g reaches up to * f's parameter p, so if o_m escapes f's activation scope, g does too and * cannot assume that p's stack slot is still alive. In contast function h * neither escapes nor uses an upvar "above" o_m's level. * * If function g itself contained lambdas that contained non-lambdas that reach * up above its level, then those non-lambdas would have to be marked too. This * process is potentially exponential in the number of functions, but generally * not so complex. But it can't be done during a single recursive traversal of * the funbox tree, so we must use a work queue. * * Return the minimal "skipmin" for funbox and its siblings. This is the delta * between the static level of the bodies of funbox and its peers (which must * be funbox->level + 1), and the static level of the nearest upvar among all * the upvars contained by funbox and its peers. If there are no upvars, return * FREE_STATIC_LEVEL. Thus this function never returns 0. */ static uintN FindFunArgs(FunctionBox *funbox, int level, FunctionBoxQueue *queue) { uintN allskipmin = UpvarCookie::FREE_LEVEL; do { ParseNode *fn = funbox->node; JS_ASSERT(fn->isArity(PN_FUNC)); int fnlevel = level; /* * An eval can leak funbox, functions along its ancestor line, and its * immediate kids. Since FindFunArgs uses DFS and the parser propagates * TCF_FUN_HEAVYWEIGHT bottom up, funbox's ancestor function nodes have * already been marked as funargs by this point. Therefore we have to * flag only funbox->node and funbox->kids' nodes here. * * Generators need to be treated in the same way. Even if the value * of a generator function doesn't escape, anything defined or referred * to inside the generator can escape through a call to the generator. * We could imagine doing static analysis to track the calls and see * if any iterators or values returned by iterators escape, but that * would be hard, so instead we just assume everything might escape. */ if (funbox->tcflags & (TCF_FUN_HEAVYWEIGHT | TCF_FUN_IS_GENERATOR)) { fn->setFunArg(); for (FunctionBox *kid = funbox->kids; kid; kid = kid->siblings) kid->node->setFunArg(); } /* * Compute in skipmin the least distance from fun's static level up to * an upvar, whether used directly by fun, or indirectly by a function * nested in fun. */ uintN skipmin = UpvarCookie::FREE_LEVEL; ParseNode *pn = fn->pn_body; if (pn->isKind(PNK_UPVARS)) { AtomDefnMapPtr &upvars = pn->pn_names; JS_ASSERT(upvars->count() != 0); for (AtomDefnRange r = upvars->all(); !r.empty(); r.popFront()) { Definition *defn = r.front().value(); Definition *lexdep = defn->resolve(); if (!lexdep->isFreeVar()) { uintN upvarLevel = lexdep->frameLevel(); if (int(upvarLevel) <= fnlevel) fn->setFunArg(); uintN skip = (funbox->level + 1) - upvarLevel; if (skip < skipmin) skipmin = skip; } } } /* * If this function escapes, whether directly (the parser detects such * escapes) or indirectly (because this non-escaping function uses an * upvar that reaches across an outer function boundary where the outer * function escapes), enqueue it for further analysis, and bump fnlevel * to trap any non-escaping children. */ if (fn->isFunArg()) { queue->push(funbox); fnlevel = int(funbox->level); } /* * Now process the current function's children, and recalibrate their * cumulative skipmin to be relative to the current static level. */ if (funbox->kids) { uintN kidskipmin = FindFunArgs(funbox->kids, fnlevel, queue); JS_ASSERT(kidskipmin != 0); if (kidskipmin != UpvarCookie::FREE_LEVEL) { --kidskipmin; if (kidskipmin != 0 && kidskipmin < skipmin) skipmin = kidskipmin; } } /* * Finally, after we've traversed all of the current function's kids, * minimize allskipmin against our accumulated skipmin. Minimize across * funbox and all of its siblings, to compute our return value. */ if (skipmin != UpvarCookie::FREE_LEVEL) { if (skipmin < allskipmin) allskipmin = skipmin; } } while ((funbox = funbox->siblings) != NULL); return allskipmin; }