private void handleReturn(Node node) { Node lastJump = null; for (Iterator<Node> iter = exceptionHandler.iterator(); iter.hasNext(); ) { Node curHandler = iter.next(); if (NodeUtil.isFunction(curHandler)) { break; } if (NodeUtil.hasFinally(curHandler)) { if (lastJump == null) { createEdge(node, Branch.UNCOND, curHandler.getLastChild()); } else { finallyMap.put(lastJump, computeFallThrough(curHandler.getLastChild())); } lastJump = curHandler; } } if (node.hasChildren()) { connectToPossibleExceptionHandler(node, node.getFirstChild()); } if (lastJump == null) { createEdge(node, Branch.UNCOND, null); } else { finallyMap.put(lastJump, null); } }
private boolean isFusableControlStatement(Node n) { switch (n.getToken()) { case IF: case THROW: case SWITCH: case EXPR_RESULT: return true; case RETURN: // We don't want to add a new return value. return n.hasChildren(); case FOR: if (NodeUtil.isForIn(n)) { // Avoid cases where we have for(var x = foo() in a) { .... return !mayHaveSideEffects(n.getFirstChild()); } else { // Avoid cases where we have for(var x;_;_) { .... return !n.getFirstChild().isVar(); } case LABEL: return isFusableControlStatement(n.getLastChild()); case BLOCK: return !n.isSyntheticBlock() && isFusableControlStatement(n.getFirstChild()); default: break; } return false; }
private void validateContinue(Node n) { validateNodeType(Token.CONTINUE, n); validateMaximumChildCount(n, 1); if (n.hasChildren()) { validateLabelName(n.getFirstChild()); } }
private void validateTry(Node n) { validateNodeType(Token.TRY, n); validateChildCountIn(n, 2, 3); validateBlock(n.getFirstChild()); boolean seenCatchOrFinally = false; // Validate catch Node catches = n.getChildAtIndex(1); validateNodeType(Token.BLOCK, catches); validateMaximumChildCount(catches, 1); if (catches.hasChildren()) { validateCatch(catches.getFirstChild()); seenCatchOrFinally = true; } // Validate finally if (n.getChildCount() == 3) { validateBlock(n.getLastChild()); seenCatchOrFinally = true; } if (!seenCatchOrFinally) { violation("Missing catch or finally for try statement.", n); } }
private void validateBreak(Node n) { validateNodeType(Token.BREAK, n); validateMaximumChildCount(n, 1); if (n.hasChildren()) { validateLabelName(n.getFirstChild()); } }
private void validateReturn(Node n) { validateNodeType(Token.RETURN, n); validateMaximumChildCount(n, 1); if (n.hasChildren()) { validateExpression(n.getFirstChild()); } }
private void handleContinue(Node node) { String label = null; if (node.hasChildren()) { label = node.getFirstChild().getString(); } Node cur; Node lastJump; // Similar to handBreak's logic with a few minor variation. Node parent = node.getParent(); for (cur = node, lastJump = node; !isContinueTarget(cur, parent, label); cur = parent, parent = parent.getParent()) { if (cur.getType() == Token.TRY && NodeUtil.hasFinally(cur)) { if (lastJump == node) { createEdge(lastJump, Branch.UNCOND, cur.getLastChild()); } else { finallyMap.put(lastJump, computeFallThrough(cur.getLastChild())); } lastJump = cur; } Preconditions.checkState(parent != null, "Cannot find continue target."); } Node iter = cur; if (cur.getChildCount() == 4) { iter = cur.getFirstChild().getNext().getNext(); } if (lastJump == node) { createEdge(node, Branch.UNCOND, iter); } else { finallyMap.put(lastJump, iter); } }
/** * Inline a function that fulfills the requirements of canInlineReferenceDirectly into the call * site, replacing only the CALL node. */ private Node inlineReturnValue(Node callNode, Node fnNode) { Node block = fnNode.getLastChild(); Node callParentNode = callNode.getParent(); // NOTE: As the normalize pass guarantees globals aren't being // shadowed and an expression can't introduce new names, there is // no need to check for conflicts. // Create an argName -> expression map, checking for side effects. Map<String, Node> argMap = FunctionArgumentInjector.getFunctionCallParameterMap( fnNode, callNode, this.safeNameIdSupplier); Node newExpression; if (!block.hasChildren()) { Node srcLocation = block; newExpression = NodeUtil.newUndefinedNode(srcLocation); } else { Node returnNode = block.getFirstChild(); Preconditions.checkArgument(returnNode.getType() == Token.RETURN); // Clone the return node first. Node safeReturnNode = returnNode.cloneTree(); Node inlineResult = FunctionArgumentInjector.inject(safeReturnNode, null, argMap); Preconditions.checkArgument(safeReturnNode == inlineResult); newExpression = safeReturnNode.removeFirstChild(); } callParentNode.replaceChild(callNode, newExpression); return newExpression; }
/** 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); } }
/** * Gets whether a node is a CALL node whose return value should be stripped. A call's return * value should be stripped if the function getting called is a static method in a class that * gets stripped. For example, if "goog.debug.Logger" is a strip name, then this function * returns true for a call such as "goog.debug.Logger.getLogger(...)". It may also simply be a * function that is getting stripped. For example, if "getLogger" is a strip name, but not * "goog.debug.Logger", this will still return true. * * @param n A node (typically a CALL node) * @return Whether the call's return value should be stripped */ boolean isCallWhoseReturnValueShouldBeStripped(@Nullable Node n) { return n != null && (n.getType() == Token.CALL || n.getType() == Token.NEW) && n.hasChildren() && (qualifiedNameBeginsWithStripType(n.getFirstChild()) || nameEndsWithFieldNameToStrip(n.getFirstChild())); }
/** * 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); } }
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; } }
private JSType getNamedTypeHelper( Node n, DeclaredTypeRegistry registry, ImmutableList<String> outerTypeParameters) throws UnknownTypeException { String typeName = n.getString(); switch (typeName) { case "boolean": return JSType.BOOLEAN; case "null": return JSType.NULL; case "number": return JSType.NUMBER; case "string": return JSType.STRING; case "undefined": case "void": return JSType.UNDEFINED; case "Function": return maybeMakeNullable(registry.getCommonTypes().qmarkFunction()); case "Object": // We don't generally handle parameterized Object<...>, but we want to // at least not warn about inexistent properties on it, so we type it // as @dict. return maybeMakeNullable(n.hasChildren() ? JSType.TOP_DICT : JSType.TOP_OBJECT); default: return lookupTypeByName(typeName, n, registry, outerTypeParameters); } }
private void validateYield(Node n) { validateEs6Feature("yield", n); validateNodeType(Token.YIELD, n); validateChildCountIn(n, 0, 1); if (n.hasChildren()) { validateExpression(n.getFirstChild()); } }
/** Converts extended object literal {a} to {a:a}. */ private void visitStringKey(Node n) { if (!n.hasChildren()) { Node name = IR.name(n.getString()); name.copyInformationFrom(n); n.addChildToBack(name); compiler.reportCodeChange(); } }
/** Converts extended object literal {a} to {a:a}. */ private void visitStringKey(Node n) { if (!n.hasChildren()) { Node name = IR.name(n.getString()); name.useSourceInfoIfMissingFrom(n); n.addChildToBack(name); compiler.reportCodeChange(); } }
void replaceNodeInPlace(Node n, Node replacement) { Node parent = n.getParent(); if (n.hasChildren()) { Node children = n.removeChildren(); replacement.addChildrenToFront(children); } parent.replaceChild(n, replacement); }
private JSType getNominalTypeHelper( RawNominalType rawType, Node n, DeclaredTypeRegistry registry, ImmutableList<String> outerTypeParameters) throws UnknownTypeException { NominalType uninstantiated = rawType.getAsNominalType(); if (!rawType.isGeneric() && !n.hasChildren()) { return rawType.getInstanceWithNullability(NULLABLE_TYPES_BY_DEFAULT); } ImmutableList.Builder<JSType> typeList = ImmutableList.builder(); if (n.hasChildren()) { // Compute instantiation of polymorphic class/interface. Preconditions.checkState(n.getFirstChild().isBlock(), n); for (Node child : n.getFirstChild().children()) { typeList.add(getTypeFromCommentHelper(child, registry, outerTypeParameters)); } } ImmutableList<JSType> typeArguments = typeList.build(); ImmutableList<String> typeParameters = rawType.getTypeParameters(); int typeArgsSize = typeArguments.size(); int typeParamsSize = typeParameters.size(); if (typeArgsSize != typeParamsSize) { // We used to also warn when (typeArgsSize < typeParamsSize), but it // happens so often that we stopped. Array, Object and goog.Promise are // common culprits, but many other types as well. if (typeArgsSize > typeParamsSize) { warnings.add( JSError.make( n, INVALID_GENERICS_INSTANTIATION, uninstantiated.getName(), String.valueOf(typeParamsSize), String.valueOf(typeArgsSize))); } return maybeMakeNullable( JSType.fromObjectType( ObjectType.fromNominalType( uninstantiated.instantiateGenerics( fixLengthOfTypeList(typeParameters.size(), typeArguments))))); } return maybeMakeNullable( JSType.fromObjectType( ObjectType.fromNominalType(uninstantiated.instantiateGenerics(typeArguments)))); }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (NodeUtil.isVarDeclaration(n) && removable.contains(n.getString())) { parent.removeChild(n); if (!parent.hasChildren()) { parent.getParent().removeChild(parent); } } }
private boolean hasShorthandAssignment(Node objLit) { Preconditions.checkState(objLit.isObjectLit()); for (Node property : objLit.children()) { if (property.isStringKey() && !property.hasChildren()) { return true; } } return false; }
private void visitObjectWithComputedProperty(Node obj, Node parent) { Preconditions.checkArgument(obj.isObjectLit()); List<Node> props = new ArrayList<>(); Node currElement = obj.getFirstChild(); while (currElement != null) { if (currElement.getBooleanProp(Node.COMPUTED_PROP_GETTER) || currElement.getBooleanProp(Node.COMPUTED_PROP_SETTER)) { cannotConvertYet(currElement, "computed getter/setter"); return; } else if (currElement.isGetterDef() || currElement.isSetterDef()) { currElement = currElement.getNext(); } else { Node nextNode = currElement.getNext(); obj.removeChild(currElement); props.add(currElement); currElement = nextNode; } } String objName = FRESH_COMP_PROP_VAR + compiler.getUniqueNameIdSupplier().get(); props = Lists.reverse(props); Node result = IR.name(objName); for (Node propdef : props) { if (propdef.isComputedProp()) { Node propertyExpression = propdef.removeFirstChild(); Node value = propdef.removeFirstChild(); result = IR.comma(IR.assign(IR.getelem(IR.name(objName), propertyExpression), value), result); } else { if (!propdef.hasChildren()) { Node name = IR.name(propdef.getString()).useSourceInfoIfMissingFrom(propdef); propdef.addChildToBack(name); } Node val = propdef.removeFirstChild(); propdef.setType(Token.STRING); int type = propdef.isQuotedString() ? Token.GETELEM : Token.GETPROP; Node access = new Node(type, IR.name(objName), propdef); result = IR.comma(IR.assign(access, val), result); } } Node statement = obj; while (!NodeUtil.isStatement(statement)) { statement = statement.getParent(); } result.useSourceInfoIfMissingFromForTree(obj); parent.replaceChild(obj, result); Node var = IR.var(IR.name(objName), obj); var.useSourceInfoIfMissingFromForTree(statement); statement.getParent().addChildBefore(var, statement); compiler.reportCodeChange(); }
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)); } }
/** @return The difference between the function definition cost and inline cost. */ private static int inlineCostDelta(Node fnNode, Set<String> namesToAlias, InliningMode mode) { // The part of the function that is never inlined: // "function xx(xx,xx){}" (15 + (param count * 3) -1; int paramCount = NodeUtil.getFnParameters(fnNode).getChildCount(); int commaCount = (paramCount > 1) ? paramCount - 1 : 0; int costDeltaFunctionOverhead = 15 + commaCount + (paramCount * InlineCostEstimator.ESTIMATED_IDENTIFIER_COST); Node block = fnNode.getLastChild(); if (!block.hasChildren()) { // Assume the inline cost is zero for empty functions. return -costDeltaFunctionOverhead; } if (mode == InliningMode.DIRECT) { // The part of the function that is inlined using direct inlining: // "return " (7) return -(costDeltaFunctionOverhead + 7); } else { int aliasCount = namesToAlias.size(); // Originally, we estimated purely base on the function code size, relying // on later optimizations. But that did not produce good results, so here // we try to estimate the something closer to the actual inlined coded. // NOTE 1: Result overhead is only if there is an assignment, but // getting that information would require some refactoring. // NOTE 2: The aliasing overhead is currently an under-estimate, // as some parameters are aliased because of the parameters used. // Perhaps we should just assume all parameters will be aliased? final int INLINE_BLOCK_OVERHEAD = 4; // "X:{}" final int PER_RETURN_OVERHEAD = 2; // "return" --> "break X" final int PER_RETURN_RESULT_OVERHEAD = 3; // "XX=" final int PER_ALIAS_OVERHEAD = 3; // "XX=" // TODO(johnlenz): Counting the number of returns is relatively expensive // this information should be determined during the traversal and // cached. int returnCount = NodeUtil.getNodeTypeReferenceCount( block, Token.RETURN, new NodeUtil.MatchShallowStatement()); int resultCount = (returnCount > 0) ? returnCount - 1 : 0; int baseOverhead = (returnCount > 0) ? INLINE_BLOCK_OVERHEAD : 0; int overhead = baseOverhead + returnCount * PER_RETURN_OVERHEAD + resultCount * PER_RETURN_RESULT_OVERHEAD + aliasCount * PER_ALIAS_OVERHEAD; return (overhead - costDeltaFunctionOverhead); } }
private void validateMinimumChildCount(Node n, int i) { boolean valid = false; if (i == 1) { valid = n.hasChildren(); } else if (i == 2) { valid = n.hasMoreThanOneChild(); } else { valid = n.getChildCount() >= i; } if (!valid) { violation("Expected at least " + i + " children, but was " + n.getChildCount(), n); } }
/** Remove useless calls: Object.defineProperties(o, {}) -> o */ private Node tryFoldCall(Node n) { Preconditions.checkArgument(n.isCall()); if (NodeUtil.isObjectDefinePropertiesDefinition(n)) { Node srcObj = n.getLastChild(); if (srcObj.isObjectLit() && !srcObj.hasChildren()) { Node parent = n.getParent(); Node destObj = n.getSecondChild().detachFromParent(); parent.replaceChild(n, destObj); reportCodeChange(); } } return n; }
/** * Build parameter and local information for the template and replace the references in the * template 'fn' with placeholder nodes use to facility matching. */ private void prepTemplatePlaceholders(Node fn) { final List<String> locals = templateLocals; final List<String> params = templateParams; final Map<String, JSType> paramTypes = new HashMap<>(); // drop the function name so it isn't include in the name maps String fnName = fn.getFirstChild().getString(); fn.getFirstChild().setString(""); // Build a list of parameter names and types. Node templateParametersNode = fn.getSecondChild(); JSDocInfo info = NodeUtil.getBestJSDocInfo(fn); if (templateParametersNode.hasChildren()) { Preconditions.checkNotNull( info, "Missing JSDoc declaration for template function %s", fnName); } for (Node paramNode : templateParametersNode.children()) { String name = paramNode.getString(); JSTypeExpression expression = info.getParameterType(name); Preconditions.checkNotNull( expression, "Missing JSDoc for parameter %s of template function %s", name, fnName); JSType type = expression.evaluate(null, compiler.getTypeIRegistry()); Preconditions.checkNotNull(type); params.add(name); paramTypes.put(name, type); } // Find references to local variables and parameters and replace them. traverse( fn, new Visitor() { @Override public void visit(Node n) { if (n.isName()) { Node parent = n.getParent(); String name = n.getString(); if (!name.isEmpty() && parent.isVar() && !locals.contains(name)) { locals.add(n.getString()); } if (params.contains(name)) { JSType type = paramTypes.get(name); replaceNodeInPlace(n, createTemplateParameterNode(params.indexOf(name), type)); } else if (locals.contains(name)) { replaceNodeInPlace(n, createTemplateLocalNameNode(locals.indexOf(name))); } } } }); }
private void validateObjectLitGetKey(Node n) { validateNodeType(Token.GETTER_DEF, n); validateChildCount(n); validateObjectLiteralKeyName(n); Node function = n.getFirstChild(); validateFunctionExpression(function); // objlit get functions must be nameless, and must have zero parameters. if (!function.getFirstChild().getString().isEmpty()) { violation("Expected unnamed function expression.", n); } Node functionParams = function.getChildAtIndex(1); if (functionParams.hasChildren()) { violation("get methods must not have parameters.", n); } }
/** * Removes declarations of any variables whose names are strip names or whose whose rvalues are * static method calls on strip types. Builds a set of removed variables so that all references * to them can be removed. * * @param t The traversal * @param n A VAR node * @param parent {@code n}'s parent */ void removeVarDeclarationsByNameOrRvalue(NodeTraversal t, Node n, Node parent) { for (Node nameNode = n.getFirstChild(); nameNode != null; nameNode = nameNode.getNext()) { String name = nameNode.getString(); if (isStripName(name) || isCallWhoseReturnValueShouldBeStripped(nameNode.getFirstChild())) { // Remove the NAME. Scope scope = t.getScope(); varsToRemove.add(scope.getVar(name)); n.removeChild(nameNode); compiler.reportCodeChange(); } } if (!n.hasChildren()) { // Must also remove the VAR. replaceWithEmpty(n, parent); compiler.reportCodeChange(); } }
/** * Flattens a particular prefix of a single name reference. * * @param alias A flattened prefix name (e.g. "a$b") * @param n The node corresponding to a subproperty name (e.g. "a.b.c.d") * @param depth The difference in depth between the property name and the prefix name (e.g. 2) * @param originalName String version of the property name. */ private void flattenNameRefAtDepth(String alias, Node n, int depth, String originalName) { // This method has to work for both GETPROP chains and, in rare cases, // OBJLIT keys, possibly nested. That's why we check for children before // proceeding. In the OBJLIT case, we don't need to do anything. int nType = n.getType(); boolean isQName = nType == Token.NAME || nType == Token.GETPROP; boolean isObjKey = NodeUtil.isObjectLitKey(n); Preconditions.checkState(isObjKey || isQName); if (isQName) { for (int i = 1; i < depth && n.hasChildren(); i++) { n = n.getFirstChild(); } if (n.isGetProp() && n.getFirstChild().isGetProp()) { flattenNameRef(alias, n.getFirstChild(), n, originalName); } } }
private void validateTemplateLit(Node n) { validateEs6Feature("template literal", n); validateNodeType(Token.TEMPLATELIT, n); if (!n.hasChildren()) { return; } for (int i = 0; i < n.getChildCount(); i++) { Node child = n.getChildAtIndex(i); // If the first child is not a STRING, this is a tagged template. if (i == 0 && !child.isString()) { validateExpression(child); } else if (child.isString()) { validateString(child); } else { validateTemplateLitSub(child); } } }