@Override public void visit(NodeTraversal t, Node n, Node parent) { if ((t.inGlobalScope() && inlineGlobalFunctions) || (!t.inGlobalScope() && inlineLocalFunctions)) { findNamedFunctions(t, n, parent); findFunctionExpressions(t, n); } }
public void enterScope(NodeTraversal t) { if (t.inGlobalScope()) { scopes.push(typeSystem.getRootScope()); } else { scopes.push(typeSystem.getFunctionScope(t.getScopeRoot())); } }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isString() && !parent.isGetProp() && !parent.isRegExp()) { String str = n.getString(); // "undefined" is special-cased, since it needs to be used when JS code // is unloading and therefore variable references aren't available. // This is because of a bug in Firefox. if ("undefined".equals(str)) { return; } if (blacklist != null && blacklist.reset(str).find()) { return; } if (aliasableStrings == null || aliasableStrings.contains(str)) { StringOccurrence occurrence = new StringOccurrence(n, parent); StringInfo info = getOrCreateStringInfo(str); info.occurrences.add(occurrence); info.numOccurrences++; if (t.inGlobalScope() || isInThrowExpression(n)) { info.numOccurrencesInfrequentlyExecuted++; } // The current module. JSModule module = t.getModule(); if (info.numOccurrences != 1) { // Check whether the current module depends on the module containing // the declaration. if (module != null && info.moduleToContainDecl != null && module != info.moduleToContainDecl && !moduleGraph.dependsOn(module, info.moduleToContainDecl)) { // We need to declare this string in the deepest module in the // module dependency graph that both of these modules depend on. module = moduleGraph.getDeepestCommonDependency(module, info.moduleToContainDecl); } else { // use the previously saved insertion location. return; } } Node varParent = moduleVarParentMap.get(module); if (varParent == null) { varParent = compiler.getNodeForCodeInsertion(module); moduleVarParentMap.put(module, varParent); } info.moduleToContainDecl = module; info.parentForNewVarDecl = varParent; info.siblingToInsertVarDeclBefore = varParent.getFirstChild(); } } }
@Override public void enterScope(NodeTraversal t) { if (t.inGlobalScope()) return; Iterator<Var> it = t.getScope().getVars(); while (it.hasNext()) { Var current = it.next(); if (current.isBleedingFunction()) { localBleedingFunctions.add(current); localBleedingFunctionsPerScope.put(t.getScope().getParent(), current); } } }
/** * Determines whether a function can be inlined at a particular call site. - Don't inline if the * calling function contains an inner function and inlining would introduce new globals. */ private boolean callMeetsBlockInliningRequirements( NodeTraversal t, Node callNode, Node fnNode, Set<String> namesToAlias) { // Note: functions that contain function definitions are filtered out // in isCanidateFunction. // TODO(johnlenz): Determining if the called function contains VARs // or if the caller contains inner functions accounts for 20% of the // runtime cost of this pass. // Don't inline functions with var declarations into a scope with inner // functions as the new vars would leak into the inner function and // cause memory leaks. boolean fnContainsVars = NodeUtil.has( NodeUtil.getFunctionBody(fnNode), new NodeUtil.MatchDeclaration(), new NodeUtil.MatchShallowStatement()); boolean callerContainsFunction = false; if (!t.inGlobalScope()) { Node fnCaller = t.getScopeRoot(); Node fnCallerBody = fnCaller.getLastChild(); callerContainsFunction = NodeUtil.containsFunction(fnCallerBody); } if (fnContainsVars && callerContainsFunction) { return false; } // If the caller contains functions, verify we aren't adding any // additional VAR declarations because aliasing is needed. if (callerContainsFunction) { Map<String, Node> args = FunctionArgumentInjector.getFunctionCallParameterMap( fnNode, callNode, this.safeNameIdSupplier); boolean hasArgs = !args.isEmpty(); if (hasArgs) { // Limit the inlining Set<String> allNamesToAlias = Sets.newHashSet(namesToAlias); FunctionArgumentInjector.maybeAddTempsForCallArguments( fnNode, args, allNamesToAlias, compiler.getCodingConvention()); if (!allNamesToAlias.isEmpty()) { return false; } } } return true; }
/** * @param t The traversal use to reach the call site. * @param callNode The CALL node. * @param fnNode The function to evaluate for inlining. * @param needAliases A set of function parameter names that can not be used without aliasing. * Returned by getUnsafeParameterNames(). * @param mode Inlining mode to be used. * @param referencesThis Whether fnNode contains references to its this object. * @param containsFunctions Whether fnNode contains inner functions. * @return Whether the inlining can occur. */ CanInlineResult canInlineReferenceToFunction( NodeTraversal t, Node callNode, Node fnNode, Set<String> needAliases, InliningMode mode, boolean referencesThis, boolean containsFunctions) { // TODO(johnlenz): This function takes too many parameter, without // context. Modify the API to take a structure describing the function. // Allow direct function calls or "fn.call" style calls. if (!isSupportedCallType(callNode)) { return CanInlineResult.NO; } // Limit where functions that contain functions can be inline. Introducing // an inner function into another function can capture a variable and cause // a memory leak. This isn't a problem in the global scope as those values // last until explicitly cleared. if (containsFunctions && !t.inGlobalScope()) { // TODO(johnlenz): Allow inlining into any scope without local names or // inner functions. return CanInlineResult.NO; } // TODO(johnlenz): Add support for 'apply' if (referencesThis && !NodeUtil.isFunctionObjectCall(callNode)) { // TODO(johnlenz): Allow 'this' references to be replaced with a // global 'this' object. return CanInlineResult.NO; } if (mode == InliningMode.DIRECT) { return canInlineReferenceDirectly(callNode, fnNode); } else { return canInlineReferenceAsStatementBlock(t, callNode, fnNode, needAliases); } }
/** Returns the function the traversal is currently traversing, or null if in the global scope. */ private static Node getContainingFunction(NodeTraversal t) { return (t.inGlobalScope()) ? null : t.getScopeRoot(); }
@Override public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { // Don't traverse into function bodies // if we aren't inlining local functions. return inlineLocalFunctions || nodeTraversal.inGlobalScope(); }
@SuppressWarnings("incomplete-switch") @Override public void visit(NodeTraversal t, Node n, Node parent) { if (!n.isCall()) { return; } String callName = n.getFirstChild().getQualifiedName(); TweakFunction tweakFunc = TWEAK_FUNCTIONS_MAP.get(callName); if (tweakFunc == null) { return; } if (tweakFunc == TweakFunction.GET_COMPILER_OVERRIDES) { getOverridesCalls.add(new TweakFunctionCall(tweakFunc, n)); return; } // Ensure the first parameter (the tweak ID) is a string literal. Node tweakIdNode = n.getFirstChild().getNext(); if (!tweakIdNode.isString()) { compiler.report(t.makeError(tweakIdNode, NON_LITERAL_TWEAK_ID_ERROR)); return; } String tweakId = tweakIdNode.getString(); // Make sure there is a TweakInfo structure for it. TweakInfo tweakInfo = allTweaks.get(tweakId); if (tweakInfo == null) { tweakInfo = new TweakInfo(tweakId); allTweaks.put(tweakId, tweakInfo); } switch (tweakFunc) { case REGISTER_BOOLEAN: case REGISTER_NUMBER: case REGISTER_STRING: // Ensure the ID contains only valid characters. if (!ID_MATCHER.matchesAllOf(tweakId)) { compiler.report(t.makeError(tweakIdNode, INVALID_TWEAK_ID_ERROR)); } // Ensure tweaks are registered in the global scope. if (!t.inGlobalScope()) { compiler.report(t.makeError(n, NON_GLOBAL_TWEAK_INIT_ERROR, tweakId)); break; } // Ensure tweaks are registered only once. if (tweakInfo.isRegistered()) { compiler.report(t.makeError(n, TWEAK_MULTIPLY_REGISTERED_ERROR, tweakId)); break; } Node tweakDefaultValueNode = tweakIdNode.getNext().getNext(); tweakInfo.addRegisterCall(t.getSourceName(), tweakFunc, n, tweakDefaultValueNode); break; case OVERRIDE_DEFAULT_VALUE: // Ensure tweaks overrides occur in the global scope. if (!t.inGlobalScope()) { compiler.report(t.makeError(n, NON_GLOBAL_TWEAK_INIT_ERROR, tweakId)); break; } // Ensure tweak overrides occur before the tweak is registered. if (tweakInfo.isRegistered()) { compiler.report(t.makeError(n, TWEAK_OVERRIDE_AFTER_REGISTERED_ERROR, tweakId)); break; } tweakDefaultValueNode = tweakIdNode.getNext(); tweakInfo.addOverrideDefaultValueCall( t.getSourceName(), tweakFunc, n, tweakDefaultValueNode); break; case GET_BOOLEAN: case GET_NUMBER: case GET_STRING: tweakInfo.addGetterCall(t.getSourceName(), tweakFunc, n); } }