@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 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; }
/** * 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; } }
/** * 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; }