/** @param marker The marker to add synthetic blocks for. */ private void addBlocks(Marker marker) { // Add block around the template section so that it looks like this: // BLOCK (synthetic) // START // BLOCK (synthetic) // BODY // END // This prevents the start or end markers from mingling with the code // in the block body. Node originalParent = marker.endMarker.getParent(); Node outerBlock = IR.block(); outerBlock.setIsSyntheticBlock(true); originalParent.addChildBefore(outerBlock, marker.startMarker); Node innerBlock = IR.block(); innerBlock.setIsSyntheticBlock(true); // Move everything after the start Node up to the end Node into the inner // block. moveSiblingExclusive(originalParent, innerBlock, marker.startMarker, marker.endMarker); // Add the start node. outerBlock.addChildToBack(originalParent.removeChildAfter(outerBlock)); // Add the inner block outerBlock.addChildToBack(innerBlock); // and finally the end node. outerBlock.addChildToBack(originalParent.removeChildAfter(outerBlock)); compiler.reportCodeChange(); }
@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(); } }
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(); } }
/** * Declares global variables to serve as aliases for the values in an object literal, optionally * removing all of the object literal's keys and values. * * @param alias The object literal's flattened name (e.g. "a$b$c") * @param objlit The OBJLIT node * @param varNode The VAR node to which new global variables should be added as children * @param nameToAddAfter The child of {@code varNode} after which new variables should be added * (may be null) * @param varParent {@code varNode}'s parent * @return The number of variables added */ private int declareVarsForObjLitValues( Name objlitName, String alias, Node objlit, Node varNode, Node nameToAddAfter, Node varParent) { int numVars = 0; int arbitraryNameCounter = 0; boolean discardKeys = !objlitName.shouldKeepKeys(); for (Node key = objlit.getFirstChild(), nextKey; key != null; key = nextKey) { Node value = key.getFirstChild(); nextKey = key.getNext(); // A get or a set can not be rewritten as a VAR. if (key.isGetterDef() || key.isSetterDef()) { continue; } // We generate arbitrary names for keys that aren't valid JavaScript // identifiers, since those keys are never referenced. (If they were, // this object literal's child names wouldn't be collapsible.) The only // reason that we don't eliminate them entirely is the off chance that // their values are expressions that have side effects. boolean isJsIdentifier = !key.isNumber() && TokenStream.isJSIdentifier(key.getString()); String propName = isJsIdentifier ? key.getString() : String.valueOf(++arbitraryNameCounter); // If the name cannot be collapsed, skip it. String qName = objlitName.getFullName() + '.' + propName; Name p = nameMap.get(qName); if (p != null && !p.canCollapse()) { continue; } String propAlias = appendPropForAlias(alias, propName); Node refNode = null; if (discardKeys) { objlit.removeChild(key); value.detachFromParent(); } else { // Substitute a reference for the value. refNode = IR.name(propAlias); if (key.getBooleanProp(Node.IS_CONSTANT_NAME)) { refNode.putBooleanProp(Node.IS_CONSTANT_NAME, true); } key.replaceChild(value, refNode); } // Declare the collapsed name as a variable with the original value. Node nameNode = IR.name(propAlias); nameNode.addChildToFront(value); if (key.getBooleanProp(Node.IS_CONSTANT_NAME)) { nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true); } Node newVar = IR.var(nameNode).useSourceInfoIfMissingFromForTree(key); if (nameToAddAfter != null) { varParent.addChildAfter(newVar, nameToAddAfter); } else { varParent.addChildBefore(newVar, varNode); } compiler.reportCodeChange(); nameToAddAfter = newVar; // Update the global name's node ancestry if it hasn't already been // done. (Duplicate keys in an object literal can bring us here twice // for the same global name.) if (isJsIdentifier && p != null) { if (!discardKeys) { Ref newAlias = p.getDeclaration().cloneAndReclassify(Ref.Type.ALIASING_GET); newAlias.node = refNode; p.addRef(newAlias); } p.getDeclaration().node = nameNode; if (value.isFunction()) { checkForHosedThisReferences(value, key.getJSDocInfo(), p); } } numVars++; } return numVars; }
/** * Updates the initial assignment to a collapsible property at global scope by changing it to a * variable declaration (e.g. a.b = 1 -> var a$b = 1). The property's value may either be a * primitive or an object literal or function whose properties aren't collapsible. * * @param alias The flattened property name (e.g. "a$b") * @param refName The name for the reference being updated. * @param ref An object containing information about the assignment getting updated */ private void updateSimpleDeclaration(String alias, Name refName, Ref ref) { Node rvalue = ref.node.getNext(); Node parent = ref.node.getParent(); Node grandparent = parent.getParent(); Node greatGrandparent = grandparent.getParent(); if (rvalue != null && rvalue.isFunction()) { checkForHosedThisReferences(rvalue, refName.docInfo, refName); } // Create the new alias node. Node nameNode = NodeUtil.newName(compiler, alias, grandparent.getFirstChild(), refName.getFullName()); NodeUtil.copyNameAnnotations(ref.node.getLastChild(), nameNode); if (grandparent.isExprResult()) { // BEFORE: a.b.c = ...; // exprstmt // assign // getprop // getprop // name a // string b // string c // NODE // AFTER: var a$b$c = ...; // var // name a$b$c // NODE // Remove the r-value (NODE). parent.removeChild(rvalue); nameNode.addChildToFront(rvalue); Node varNode = IR.var(nameNode); greatGrandparent.replaceChild(grandparent, varNode); } else { // This must be a complex assignment. Preconditions.checkNotNull(ref.getTwin()); // BEFORE: // ... (x.y = 3); // // AFTER: // var x$y; // ... (x$y = 3); Node current = grandparent; Node currentParent = grandparent.getParent(); for (; !currentParent.isScript() && !currentParent.isBlock(); current = currentParent, currentParent = currentParent.getParent()) {} // Create a stub variable declaration right // before the current statement. Node stubVar = IR.var(nameNode.cloneTree()).useSourceInfoIfMissingFrom(nameNode); currentParent.addChildBefore(stubVar, current); parent.replaceChild(ref.node, nameNode); } compiler.reportCodeChange(); }
/** * Inline a function which fulfills the requirements of canInlineReferenceAsStatementBlock into * the call site, replacing the parent expression. */ private Node inlineFunction(Node callNode, Node fnNode, String fnName) { Node parent = callNode.getParent(); Node grandParent = parent.getParent(); // TODO(johnlenz): Consider storing the callSite classification in the // reference object and passing it in here. CallSiteType callSiteType = classifyCallSite(callNode); Preconditions.checkArgument(callSiteType != CallSiteType.UNSUPPORTED); // Store the name for the result. This will be used to // replace "return expr" statements with "resultName = expr" // to replace String resultName = null; boolean needsDefaultReturnResult = true; switch (callSiteType) { case SIMPLE_ASSIGNMENT: resultName = parent.getFirstChild().getString(); break; case VAR_DECL_SIMPLE_ASSIGNMENT: resultName = parent.getString(); break; case SIMPLE_CALL: resultName = null; // "foo()" doesn't need a result. needsDefaultReturnResult = false; break; case EXPRESSION: resultName = getUniqueResultName(); needsDefaultReturnResult = false; // The intermediary result already // has the default value. break; case DECOMPOSABLE_EXPRESSION: throw new IllegalStateException( "Decomposable expressions must decomposed before inlining."); default: throw new IllegalStateException("Unexpected call site type."); } boolean isCallInLoop = NodeUtil.isWithinLoop(callNode); FunctionToBlockMutator mutator = new FunctionToBlockMutator(compiler, this.safeNameIdSupplier); Node newBlock = mutator.mutate( fnName, fnNode, callNode, resultName, needsDefaultReturnResult, isCallInLoop); // TODO(nicksantos): Create a common mutation function that // can replace either a VAR name assignment, assignment expression or // a EXPR_RESULT. Node greatGrandParent = grandParent.getParent(); switch (callSiteType) { case VAR_DECL_SIMPLE_ASSIGNMENT: // Remove the call from the name node. parent.removeChild(parent.getFirstChild()); Preconditions.checkState(parent.getFirstChild() == null); // Add the call, after the VAR. greatGrandParent.addChildAfter(newBlock, grandParent); break; case SIMPLE_ASSIGNMENT: // The assignment is now part of the inline function so // replace it completely. Preconditions.checkState(NodeUtil.isExpressionNode(grandParent)); greatGrandParent.replaceChild(grandParent, newBlock); break; case SIMPLE_CALL: // If nothing is looking at the result just replace the call. Preconditions.checkState(NodeUtil.isExpressionNode(parent)); grandParent.replaceChild(parent, newBlock); break; case EXPRESSION: // TODO(johnlenz): Maybe change this so that movable and decomposable // expressions are handled the same way: The call is moved and // then handled by one the three basic cases, rather than // introducing a new case. Node injectionPoint = ExpressionDecomposer.findInjectionPoint(callNode); Preconditions.checkNotNull(injectionPoint); Node injectionPointParent = injectionPoint.getParent(); Preconditions.checkNotNull(injectionPointParent); Preconditions.checkState(NodeUtil.isStatementBlock(injectionPointParent)); // Declare the intermediate result name. newBlock.addChildrenToFront( NodeUtil.newVarNode(resultName, null).copyInformationFromForTree(callNode)); // Inline the function before the selected injection point (before // the call). injectionPointParent.addChildBefore(newBlock, injectionPoint); // Replace the call site with a reference to the intermediate // result name. parent.replaceChild(callNode, Node.newString(Token.NAME, resultName)); break; default: throw new IllegalStateException("Unexpected call site type."); } return newBlock; }