/** Traverses a parse tree recursively with a scope, starting at that scope's root. */ void traverseAtScope(Scope s) { Node n = s.getRootNode(); if (n.isFunction()) { // We need to do some extra magic to make sure that the scope doesn't // get re-created when we dive into the function. if (inputId == null) { inputId = NodeUtil.getInputId(n); } sourceName = getSourceName(n); curNode = n; pushScope(s); Node args = n.getFirstChild().getNext(); Node body = args.getNext(); traverseBranch(args, n); traverseBranch(body, n); popScope(); } else if (n.isBlock()) { if (inputId == null) { inputId = NodeUtil.getInputId(n); } sourceName = getSourceName(n); curNode = n; pushScope(s); traverseBranch(n, n.getParent()); popScope(); } else { Preconditions.checkState(s.isGlobal(), "Expected global scope. Got:", s); traverseWithScope(n, s); } }
/** * @param fnName The name of this function. This either the name of the variable to which the * function is assigned or the name from the FUNCTION node. * @param fnNode The FUNCTION node of the function to inspect. * @return Whether the function node meets the minimum requirements for inlining. */ boolean doesFunctionMeetMinimumRequirements(final String fnName, Node fnNode) { Node block = NodeUtil.getFunctionBody(fnNode); // Basic restrictions on functions that can be inlined: // 1) It contains a reference to itself. // 2) It uses its parameters indirectly using "arguments" (it isn't // handled yet. // 3) It references "eval". Inline a function containing eval can have // large performance implications. final String fnRecursionName = fnNode.getFirstChild().getString(); Preconditions.checkState(fnRecursionName != null); Predicate<Node> p = new Predicate<Node>() { public boolean apply(Node n) { if (n.getType() == Token.NAME) { return n.getString().equals("arguments") || n.getString().equals("eval") || n.getString().equals(fnName) || (fnRecursionName.length() != 0 && n.getString().equals(fnRecursionName)); } return false; } }; return !NodeUtil.has(block, p, Predicates.<Node>alwaysTrue()); }
private static boolean isLhsOfEnhancedForExpression(Node n) { Node parent = n.getParent(); if (NodeUtil.isNameDeclaration(parent)) { return isLhsOfEnhancedForExpression(parent); } return NodeUtil.isEnhancedFor(parent) && parent.getFirstChild() == n; }
private void scanRoot(Node n) { if (n.isFunction()) { if (inputId == null) { inputId = NodeUtil.getInputId(n); // TODO(johnlenz): inputId maybe null if the FUNCTION node is detached // from the AST. // Is it meaningful to build a scope for detached FUNCTION node? } final Node fnNameNode = n.getFirstChild(); final Node args = fnNameNode.getNext(); final Node body = args.getNext(); // Bleed the function name into the scope, if it hasn't // been declared in the outer scope. String fnName = fnNameNode.getString(); if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) { declareVar(fnNameNode); } // Args: Declare function variables Preconditions.checkState(args.isParamList()); for (Node a = args.getFirstChild(); a != null; a = a.getNext()) { Preconditions.checkState(a.isName()); declareVar(a); } // Body scanVars(body); } else { // It's the global block Preconditions.checkState(scope.getParent() == null); scanVars(n); } }
/** * If we haven't found a return value yet, try to look at the "return" statements in the function. */ FunctionTypeBuilder inferReturnStatementsAsLastResort(@Nullable Node functionBlock) { if (functionBlock == null || compiler.getInput(sourceName).isExtern()) { return this; } Preconditions.checkArgument(functionBlock.getType() == Token.BLOCK); if (returnType == null) { boolean hasNonEmptyReturns = false; List<Node> worklist = Lists.newArrayList(functionBlock); while (!worklist.isEmpty()) { Node current = worklist.remove(worklist.size() - 1); int cType = current.getType(); if (cType == Token.RETURN && current.getFirstChild() != null || cType == Token.THROW) { hasNonEmptyReturns = true; break; } else if (NodeUtil.isStatementBlock(current) || NodeUtil.isControlStructure(current)) { for (Node child = current.getFirstChild(); child != null; child = child.getNext()) { worklist.add(child); } } } if (!hasNonEmptyReturns) { returnType = typeRegistry.getNativeType(VOID_TYPE); returnTypeInferred = true; } } return this; }
/** If this is an assign to a variable or its property, return it. Otherwise, return null. */ static Assign maybeCreateAssign(Node assignNode) { Preconditions.checkState(NodeUtil.isAssignmentOp(assignNode)); // Skip one level of GETPROPs or GETELEMs. // // Don't skip more than one level, because then we get into // situations where assigns to properties of properties will always // trigger side-effects, and the variable they're on cannot be removed. boolean isPropAssign = false; Node current = assignNode.getFirstChild(); if (NodeUtil.isGet(current)) { current = current.getFirstChild(); isPropAssign = true; if (current.getType() == Token.GETPROP && current.getLastChild().getString().equals("prototype")) { // Prototype properties sets should be considered like normal // property sets. current = current.getFirstChild(); } } if (current.getType() == Token.NAME) { return new Assign(assignNode, current, isPropAssign); } return null; }
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 void addExportMethod( Map<String, GenerateNodeContext> exports, String export, GenerateNodeContext context) { CodingConvention convention = compiler.getCodingConvention(); // Emit the proper CALL expression. // This is an optimization to avoid exporting everything as a symbol // because exporting a property is significantly simpler/faster. // Only export the property if the parent is being exported or // if the parent is "prototype" and the grandparent is being exported. String parent = null; String grandparent = null; Node node = context.getNode().getFirstChild(); if (node.isGetProp()) { Node parentNode = node.getFirstChild(); parent = parentNode.getQualifiedName(); if (parentNode.isGetProp() && parentNode.getLastChild().getString().equals(PROTOTYPE_PROPERTY)) { grandparent = parentNode.getFirstChild().getQualifiedName(); } } boolean useExportSymbol = true; if (grandparent != null && exports.containsKey(grandparent)) { // grandparent is only set for properties exported off a prototype obj. useExportSymbol = false; } else if (parent != null && exports.containsKey(parent)) { useExportSymbol = false; } Node call; if (useExportSymbol) { // exportSymbol(publicPath, object); call = IR.call( NodeUtil.newQualifiedNameNode( convention, exportSymbolFunction, context.getNode(), export), IR.string(export), NodeUtil.newQualifiedNameNode(convention, export, context.getNode(), export)); } else { // exportProperty(object, publicName, symbol); String property = getPropertyName(node); call = IR.call( NodeUtil.newQualifiedNameNode( convention, exportPropertyFunction, context.getNode(), exportPropertyFunction), NodeUtil.newQualifiedNameNode( convention, parent, context.getNode(), exportPropertyFunction), IR.string(property), NodeUtil.newQualifiedNameNode( convention, export, context.getNode(), exportPropertyFunction)); } Node expression = IR.exprResult(call); annotate(expression); addStatement(context, expression); compiler.reportCodeChange(); }
@Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { // Function calls case Token.CALL: Node child = n.getFirstChild(); String name = null; // NOTE: The normalization pass insures that local names do not // collide with global names. if (child.isName()) { name = child.getString(); } else if (child.isFunction()) { name = anonFunctionMap.get(child); } else if (NodeUtil.isFunctionObjectCall(n)) { Preconditions.checkState(NodeUtil.isGet(child)); Node fnIdentifingNode = child.getFirstChild(); if (fnIdentifingNode.isName()) { name = fnIdentifingNode.getString(); } else if (fnIdentifingNode.isFunction()) { name = anonFunctionMap.get(fnIdentifingNode); } } if (name != null) { FunctionState fs = functionMap.get(name); // Only visit call-sites for functions that can be inlined. if (fs != null) { callback.visitCallSite(t, n, fs); } } break; } }
/** * Connects cfgNode to the proper CATCH block if target subtree might throw an exception. If there * are FINALLY blocks reached before a CATCH, it will make the corresponding entry in finallyMap. */ private void connectToPossibleExceptionHandler(Node cfgNode, Node target) { if (mayThrowException(target) && !exceptionHandler.isEmpty()) { Node lastJump = cfgNode; for (Node handler : exceptionHandler) { if (NodeUtil.isFunction(handler)) { return; } Preconditions.checkState(handler.getType() == Token.TRY); Node catchBlock = NodeUtil.getCatchBlock(handler); if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY. if (lastJump == cfgNode) { createEdge(cfgNode, Branch.ON_EX, handler.getLastChild()); } else { finallyMap.put(lastJump, handler.getLastChild()); } } else { // Has a catch. if (lastJump == cfgNode) { createEdge(cfgNode, Branch.ON_EX, catchBlock); return; } else { finallyMap.put(lastJump, catchBlock); } } lastJump = handler; } } }
/** Try to fold {@code left instanceof right} into {@code true} or {@code false}. */ private Node tryFoldInstanceof(Node n, Node left, Node right) { Preconditions.checkArgument(n.isInstanceOf()); // TODO(johnlenz) Use type information if available to fold // instanceof. if (NodeUtil.isLiteralValue(left, true) && !mayHaveSideEffects(right)) { Node replacementNode = null; if (NodeUtil.isImmutableValue(left)) { // Non-object types are never instances. replacementNode = IR.falseNode(); } else if (right.isName() && "Object".equals(right.getString())) { replacementNode = IR.trueNode(); } if (replacementNode != null) { n.getParent().replaceChild(n, replacementNode); reportCodeChange(); return replacementNode; } } return n; }
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; } }
static TernaryValue evaluateComparison(Token op, Node left, Node right, boolean useTypes) { // Don't try to minimize side-effects here. if (NodeUtil.mayHaveSideEffects(left) || NodeUtil.mayHaveSideEffects(right)) { return TernaryValue.UNKNOWN; } switch (op) { case EQ: return tryAbstractEqualityComparison(left, right, useTypes); case NE: return tryAbstractEqualityComparison(left, right, useTypes).not(); case SHEQ: return tryStrictEqualityComparison(left, right, useTypes); case SHNE: return tryStrictEqualityComparison(left, right, useTypes).not(); case LT: return tryAbstractRelationalComparison(left, right, useTypes, false); case GT: return tryAbstractRelationalComparison(right, left, useTypes, false); case LE: return tryAbstractRelationalComparison(right, left, useTypes, true).not(); case GE: return tryAbstractRelationalComparison(left, right, useTypes, true).not(); } throw new IllegalStateException("Unexpected operator for comparison"); }
/** * Replaces a GETPROP a.b.c with a NAME a$b$c. * * @param alias A flattened prefix name (e.g. "a$b") * @param n The GETPROP node corresponding to the original name (e.g. "a.b") * @param parent {@code n}'s parent * @param originalName String version of the property name. */ private void flattenNameRef(String alias, Node n, Node parent, String originalName) { Preconditions.checkArgument( n.isGetProp(), "Expected GETPROP, found %s. Node: %s", Token.name(n.getType()), n); // BEFORE: // getprop // getprop // name a // string b // string c // AFTER: // name a$b$c Node ref = NodeUtil.newName(compiler, alias, n, originalName); NodeUtil.copyNameAnnotations(n.getLastChild(), ref); if (parent.isCall() && n == parent.getFirstChild()) { // The node was a call target, we are deliberately flatten these as // we node the "this" isn't provided by the namespace. Mark it as such: parent.putBooleanProp(Node.FREE_CALL, true); } TypeI type = n.getTypeI(); if (type != null) { ref.setTypeI(type); } parent.replaceChild(n, ref); compiler.reportCodeChange(); }
/** * Adds an interface for the given ClassDefinition to externs. This allows generated setter * functions for read-only properties to avoid renaming altogether. * * @see https://www.polymer-project.org/0.8/docs/devguide/properties.html#read-only */ private void addInterfaceExterns( final ClassDefinition cls, List<MemberDefinition> readOnlyProps) { Node block = IR.block(); String interfaceName = getInterfaceName(cls); Node fnNode = IR.function(IR.name(""), IR.paramList(), IR.block()); Node varNode = IR.var(NodeUtil.newQName(compiler, interfaceName), fnNode); JSDocInfoBuilder info = new JSDocInfoBuilder(true); info.recordInterface(); varNode.setJSDocInfo(info.build()); block.addChildToBack(varNode); appendPropertiesToBlock(cls, block, interfaceName + ".prototype."); for (MemberDefinition prop : readOnlyProps) { // Add all _set* functions to avoid renaming. String propName = prop.name.getString(); String setterName = "_set" + propName.substring(0, 1).toUpperCase() + propName.substring(1); Node setterExprNode = IR.exprResult(NodeUtil.newQName(compiler, interfaceName + ".prototype." + setterName)); JSDocInfoBuilder setterInfo = new JSDocInfoBuilder(true); JSTypeExpression propType = getTypeFromProperty(prop); setterInfo.recordParameter(propName, propType); setterExprNode.getFirstChild().setJSDocInfo(setterInfo.build()); block.addChildToBack(setterExprNode); } Node parent = polymerElementExterns.getParent(); Node stmts = block.removeChildren(); parent.addChildrenToBack(stmts); compiler.reportCodeChange(); }
/** * @param name The Name whose properties references should be updated. * @param value The value to use when rewriting. * @param depth The chain depth. * @param newNodes Expression nodes that have been updated. */ private static void rewriteAliasProps(Name name, Node value, int depth, Set<AstChange> newNodes) { if (name.props == null) { return; } Preconditions.checkState(!value.matchesQualifiedName(name.getFullName())); for (Name prop : name.props) { rewriteAliasProps(prop, value, depth + 1, newNodes); List<Ref> refs = new ArrayList<>(prop.getRefs()); for (Ref ref : refs) { Node target = ref.node; for (int i = 0; i <= depth; i++) { if (target.isGetProp()) { target = target.getFirstChild(); } else if (NodeUtil.isObjectLitKey(target)) { // Object literal key definitions are a little trickier, as we // need to find the assignment target Node gparent = target.getParent().getParent(); if (gparent.isAssign()) { target = gparent.getFirstChild(); } else { Preconditions.checkState(NodeUtil.isObjectLitKey(gparent)); target = gparent; } } else { throw new IllegalStateException("unexpected: " + target); } } Preconditions.checkState(target.isGetProp() || target.isName()); target.getParent().replaceChild(target, value.cloneTree()); prop.removeRef(ref); // Rescan the expression root. newNodes.add(new AstChange(ref.module, ref.scope, ref.node)); } } }
private void addStatement(GenerateNodeContext context, Node stmt) { CodingConvention convention = compiler.getCodingConvention(); Node n = context.getNode(); Node exprRoot = n; while (!NodeUtil.isStatementBlock(exprRoot.getParent())) { exprRoot = exprRoot.getParent(); } // It's important that any class-building calls (goog.inherits) // come right after the class definition, so move the export after that. while (true) { Node next = exprRoot.getNext(); if (next != null && NodeUtil.isExprCall(next) && convention.getClassesDefinedByCall(next.getFirstChild()) != null) { exprRoot = next; } else { break; } } Node block = exprRoot.getParent(); block.addChildAfter(stmt, exprRoot); }
private void addToDefinePropertiesObject(ClassDeclarationMetadata metadata, Node member) { Node obj = member.isStaticMember() ? metadata.definePropertiesObjForClass : metadata.definePropertiesObjForPrototype; Node prop = NodeUtil.getFirstPropMatchingKey(obj, member.getString()); if (prop == null) { prop = IR.objectlit( IR.stringKey("configurable", IR.trueNode()), IR.stringKey("enumerable", IR.trueNode())); obj.addChildToBack(IR.stringKey(member.getString(), prop)); } Node function = member.getLastChild(); JSDocInfoBuilder info = JSDocInfoBuilder.maybeCopyFrom(NodeUtil.getBestJSDocInfo(function)); info.recordThisType( new JSTypeExpression( new Node(Token.BANG, IR.string(metadata.fullClassName)), member.getSourceFileName())); Node stringKey = IR.stringKey(member.isGetterDef() ? "get" : "set", function.detachFromParent()); stringKey.setJSDocInfo(info.build()); prop.addChildToBack(stringKey); prop.useSourceInfoIfMissingFromForTree(member); }
/** * Returns the nth argument node given a usage site for a direct function call or for a * func.call() node. */ private static Node getArgumentForCallOrNewOrDotCall(UseSite site, final int argIndex) { int adjustedArgIndex = argIndex; Node parent = site.node.getParent(); if (NodeUtil.isFunctionObjectCall(parent)) { adjustedArgIndex++; } return NodeUtil.getArgumentForCallOrNew(parent, adjustedArgIndex); }
/** * Processes array literals or calls containing spreads. Eg.: [1, 2, ...x, 4, 5] => [1, * 2].concat(x, [4, 5]); Eg.: f(...arr) => f.apply(null, arr) Eg.: new F(...args) => new * Function.prototype.bind.apply(F, [].concat(args)) */ private void visitArrayLitOrCallWithSpread(Node node, Node parent) { Preconditions.checkArgument(node.isCall() || node.isArrayLit() || node.isNew()); List<Node> groups = new ArrayList<>(); Node currGroup = null; Node callee = node.isArrayLit() ? null : node.removeFirstChild(); Node currElement = node.removeFirstChild(); while (currElement != null) { if (currElement.isSpread()) { if (currGroup != null) { groups.add(currGroup); currGroup = null; } groups.add(currElement.removeFirstChild()); } else { if (currGroup == null) { currGroup = IR.arraylit(); } currGroup.addChildToBack(currElement); } currElement = node.removeFirstChild(); } if (currGroup != null) { groups.add(currGroup); } Node result = null; Node joinedGroups = IR.call( IR.getprop(IR.arraylit(), IR.string("concat")), groups.toArray(new Node[groups.size()])); if (node.isArrayLit()) { result = joinedGroups; } else if (node.isCall()) { if (NodeUtil.mayHaveSideEffects(callee) && callee.isGetProp()) { Node statement = node; while (!NodeUtil.isStatement(statement)) { statement = statement.getParent(); } Node freshVar = IR.name(FRESH_SPREAD_VAR + freshSpreadVarCounter++); Node n = IR.var(freshVar.cloneTree()); n.useSourceInfoIfMissingFromForTree(statement); statement.getParent().addChildBefore(n, statement); callee.addChildToFront(IR.assign(freshVar.cloneTree(), callee.removeFirstChild())); result = IR.call(IR.getprop(callee, IR.string("apply")), freshVar, joinedGroups); } else { Node context = callee.isGetProp() ? callee.getFirstChild().cloneTree() : IR.nullNode(); result = IR.call(IR.getprop(callee, IR.string("apply")), context, joinedGroups); } } else { Node bindApply = NodeUtil.newQualifiedNameNode( compiler.getCodingConvention(), "Function.prototype.bind.apply"); result = IR.newNode(bindApply, callee, joinedGroups); } result.useSourceInfoIfMissingFromForTree(node); parent.replaceChild(node, result); compiler.reportCodeChange(); }
private void checkClassReassignment(Node clazz) { Node name = NodeUtil.getClassNameNode(clazz); Node enclosingFunction = NodeUtil.getEnclosingFunction(clazz); if (enclosingFunction == null) { return; } CheckClassAssignments checkAssigns = new CheckClassAssignments(name); NodeTraversal.traverseEs6(compiler, enclosingFunction, checkAssigns); }
private Node tryFoldArrayAccess(Node n, Node left, Node right) { // If GETPROP/GETELEM is used as assignment target the array literal is // acting as a temporary we can't fold it here: // "[][0] += 1" if (NodeUtil.isAssignmentTarget(n)) { return n; } if (!right.isNumber()) { // Sometimes people like to use complex expressions to index into // arrays, or strings to index into array methods. return n; } double index = right.getDouble(); int intIndex = (int) index; if (intIndex != index) { report(INVALID_GETELEM_INDEX_ERROR, right); return n; } if (intIndex < 0) { report(INDEX_OUT_OF_BOUNDS_ERROR, right); return n; } Node current = left.getFirstChild(); Node elem = null; for (int i = 0; current != null; i++) { if (i != intIndex) { if (mayHaveSideEffects(current)) { return n; } } else { elem = current; } current = current.getNext(); } if (elem == null) { report(INDEX_OUT_OF_BOUNDS_ERROR, right); return n; } if (elem.isEmpty()) { elem = NodeUtil.newUndefinedNode(elem); } else { left.removeChild(elem); } // Replace the entire GETELEM with the value n.getParent().replaceChild(n, elem); reportCodeChange(); return elem; }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (!n.isFunction()) { return; } int id = functionNames.getFunctionId(n); if (id < 0) { // Function node was added during compilation; don't instrument. return; } String name = functionNames.getFunctionName(n); if (!reportFunctionName.isEmpty()) { Node body = n.getLastChild(); Node call = IR.call( IR.name(reportFunctionName), IR.number(id), IR.string(name), IR.name("arguments")); call.putBooleanProp(Node.FREE_CALL, true); Node expr = IR.exprResult(call); expr.useSourceInfoFromForTree(n); body.addChildToFront(expr); compiler.reportCodeChange(); } if (!reportFunctionExitName.isEmpty()) { (new InstrumentReturns(id, name)).process(n); } if (!definedFunctionName.isEmpty()) { Node call = IR.call(IR.name(definedFunctionName), IR.number(id), IR.string(name)); call.putBooleanProp(Node.FREE_CALL, true); call.useSourceInfoFromForTree(n); Node expr = NodeUtil.newExpr(call); Node addingRoot = null; if (NodeUtil.isFunctionDeclaration(n)) { JSModule module = t.getModule(); addingRoot = compiler.getNodeForCodeInsertion(module); addingRoot.addChildToFront(expr); } else { Node beforeChild = n; for (Node ancestor : n.getAncestors()) { Token type = ancestor.getType(); if (type == Token.BLOCK || type == Token.SCRIPT) { addingRoot = ancestor; break; } beforeChild = ancestor; } addingRoot.addChildBefore(expr, beforeChild); } compiler.reportCodeChange(); } }
/** Appends all required behavior functions and non-property members to the given block. */ private void appendBehaviorMembersToBlock(final ClassDefinition cls, Node block) { String qualifiedPath = cls.target.getQualifiedName() + ".prototype."; Map<String, Node> nameToExprResult = new HashMap<>(); for (BehaviorDefinition behavior : cls.behaviors) { for (MemberDefinition behaviorFunction : behavior.functionsToCopy) { String fnName = behaviorFunction.name.getString(); // Don't copy functions already defined by the element itself. if (NodeUtil.getFirstPropMatchingKey(cls.descriptor, fnName) != null) { continue; } // Avoid copying over the same function twice. The last definition always wins. if (nameToExprResult.containsKey(fnName)) { block.removeChild(nameToExprResult.get(fnName)); } Node fnValue = behaviorFunction.value.cloneTree(); Node exprResult = IR.exprResult(IR.assign(NodeUtil.newQName(compiler, qualifiedPath + fnName), fnValue)); JSDocInfoBuilder info = JSDocInfoBuilder.maybeCopyFrom(behaviorFunction.info); // Behaviors whose declarations are not in the global scope may contain references to // symbols which do not exist in the element's scope. Only copy a function stub. See if (!behavior.isGlobalDeclaration) { NodeUtil.getFunctionBody(fnValue).removeChildren(); } exprResult.getFirstChild().setJSDocInfo(info.build()); block.addChildToBack(exprResult); nameToExprResult.put(fnName, exprResult); } // Copy other members. for (MemberDefinition behaviorProp : behavior.nonPropertyMembersToCopy) { String propName = behaviorProp.name.getString(); if (nameToExprResult.containsKey(propName)) { block.removeChild(nameToExprResult.get(propName)); } Node exprResult = IR.exprResult(NodeUtil.newQName(compiler, qualifiedPath + propName)); JSDocInfoBuilder info = JSDocInfoBuilder.maybeCopyFrom(behaviorProp.info); if (behaviorProp.name.isGetterDef()) { info = new JSDocInfoBuilder(true); if (behaviorProp.info != null && behaviorProp.info.getReturnType() != null) { info.recordType(behaviorProp.info.getReturnType()); } } exprResult.getFirstChild().setJSDocInfo(info.build()); block.addChildToBack(exprResult); nameToExprResult.put(propName, exprResult); } } }
/** Scans and gather variables declarations under a Node */ private void scanVars(Node n, Node parent) { switch (n.getType()) { case Token.VAR: // Declare all variables. e.g. var x = 1, y, z; for (Node child = n.getFirstChild(); child != null; ) { Node next = child.getNext(); Preconditions.checkState(child.getType() == Token.NAME); String name = child.getString(); declareVar(name, child, n, parent, null, n); child = next; } return; case Token.FUNCTION: if (NodeUtil.isFunctionExpression(n)) { return; } String fnName = n.getFirstChild().getString(); if (fnName.isEmpty()) { // This is invalid, but allow it so the checks can catch it. return; } declareVar(fnName, n.getFirstChild(), n, parent, null, n); return; // should not examine function's children case Token.CATCH: Preconditions.checkState(n.getChildCount() == 2); Preconditions.checkState(n.getFirstChild().getType() == Token.NAME); // the first child is the catch var and the third child // is the code block final Node var = n.getFirstChild(); final Node block = var.getNext(); declareVar(var.getString(), var, n, parent, null, n); scanVars(block, n); return; // only one child to scan case Token.SCRIPT: sourceName = (String) n.getProp(Node.SOURCENAME_PROP); break; } // Variables can only occur in statement-level nodes, so // we only need to traverse children in a couple special cases. if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) { for (Node child = n.getFirstChild(); child != null; ) { Node next = child.getNext(); scanVars(child, n); child = next; } } }
Assign(Node assignNode, Node nameNode, boolean isPropertyAssign) { Preconditions.checkState(NodeUtil.isAssignmentOp(assignNode)); this.assignNode = assignNode; this.nameNode = nameNode; this.isPropertyAssign = isPropertyAssign; this.mayHaveSecondarySideEffects = assignNode.getParent().getType() != Token.EXPR_RESULT || NodeUtil.mayHaveSideEffects(assignNode.getFirstChild()) || NodeUtil.mayHaveSideEffects(assignNode.getLastChild()); }
/** @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); } }
public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() != Token.FUNCTION) { return; } int id = functionNames.getFunctionId(n); if (id < 0) { // Function node was added during compilation; don't instrument. return; } if (reportFunctionName.length() != 0) { Node body = n.getFirstChild().getNext().getNext(); Node call = new Node( Token.CALL, Node.newString(Token.NAME, reportFunctionName), Node.newNumber(id)); Node expr = new Node(Token.EXPR_RESULT, call); body.addChildToFront(expr); compiler.reportCodeChange(); } if (reportFunctionExitName.length() != 0) { Node body = n.getFirstChild().getNext().getNext(); (new InstrumentReturns(id)).process(body); } if (definedFunctionName.length() != 0) { Node call = new Node( Token.CALL, Node.newString(Token.NAME, definedFunctionName), Node.newNumber(id)); Node expr = NodeUtil.newExpr(call); Node addingRoot = null; if (NodeUtil.isFunctionDeclaration(n)) { JSModule module = t.getModule(); addingRoot = compiler.getNodeForCodeInsertion(module); addingRoot.addChildToFront(expr); } else { Node beforeChild = n; for (Node ancestor : n.getAncestors()) { int type = ancestor.getType(); if (type == Token.BLOCK || type == Token.SCRIPT) { addingRoot = ancestor; break; } beforeChild = ancestor; } addingRoot.addChildBefore(expr, beforeChild); } compiler.reportCodeChange(); } }
private Node baseCall(Node clazz, String methodName, Node arguments) { boolean useUnique = NodeUtil.isStatement(clazz) && !isInFunction(clazz); String uniqueClassString = useUnique ? getUniqueClassName(NodeUtil.getClassName(clazz)) : NodeUtil.getClassName(clazz); Node uniqueClassName = NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), uniqueClassString); Node base = IR.getprop(uniqueClassName, IR.string("base")); Node call = IR.call(base, IR.thisNode(), IR.string(methodName)); if (arguments != null) { call.addChildrenToBack(arguments); } return call; }
boolean isLvalue() { Node parent = getParent(); int parentType = parent.getType(); return (parentType == Token.VAR && nameNode.getFirstChild() != null) || (parentType == Token.LET && nameNode.getFirstChild() != null) || (parentType == Token.CONST && nameNode.getFirstChild() != null) || (parentType == Token.DEFAULT_VALUE && parent.getFirstChild() == nameNode) || parentType == Token.INC || parentType == Token.DEC || parentType == Token.CATCH || (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == nameNode) || isLhsOfEnhancedForExpression(nameNode) || NodeUtil.isLhsByDestructuring(nameNode); }