/** http://www.ecma-international.org/ecma-262/6.0/#sec-abstract-relational-comparison */ private static TernaryValue tryAbstractRelationalComparison( Node left, Node right, boolean useTypes, boolean willNegate) { // First, try to evaluate based on the general type. ValueType leftValueType = NodeUtil.getKnownValueType(left); ValueType rightValueType = NodeUtil.getKnownValueType(right); if (leftValueType != ValueType.UNDETERMINED && rightValueType != ValueType.UNDETERMINED) { if (leftValueType == ValueType.STRING && rightValueType == ValueType.STRING) { String lv = NodeUtil.getStringValue(left); String rv = NodeUtil.getStringValue(right); if (lv != null && rv != null) { // In JS, browsers parse \v differently. So do not compare strings if one contains \v. if (lv.indexOf('\u000B') != -1 || rv.indexOf('\u000B') != -1) { return TernaryValue.UNKNOWN; } else { return TernaryValue.forBoolean(lv.compareTo(rv) < 0); } } else if (left.isTypeOf() && right.isTypeOf() && left.getFirstChild().isName() && right.getFirstChild().isName() && left.getFirstChild().getString().equals(right.getFirstChild().getString())) { // Special case: `typeof a < typeof a` is always false. return TernaryValue.FALSE; } } } // Then, try to evaluate based on the value of the node. Try comparing as numbers. Double lv = NodeUtil.getNumberValue(left, useTypes); Double rv = NodeUtil.getNumberValue(right, useTypes); if (lv == null || rv == null) { // Special case: `x < x` is always false. // // TODO(moz): If we knew the named value wouldn't be NaN, it would be nice to handle // LE and GE. We should use type information if available here. if (!willNegate && left.isName() && right.isName()) { if (left.getString().equals(right.getString())) { return TernaryValue.FALSE; } } return TernaryValue.UNKNOWN; } if (Double.isNaN(lv) || Double.isNaN(rv)) { return TernaryValue.forBoolean(willNegate); } else { return TernaryValue.forBoolean(lv.doubleValue() < rv.doubleValue()); } }
/** http://www.ecma-international.org/ecma-262/6.0/#sec-abstract-equality-comparison */ private static TernaryValue tryAbstractEqualityComparison( Node left, Node right, boolean useTypes) { // Evaluate based on the general type. ValueType leftValueType = NodeUtil.getKnownValueType(left); ValueType rightValueType = NodeUtil.getKnownValueType(right); if (leftValueType != ValueType.UNDETERMINED && rightValueType != ValueType.UNDETERMINED) { // Delegate to strict equality comparison for values of the same type. if (leftValueType == rightValueType) { return tryStrictEqualityComparison(left, right, useTypes); } if ((leftValueType == ValueType.NULL && rightValueType == ValueType.VOID) || (leftValueType == ValueType.VOID && rightValueType == ValueType.NULL)) { return TernaryValue.TRUE; } if ((leftValueType == ValueType.NUMBER && rightValueType == ValueType.STRING) || rightValueType == ValueType.BOOLEAN) { Double rv = NodeUtil.getNumberValue(right, useTypes); return rv == null ? TernaryValue.UNKNOWN : tryAbstractEqualityComparison(left, IR.number(rv), useTypes); } if ((leftValueType == ValueType.STRING && rightValueType == ValueType.NUMBER) || leftValueType == ValueType.BOOLEAN) { Double lv = NodeUtil.getNumberValue(left, useTypes); return lv == null ? TernaryValue.UNKNOWN : tryAbstractEqualityComparison(IR.number(lv), right, useTypes); } if ((leftValueType == ValueType.STRING || leftValueType == ValueType.NUMBER) && rightValueType == ValueType.OBJECT) { return TernaryValue.UNKNOWN; } if (leftValueType == ValueType.OBJECT && (rightValueType == ValueType.STRING || rightValueType == ValueType.NUMBER)) { return TernaryValue.UNKNOWN; } return TernaryValue.FALSE; } // In general, the rest of the cases cannot be folded. return TernaryValue.UNKNOWN; }
/** * Expressions such as [foo() * 10 * 20] generate parse trees where no node has two const children * ((foo() * 10) * 20), so performArithmeticOp() won't fold it -- tryFoldLeftChildOp() will. * Specifically, it folds associative expressions where: - The left child is also an associative * expression of the same time. - The right child is a constant NUMBER constant. - The left * child's right child is a NUMBER constant. */ private Node tryFoldLeftChildOp(Node n, Node left, Node right) { Token opType = n.getType(); Preconditions.checkState( (NodeUtil.isAssociative(opType) && NodeUtil.isCommutative(opType)) || n.isAdd()); Preconditions.checkState(!n.isAdd() || !NodeUtil.mayBeString(n, shouldUseTypes)); // Use getNumberValue to handle constants like "NaN" and "Infinity" // other values are converted to numbers elsewhere. Double rightValObj = NodeUtil.getNumberValue(right, shouldUseTypes); if (rightValObj != null && left.getType() == opType) { Preconditions.checkState(left.getChildCount() == 2); Node ll = left.getFirstChild(); Node lr = ll.getNext(); Node valueToCombine = ll; Node replacement = performArithmeticOp(opType, valueToCombine, right); if (replacement == null) { valueToCombine = lr; replacement = performArithmeticOp(opType, valueToCombine, right); } if (replacement != null) { // Remove the child that has been combined left.removeChild(valueToCombine); // Replace the left op with the remaining child. n.replaceChild(left, left.removeFirstChild()); // New "-Infinity" node need location info explicitly // added. replacement.useSourceInfoIfMissingFromForTree(right); n.replaceChild(right, replacement); reportCodeChange(); } } return n; }
private void tryConvertToNumber(Node n) { switch (n.getType()) { case NUMBER: // Nothing to do return; case AND: case OR: case COMMA: tryConvertToNumber(n.getLastChild()); return; case HOOK: tryConvertToNumber(n.getSecondChild()); tryConvertToNumber(n.getLastChild()); return; case NAME: if (!NodeUtil.isUndefined(n)) { return; } break; } Double result = NodeUtil.getNumberValue(n, shouldUseTypes); if (result == null) { return; } double value = result; Node replacement = NodeUtil.numberNode(value, n); if (replacement.isEquivalentTo(n)) { return; } n.getParent().replaceChild(n, replacement); reportCodeChange(); }
/** Try to fold arithmetic binary operators */ private Node performArithmeticOp(Token opType, Node left, Node right) { // Unlike other operations, ADD operands are not always converted // to Number. if (opType == Token.ADD && (NodeUtil.mayBeString(left, shouldUseTypes) || NodeUtil.mayBeString(right, shouldUseTypes))) { return null; } double result; // TODO(johnlenz): Handle NaN with unknown value. BIT ops convert NaN // to zero so this is a little awkward here. Double lValObj = NodeUtil.getNumberValue(left, shouldUseTypes); if (lValObj == null) { return null; } Double rValObj = NodeUtil.getNumberValue(right, shouldUseTypes); if (rValObj == null) { return null; } double lval = lValObj; double rval = rValObj; switch (opType) { case BITAND: result = NodeUtil.toInt32(lval) & NodeUtil.toInt32(rval); break; case BITOR: result = NodeUtil.toInt32(lval) | NodeUtil.toInt32(rval); break; case BITXOR: result = NodeUtil.toInt32(lval) ^ NodeUtil.toInt32(rval); break; case ADD: result = lval + rval; break; case SUB: result = lval - rval; break; case MUL: result = lval * rval; break; case MOD: if (rval == 0) { return null; } result = lval % rval; break; case DIV: if (rval == 0) { return null; } result = lval / rval; break; default: throw new Error("Unexpected arithmetic operator"); } // TODO(johnlenz): consider removing the result length check. // length of the left and right value plus 1 byte for the operator. if ((String.valueOf(result).length() <= String.valueOf(lval).length() + String.valueOf(rval).length() + 1 // Do not try to fold arithmetic for numbers > 2^53. After that // point, fixed-point math starts to break down and become inaccurate. && Math.abs(result) <= MAX_FOLD_NUMBER) || Double.isNaN(result) || result == Double.POSITIVE_INFINITY || result == Double.NEGATIVE_INFINITY) { return NodeUtil.numberNode(result, null); } return null; }
/** http://www.ecma-international.org/ecma-262/6.0/#sec-strict-equality-comparison */ private static TernaryValue tryStrictEqualityComparison(Node left, Node right, boolean useTypes) { // First, try to evaluate based on the general type. ValueType leftValueType = NodeUtil.getKnownValueType(left); ValueType rightValueType = NodeUtil.getKnownValueType(right); if (leftValueType != ValueType.UNDETERMINED && rightValueType != ValueType.UNDETERMINED) { // Strict equality can only be true for values of the same type. if (leftValueType != rightValueType) { return TernaryValue.FALSE; } switch (leftValueType) { case VOID: case NULL: return TernaryValue.TRUE; case NUMBER: { if (NodeUtil.isNaN(left)) { return TernaryValue.FALSE; } if (NodeUtil.isNaN(right)) { return TernaryValue.FALSE; } Double lv = NodeUtil.getNumberValue(left, useTypes); Double rv = NodeUtil.getNumberValue(right, useTypes); if (lv != null && rv != null) { return TernaryValue.forBoolean(lv.doubleValue() == rv.doubleValue()); } break; } case STRING: { String lv = NodeUtil.getStringValue(left); String rv = NodeUtil.getStringValue(right); if (lv != null && rv != null) { // In JS, browsers parse \v differently. So do not consider strings // equal if one contains \v. if (lv.indexOf('\u000B') != -1 || rv.indexOf('\u000B') != -1) { return TernaryValue.UNKNOWN; } else { return lv.equals(rv) ? TernaryValue.TRUE : TernaryValue.FALSE; } } else if (left.isTypeOf() && right.isTypeOf() && left.getFirstChild().isName() && right.getFirstChild().isName() && left.getFirstChild().getString().equals(right.getFirstChild().getString())) { // Special case, typeof a == typeof a is always true. return TernaryValue.TRUE; } break; } case BOOLEAN: { TernaryValue lv = NodeUtil.getPureBooleanValue(left); TernaryValue rv = NodeUtil.getPureBooleanValue(right); return lv.and(rv).or(lv.not().and(rv.not())); } default: // Symbol and Object cannot be folded in the general case. return TernaryValue.UNKNOWN; } } // Then, try to evaluate based on the value of the node. There's only one special case: // Any strict equality comparison against NaN returns false. if (NodeUtil.isNaN(left) || NodeUtil.isNaN(right)) { return TernaryValue.FALSE; } return TernaryValue.UNKNOWN; }