@Override public void visit(Node n, Node parent) { Node value = n.getFirstChild(); if (value.isNumber() && value.getDouble() == 0) { super.visit(n, parent); } }
private Node tryReduceVoid(Node n) { Node child = n.getFirstChild(); if ((!child.isNumber() || child.getDouble() != 0.0) && !mayHaveSideEffects(n)) { n.replaceChild(child, IR.number(0)); reportCodeChange(); } return n; }
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; }
private void validateNumber(Node n) { validateNodeType(Token.NUMBER, n); validateChildCount(n); try { // Validate that getDouble doesn't throw n.getDouble(); } catch (UnsupportedOperationException e) { violation("Invalid NUMBER node.", n); } }
private Node tryFoldStringArrayAccess(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; } Preconditions.checkState(left.isString()); String value = left.getString(); if (intIndex >= value.length()) { report(INDEX_OUT_OF_BOUNDS_ERROR, right); return n; } char c = 0; // Note: For now skip the strings with unicode // characters as I don't understand the differences // between Java and JavaScript. for (int i = 0; i <= intIndex; i++) { c = value.charAt(i); if (c < 32 || c > 127) { return n; } } Node elem = IR.string(Character.toString(c)); // Replace the entire GETELEM with the value n.getParent().replaceChild(n, elem); reportCodeChange(); return elem; }
/** * Remove all references to a parameter if possible otherwise simplify the side-effect free * parameters. */ private void tryRemoveArgFromCallSites(Node function, int argIndex, boolean canModifyAllSites) { Definition definition = getFunctionDefinition(function); for (UseSite site : defFinder.getUseSites(definition)) { if (isModifiableCallSite(site)) { Node arg = getArgumentForCallOrNewOrDotCall(site, argIndex); if (arg != null) { Node argParent = arg.getParent(); // Even if we can't change the signature in general we can always // remove an unused value off the end of the parameter list. if (canModifyAllSites || (arg.getNext() == null && !NodeUtil.mayHaveSideEffects(arg, compiler))) { toRemove.add(arg); } else { // replace the node in the arg with 0 if (!NodeUtil.mayHaveSideEffects(arg, compiler) && (arg.getType() != Token.NUMBER || arg.getDouble() != 0)) { toReplaceWithZero.add(arg); } } } } } }
/** * Tries to optimize all the arguments array access in this scope by assigning a name to each * element. * * @param scope scope of the function * @return true if any modification has been done to the AST */ private boolean tryReplaceArguments(Scope scope) { Node parametersList = scope.getRootNode().getSecondChild(); Preconditions.checkState(parametersList.isParamList()); // Keep track of rather this function modified the AST and needs to be // reported back to the compiler later. boolean changed = false; // Number of parameter that can be accessed without using the arguments // array. int numNamedParameter = parametersList.getChildCount(); // We want to guess what the highest index that has been access from the // arguments array. We will guess that it does not use anything index higher // than the named parameter list first until we see other wise. int highestIndex = numNamedParameter - 1; // Iterate through all the references to arguments array in the function to // determine the real highestIndex. for (Node ref : currentArgumentsAccess) { Node getElem = ref.getParent(); // Bail on anything but argument[c] access where c is a constant. // TODO(user): We might not need to bail out all the time, there might // be more cases that we can cover. if (!getElem.isGetElem() || ref != getElem.getFirstChild()) { return false; } Node index = ref.getNext(); // We have something like arguments[x] where x is not a constant. That // means at least one of the access is not known. if (!index.isNumber() || index.getDouble() < 0) { // TODO(user): Its possible not to give up just yet. The type // inference did a 'semi value propagation'. If we know that string // is never a subclass of the type of the index. We'd know that // it is never 'callee'. return false; // Give up. } Node getElemParent = getElem.getParent(); // When we have argument[0](), replacing it with a() is semantically // different if argument[0] is a function call that refers to 'this' if (getElemParent.isCall() && getElemParent.getFirstChild() == getElem) { // TODO(user): We can consider using .call() if aliasing that // argument allows shorter alias for other arguments. return false; } // Replace the highest index if we see an access that has a higher index // than all the one we saw before. int value = (int) index.getDouble(); if (value > highestIndex) { highestIndex = value; } } // Number of extra arguments we need. // For example: function() { arguments[3] } access index 3 so // it will need 4 extra named arguments to changed into: // function(a,b,c,d) { d }. int numExtraArgs = highestIndex - numNamedParameter + 1; // Temporary holds the new names as string for quick access later. String[] argNames = new String[numExtraArgs]; // Insert the formal parameter to the method's signature. // Example: function() --> function(r0, r1, r2) for (int i = 0; i < numExtraArgs; i++) { String name = getNewName(); argNames[i] = name; parametersList.addChildToBack(IR.name(name).useSourceInfoIfMissingFrom(parametersList)); changed = true; } // This loop performs the replacement of arguments[x] -> a if x is known. for (Node ref : currentArgumentsAccess) { Node index = ref.getNext(); // Skip if it is unknown. if (!index.isNumber()) { continue; } int value = (int) index.getDouble(); // Unnamed parameter. if (value >= numNamedParameter) { ref.getGrandparent() .replaceChild(ref.getParent(), IR.name(argNames[value - numNamedParameter])); } else { // Here, for no apparent reason, the user is accessing a named parameter // with arguments[idx]. We can replace it with the actual name for them. Node name = parametersList.getFirstChild(); // This is a linear search for the actual name from the signature. // It is not necessary to make this fast because chances are the user // will not deliberately write code like this. for (int i = 0; i < value; i++) { name = name.getNext(); } ref.getGrandparent().replaceChild(ref.getParent(), IR.name(name.getString())); } changed = true; } return changed; }
/** Try to fold shift operations */ private Node tryFoldShift(Node n, Node left, Node right) { if (left.isNumber() && right.isNumber()) { double result; double lval = left.getDouble(); double rval = right.getDouble(); // check ranges. We do not do anything that would clip the double to // a 32-bit range, since the user likely does not intend that. if (lval < Integer.MIN_VALUE) { report(BITWISE_OPERAND_OUT_OF_RANGE, left); return n; } // only the lower 5 bits are used when shifting, so don't do anything // if the shift amount is outside [0,32) if (!(rval >= 0 && rval < 32)) { report(SHIFT_AMOUNT_OUT_OF_BOUNDS, n); return n; } int rvalInt = (int) rval; if (rvalInt != rval) { report(FRACTIONAL_BITWISE_OPERAND, right); return n; } switch (n.getType()) { case LSH: case RSH: // Convert the numbers to ints if (lval > Integer.MAX_VALUE) { report(BITWISE_OPERAND_OUT_OF_RANGE, left); return n; } int lvalInt = (int) lval; if (lvalInt != lval) { report(FRACTIONAL_BITWISE_OPERAND, left); return n; } if (n.getType() == Token.LSH) { result = lvalInt << rvalInt; } else { result = lvalInt >> rvalInt; } break; case URSH: // JavaScript handles zero shifts on signed numbers differently than // Java as an Java int can not represent the unsigned 32-bit number // where JavaScript can so use a long here. long maxUint32 = 0xffffffffL; if (lval > maxUint32) { report(BITWISE_OPERAND_OUT_OF_RANGE, left); return n; } long lvalLong = (long) lval; if (lvalLong != lval) { report(FRACTIONAL_BITWISE_OPERAND, left); return n; } result = (lvalLong & maxUint32) >>> rvalInt; break; default: throw new AssertionError("Unknown shift operator: " + n.getType()); } Node newNumber = IR.number(result); n.getParent().replaceChild(n, newNumber); reportCodeChange(); return newNumber; } return n; }
private Node tryFoldUnaryOperator(Node n) { Preconditions.checkState(n.hasOneChild(), n); Node left = n.getFirstChild(); Node parent = n.getParent(); if (left == null) { return n; } TernaryValue leftVal = NodeUtil.getPureBooleanValue(left); if (leftVal == TernaryValue.UNKNOWN) { return n; } switch (n.getType()) { case NOT: // Don't fold !0 and !1 back to false. if (late && left.isNumber()) { double numValue = left.getDouble(); if (numValue == 0 || numValue == 1) { return n; } } Node replacementNode = NodeUtil.booleanNode(!leftVal.toBoolean(true)); parent.replaceChild(n, replacementNode); reportCodeChange(); return replacementNode; case POS: if (NodeUtil.isNumericResult(left)) { // POS does nothing to numeric values. parent.replaceChild(n, left.detachFromParent()); reportCodeChange(); return left; } return n; case NEG: if (left.isName()) { if (left.getString().equals("Infinity")) { // "-Infinity" is valid and a literal, don't modify it. return n; } else if (left.getString().equals("NaN")) { // "-NaN" is "NaN". n.removeChild(left); parent.replaceChild(n, left); reportCodeChange(); return left; } } if (left.isNumber()) { double negNum = -left.getDouble(); Node negNumNode = IR.number(negNum); parent.replaceChild(n, negNumNode); reportCodeChange(); return negNumNode; } else { // left is not a number node, so do not replace, but warn the // user because they can't be doing anything good report(NEGATING_A_NON_NUMBER_ERROR, left); return n; } case BITNOT: try { double val = left.getDouble(); if (val >= Integer.MIN_VALUE && val <= Integer.MAX_VALUE) { int intVal = (int) val; if (intVal == val) { Node notIntValNode = IR.number(~intVal); parent.replaceChild(n, notIntValNode); reportCodeChange(); return notIntValNode; } else { report(FRACTIONAL_BITWISE_OPERAND, left); return n; } } else { report(BITWISE_OPERAND_OUT_OF_RANGE, left); return n; } } catch (UnsupportedOperationException ex) { // left is not a number node, so do not replace, but warn the // user because they can't be doing anything good report(NEGATING_A_NON_NUMBER_ERROR, left); return n; } default: return n; } }
/** * Returns whether two nodes are equivalent, taking into account the template parameters that were * provided to this matcher. If the template comparison node is a parameter node, then only the * types of the node must match. Otherwise, the node must be equal and the child nodes must be * equivalent according to the same function. This differs from the built in Node equivalence * function with the special comparison. */ private boolean matchesNode(Node template, Node ast) { if (isTemplateParameterNode(template)) { int paramIndex = (int) (template.getDouble()); Node previousMatch = paramNodeMatches.get(paramIndex); if (previousMatch != null) { // If this named node has already been matched against, make sure all // subsequent usages of the same named node are equivalent. return ast.isEquivalentTo(previousMatch); } // Only the types need to match for the template parameters, which allows // the template function to express arbitrary expressions. JSType templateType = template.getJSType(); Preconditions.checkNotNull(templateType, "null template parameter type."); // TODO(johnlenz): We shouldn't spend time checking template whose // types whose definitions aren't included (NoResolvedType). Alternately // we should treat them as "unknown" and perform loose matches. if (templateType.isNoResolvedType()) { return false; } MatchResult matchResult = typeMatchingStrategy.match(templateType, ast.getJSType()); isLooseMatch = matchResult.isLooseMatch(); boolean isMatch = matchResult.isMatch(); if (isMatch && previousMatch == null) { paramNodeMatches.set(paramIndex, ast); } return isMatch; } else if (isTemplateLocalNameNode(template)) { // If this template name node was already matched against, then make sure // all subsequent usages of the same template name node are equivalent in // the matched code. // For example, this code will handle the case: // function template() { // var a = 'str'; // fn(a); // } // // will only match test code: // var b = 'str'; // fn(b); // // but it will not match: // var b = 'str'; // fn('str'); int paramIndex = (int) (template.getDouble()); boolean previouslyMatched = this.localVarMatches.get(paramIndex) != null; if (previouslyMatched) { // If this named node has already been matched against, make sure all // subsequent usages of the same named node are equivalent. return ast.getString().equals(this.localVarMatches.get(paramIndex)); } else { this.localVarMatches.set(paramIndex, ast.getString()); } } // Template and AST shape has already been checked, but continue look for // other template variables (parameters and locals) that must be checked. Node templateChild = template.getFirstChild(); Node astChild = ast.getFirstChild(); while (templateChild != null) { if (!matchesNode(templateChild, astChild)) { return false; } templateChild = templateChild.getNext(); astChild = astChild.getNext(); } return true; }