@Override public void process(Node externs, Node root) { Preconditions.checkState(compiler.getLifeCycleStage().isNormalized()); NodeTraversal.traverseEs6(compiler, root, new FindCandidateFunctions()); if (fns.isEmpty()) { return; // Nothing left to do. } NodeTraversal.traverseEs6(compiler, root, new FindCandidatesReferences(fns, anonFns)); trimCandidatesNotMeetingMinimumRequirements(); if (fns.isEmpty()) { return; // Nothing left to do. } // Store the set of function names eligible for inlining and use this to // prevent function names from being moved into temporaries during // expression decomposition. If this movement were allowed it would prevent // the Inline callback from finding the function calls. // // This pass already assumes these are constants, so this is safe for anyone // using function inlining. // Set<String> fnNames = new HashSet<>(fns.keySet()); injector.setKnownConstants(fnNames); trimCandidatesUsingOnCost(); if (fns.isEmpty()) { return; // Nothing left to do. } resolveInlineConflicts(); decomposeExpressions(); NodeTraversal.traverseEs6(compiler, root, new CallVisitor(fns, anonFns, new Inline(injector))); removeInlinedFunctions(); }
@Override public void enterScope(NodeTraversal t) { // Only infer the entry root, rather than the scope root. // This ensures that incremental compilation only touches the root // that's been swapped out. inferScope(t.getCurrentNode(), t.getScope()); }
public void process(Node externs, Node root) { Node initCode = null; if (initCodeSource.length() != 0) { Node initCodeRoot = compiler.parseSyntheticCode(templateFilename + ":init", initCodeSource); if (initCodeRoot != null && initCodeRoot.getFirstChild() != null) { initCode = initCodeRoot.removeChildren(); } else { return; // parse failure } } NodeTraversal.traverse(compiler, root, new RemoveCallback(declarationsToRemove)); NodeTraversal.traverse(compiler, root, new InstrumentCallback()); if (appNameSetter.length() != 0) { Node call = new Node( Token.CALL, Node.newString(Token.NAME, appNameSetter), Node.newString(appNameStr)); Node expr = new Node(Token.EXPR_RESULT, call); Node addingRoot = compiler.getNodeForCodeInsertion(null); addingRoot.addChildrenToFront(expr); compiler.reportCodeChange(); } if (initCode != null) { Node addingRoot = compiler.getNodeForCodeInsertion(null); addingRoot.addChildrenToFront(initCode); compiler.reportCodeChange(); } }
/** For each node, update the block stack and reference collection as appropriate. */ @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isName() || n.isRest() || (n.isStringKey() && parent.isObjectPattern() && !n.hasChildren())) { Var v; if (n.getString().equals("arguments")) { v = t.getScope().getArgumentsVar(); } else { v = t.getScope().getVar(n.getString()); } if (v != null) { if (varFilter.apply(v)) { addReference(v, new Reference(n, t, peek(blockStack))); } if (v.getParentNode() != null && NodeUtil.isHoistedFunctionDeclaration(v.getParentNode()) && // If we're only traversing a narrow scope, do not try to climb outside. (narrowScope == null || narrowScope.getDepth() <= v.getScope().getDepth())) { outOfBandTraversal(v); } } } if (isBlockBoundary(n, parent)) { pop(blockStack); } }
public void findNamedFunctions(NodeTraversal t, Node n, Node parent) { if (!NodeUtil.isStatement(n)) { // There aren't any interesting functions here. return; } switch (n.getType()) { // Functions expressions in the form of: // var fooFn = function(x) { return ... } case Token.VAR: Preconditions.checkState(n.hasOneChild()); Node nameNode = n.getFirstChild(); if (nameNode.isName() && nameNode.hasChildren() && nameNode.getFirstChild().isFunction()) { maybeAddFunction(new FunctionVar(n), t.getModule()); } break; // Named functions // function Foo(x) { return ... } case Token.FUNCTION: Preconditions.checkState(NodeUtil.isStatementBlock(parent) || parent.isLabel()); if (!NodeUtil.isFunctionExpression(n)) { Function fn = new NamedFunction(n); maybeAddFunction(fn, t.getModule()); } break; } }
public void testReport() { final List<JSError> errors = new ArrayList<JSError>(); Compiler compiler = new Compiler( new BasicErrorManager() { @Override public void report(CheckLevel level, JSError error) { errors.add(error); } @Override public void println(CheckLevel level, JSError error) {} @Override protected void printSummary() {} }); compiler.initCompilerOptionsIfTesting(); NodeTraversal t = new NodeTraversal(compiler, null); DiagnosticType dt = DiagnosticType.warning("FOO", "{0}, {1} - {2}"); t.report(null, dt, "Foo", "Bar", "Hello"); assertEquals(1, errors.size()); assertEquals("Foo, Bar - Hello", errors.get(0).description); }
@Override public void exitScope(NodeTraversal traversal) { Preconditions.checkNotNull(traversal); // This is the case when we are exiting the global scope where we had never // collected argument access list. Since we do not perform this optimization // for the global scope, we will skip this exit point. if (currentArgumentsAccess == null) { return; } Node function = traversal.getScopeRoot(); if (!function.isFunction()) { return; } // Attempt to replace the argument access and if the AST has been change, // report back to the compiler. if (tryReplaceArguments(traversal.getScope())) { traversal.getCompiler().reportCodeChange(); } // After the attempt to replace the arguments. The currentArgumentsAccess // is stale and as we exit the Scope, no longer holds all the access to the // current scope anymore. We'll pop the access list from the outer scope // and set it as currentArgumentsAccess if the outer scope is not the global // scope. if (!argumentsAccessStack.isEmpty()) { currentArgumentsAccess = argumentsAccessStack.pop(); } else { currentArgumentsAccess = null; } }
/** Checks for illegal declarations. */ private void checkDeclaration(NodeTraversal t, Node n) { if ("eval".equals(n.getString())) { t.report(n, EVAL_DECLARATION); } else if ("arguments".equals(n.getString())) { t.report(n, ARGUMENTS_DECLARATION); } }
/** * Expand a jQuery.expandedEach call * * <p>Expanded jQuery.expandedEach calls will replace the GETELEM nodes of a property assignment * with GETPROP nodes to allow for renaming. */ private void maybeExpandJqueryEachCall(NodeTraversal t, Node n) { Node objectToLoopOver = n.getChildAtIndex(1); if (objectToLoopOver == null) { return; } Node callbackFunction = objectToLoopOver.getNext(); if (callbackFunction == null || !callbackFunction.isFunction()) { return; } // Run the peephole optimizations on the first argument to handle // cases like ("a " + "b").split(" ") peepholePasses.process(null, n.getChildAtIndex(1)); // Create a reference tree Node nClone = n.cloneTree(); objectToLoopOver = nClone.getChildAtIndex(1); // Check to see if the first argument is something we recognize and can // expand. if (!objectToLoopOver.isObjectLit() && !(objectToLoopOver.isArrayLit() && isArrayLitValidForExpansion(objectToLoopOver))) { t.report(n, JQUERY_UNABLE_TO_EXPAND_INVALID_LIT_ERROR, (String) null); return; } // Find all references to the callback function arguments List<Node> keyNodeReferences = new ArrayList<>(); List<Node> valueNodeReferences = new ArrayList<>(); new NodeTraversal( compiler, new FindCallbackArgumentReferences( callbackFunction, keyNodeReferences, valueNodeReferences, objectToLoopOver.isArrayLit())) .traverseInnerNode( NodeUtil.getFunctionBody(callbackFunction), callbackFunction, t.getScope()); if (keyNodeReferences.isEmpty()) { // We didn't do anything useful ... t.report(n, JQUERY_USELESS_EACH_EXPANSION, (String) null); return; } Node fncBlock = tryExpandJqueryEachCall( t, nClone, callbackFunction, keyNodeReferences, valueNodeReferences); if (fncBlock != null && fncBlock.hasChildren()) { replaceOriginalJqueryEachCall(n, fncBlock); } else { // We didn't do anything useful ... t.report(n, JQUERY_USELESS_EACH_EXPANSION, (String) null); } }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (!NodeUtil.isReferenceName(n)) { return; } Scope referencedIn = t.getScope(); String oldName = n.getString(); Scope current = referencedIn; boolean doRename = false; String newName = null; while (current != null) { Map<String, String> renamesAtCurrentLevel = renameMap.get(current.getRootNode()); if (current.isDeclared(oldName, false)) { return; } else if (renamesAtCurrentLevel != null && renamesAtCurrentLevel.containsKey(oldName)) { doRename = true; newName = renamesAtCurrentLevel.get(oldName); break; } else { current = current.getParent(); } } if (doRename) { n.setString(newName); t.getCompiler().reportCodeChange(); } }
public void exitScope(NodeTraversal t) { Scope scope = t.getScope(); Node node = t.getCurrentNode(); if (scope.isLocal()) { inferTypes(t, node, scope); } }
public void testUnexpectedException() { final String TEST_EXCEPTION = "test me"; NodeTraversal.Callback cb = new NodeTraversal.AbstractPostOrderCallback() { public void visit(NodeTraversal t, Node n, Node parent) { throw new RuntimeException(TEST_EXCEPTION); } }; Compiler compiler = new Compiler(); NodeTraversal t = new NodeTraversal(compiler, cb); String code = "function foo() {}"; Node tree = parse(compiler, code); try { t.traverse(tree); fail("Expected RuntimeException"); } catch (RuntimeException e) { assertTrue( e.getMessage() .startsWith( "INTERNAL COMPILER ERROR.\n" + "Please report this problem.\n" + "test me")); } }
public void testGetCurrentNode() { Compiler compiler = new Compiler(); ScopeCreator creator = new SyntacticScopeCreator(compiler); ExpectNodeOnEnterScope callback = new ExpectNodeOnEnterScope(); NodeTraversal t = new NodeTraversal(compiler, callback, creator); String code = "" + "var a; " + "function foo() {" + " var b;" + "}"; Node tree = parse(compiler, code); Scope topScope = creator.createScope(tree, null); // Calling #traverseWithScope uses the given scope but starts traversal at // the given node. callback.expect(tree.getFirstChild(), tree); t.traverseWithScope(tree.getFirstChild(), topScope); callback.assertEntered(); // Calling #traverse creates a new scope with the given node as the root. callback.expect(tree.getFirstChild(), tree.getFirstChild()); t.traverse(tree.getFirstChild()); callback.assertEntered(); // Calling #traverseAtScope starts traversal from the scope's root. Node fn = tree.getFirstChild().getNext(); Scope fnScope = creator.createScope(fn, topScope); callback.expect(fn, fn); t.traverseAtScope(fnScope); callback.assertEntered(); }
public void testGetScopeRoot() { Compiler compiler = new Compiler(); NodeTraversal t = new NodeTraversal( compiler, new NodeTraversal.ScopedCallback() { public void enterScope(NodeTraversal t) { Node root1 = t.getScopeRoot(); Node root2 = t.getScope().getRootNode(); assertEquals(root1, root2); } public void exitScope(NodeTraversal t) {} public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { return true; } public void visit(NodeTraversal t, Node n, Node parent) {} }); String code = "" + "var a; " + "function foo() {" + " var b" + "}"; Node tree = parse(compiler, code); t.traverse(tree); }
public void enterScope(NodeTraversal t) { if (t.inGlobalScope()) { scopes.push(typeSystem.getRootScope()); } else { scopes.push(typeSystem.getFunctionScope(t.getScopeRoot())); } }
/** Checks that object literal keys or class method names are valid. */ private static void checkObjectLiteralOrClass(NodeTraversal t, Node n) { Set<String> getters = new HashSet<>(); Set<String> setters = new HashSet<>(); for (Node key = n.getFirstChild(); key != null; key = key.getNext()) { if (!key.isSetterDef()) { // normal property and getter cases if (!getters.add(key.getString())) { if (n.isClassMembers()) { t.report(key, DUPLICATE_CLASS_METHODS); } else { t.report(key, DUPLICATE_OBJECT_KEY); } } } if (!key.isGetterDef()) { // normal property and setter cases if (!setters.add(key.getString())) { if (n.isClassMembers()) { t.report(key, DUPLICATE_CLASS_METHODS); } else { t.report(key, DUPLICATE_OBJECT_KEY); } } } } }
@Override public void process(Node externs, Node root) { Node initCode = null; if (!initCodeSource.isEmpty()) { Node initCodeRoot = compiler.parseSyntheticCode("template:init", initCodeSource); if (initCodeRoot != null && initCodeRoot.getFirstChild() != null) { initCode = initCodeRoot.removeChildren(); } else { return; // parse failure } } NodeTraversal.traverseEs6(compiler, root, new RemoveCallback(declarationsToRemove)); NodeTraversal.traverseEs6(compiler, root, new InstrumentCallback()); if (!appNameSetter.isEmpty()) { Node call = IR.call(IR.name(appNameSetter), IR.string(appNameStr)); call.putBooleanProp(Node.FREE_CALL, true); Node expr = IR.exprResult(call); Node addingRoot = compiler.getNodeForCodeInsertion(null); addingRoot.addChildrenToFront(expr.useSourceInfoIfMissingFromForTree(addingRoot)); compiler.reportCodeChange(); } if (initCode != null) { Node addingRoot = compiler.getNodeForCodeInsertion(null); addingRoot.addChildrenToFront(initCode); compiler.reportCodeChange(); } }
@Override public void process(Node externs, Node root) { assignmentLog = new StringBuilder(); // Do variable reference counting. NodeTraversal.traverse(compiler, externs, new ProcessVars(true)); NodeTraversal.traverse(compiler, root, new ProcessVars(false)); // Make sure that new names don't overlap with extern names. reservedNames.addAll(externNames); // Rename vars, sorted by frequency of occurrence to minimize code size. SortedSet<Assignment> varsByFrequency = new TreeSet<Assignment>(FREQUENCY_COMPARATOR); varsByFrequency.addAll(assignments.values()); if (shouldShadow) { new ShadowVariables(compiler, assignments, varsByFrequency, pseudoNameMap) .process(externs, root); } // First try to reuse names from an earlier compilation. if (prevUsedRenameMap != null) { reusePreviouslyUsedVariableMap(); } // Assign names, sorted by descending frequency to minimize code size. assignNames(varsByFrequency); boolean changed = false; // Rename the globals! for (Node n : globalNameNodes) { String newName = getNewGlobalName(n); // Note: if newName is null, then oldName is an extern. if (newName != null) { n.setString(newName); changed = true; } } // Rename the locals! int count = 0; for (Node n : localNameNodes) { String newName = getNewLocalName(n); if (newName != null) { n.setString(newName); changed = true; } count++; } if (changed) { compiler.reportCodeChange(); } // Lastly, write the name assignments to the debug log. compiler.addToDebugLog("JS var assignments:\n" + assignmentLog); assignmentLog = null; }
@Override public void process(Node externs, Node root) { NameAnonyms na = new NameAnonyms(root, compiler, externs); NodeTraversal.traverse(compiler, root, na); NodeTraversal.traverse(compiler, root, functionListExtractor); }
/** Checks that variables, functions, and arguments are not deleted. */ private static void checkDelete(NodeTraversal t, Node n) { if (n.getFirstChild().isName()) { Var v = t.getScope().getVar(n.getFirstChild().getString()); if (v != null) { t.report(n, DELETE_VARIABLE); } } }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if ((t.inGlobalScope() && inlineGlobalFunctions) || (!t.inGlobalScope() && inlineLocalFunctions)) { findNamedFunctions(t, n, parent); findFunctionExpressions(t, n); } }
@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(); } } }
private static void reportIfWasEmpty(NodeTraversal t, Node block) { Preconditions.checkState(block.isBlock()); // A semicolon is distinguished from a block without children by // annotating it with EMPTY_BLOCK. Blocks without children are // usually intentional, especially with loops. if (!block.hasChildren() && block.isAddedBlock()) { t.getCompiler().report(t.makeError(block, SUSPICIOUS_SEMICOLON)); } }
/** Checks that an assignment is not to the "arguments" object. */ private static void checkAssignment(NodeTraversal t, Node n) { if (n.getFirstChild().isName()) { if ("arguments".equals(n.getFirstChild().getString())) { t.report(n, ARGUMENTS_ASSIGNMENT); } else if ("eval".equals(n.getFirstChild().getString())) { // Note that assignment to eval is already illegal because any use of // that name is illegal. t.report(n, EVAL_ASSIGNMENT); } } }
public void process(Node externs, Node root) { for (TypeMismatch mis : compiler.getTypeValidator().getMismatches()) { addInvalidatingType(mis.typeA); addInvalidatingType(mis.typeB); } StaticScope<T> scope = typeSystem.getRootScope(); NodeTraversal.traverse(compiler, externs, new FindExternProperties()); NodeTraversal.traverse(compiler, root, new FindRenameableProperties()); renameProperties(); }
/** Updates block stack and invokes any additional behavior. */ @Override public void exitScope(NodeTraversal t) { pop(blockStack); if (t.getScope().isGlobal()) { // Update global scope reference lists when we are done with it. compiler.updateGlobalVarReferences(referenceMap, t.getScopeRoot()); behavior.afterExitScope(t, compiler.getGlobalVarReferences()); } else { behavior.afterExitScope(t, new ReferenceMapWrapper(referenceMap)); } }
/** Inline a function into the call site. */ private void inlineFunction(NodeTraversal t, Reference ref, FunctionState fs) { Function fn = fs.getFn(); String fnName = fn.getName(); Node fnNode = fs.getSafeFnNode(); Node newExpr = injector.inline(ref, fnName, fnNode); if (!newExpr.isEquivalentTo(ref.callNode)) { t.getCompiler().reportChangeToEnclosingScope(newExpr); } t.getCompiler().addToDebugLog("Inlined function: " + fn.getName()); }
@Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { NodeTraversal.traverse(compiler, scriptRoot, new CollectUndeclaredNames()); NodeTraversal.traverse(compiler, scriptRoot, this); NodeTraversal.traverse(compiler, scriptRoot, new RenameReferences()); LoopClosureTransformer transformer = new LoopClosureTransformer(); NodeTraversal.traverse(compiler, scriptRoot, transformer); transformer.transformLoopClosure(); varify(); NodeTraversal.traverse(compiler, scriptRoot, new RewriteBlockScopedFunctionDeclaration()); }
@Override public void process(Node externs, Node root) { NodeTraversal.traverseRoots(compiler, new CollectUndeclaredNames(), externs, root); NodeTraversal.traverseRoots(compiler, this, externs, root); NodeTraversal.traverseRoots(compiler, new RenameReferences(), externs, root); LoopClosureTransformer transformer = new LoopClosureTransformer(); NodeTraversal.traverseRoots(compiler, transformer, externs, root); transformer.transformLoopClosure(); varify(); NodeTraversal.traverseRoots( compiler, new RewriteBlockScopedFunctionDeclaration(), externs, root); }
@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); } } }