/** Try to fold an ADD node with constant operands */ private Node tryFoldAddConstantString(Node n, Node left, Node right) { if (left.isString() || right.isString() || left.isArrayLit() || right.isArrayLit()) { // Add strings. String leftString = NodeUtil.getStringValue(left); String rightString = NodeUtil.getStringValue(right); if (leftString != null && rightString != null) { Node newStringNode = IR.string(leftString + rightString); n.getParent().replaceChild(n, newStringNode); reportCodeChange(); return newStringNode; } } return n; }
/** * Expressions such as [foo() + 'a' + 'b'] generate parse trees where no node has two const * children ((foo() + 'a') + 'b'), so tryFoldAdd() won't fold it -- tryFoldLeftChildAdd() will * (for Strings). Specifically, it folds Add expressions where: - The left child is also and add * expression - The right child is a constant value - The left child's right child is a STRING * constant. */ private Node tryFoldChildAddString(Node n, Node left, Node right) { if (NodeUtil.isLiteralValue(right, false) && left.isAdd()) { Node ll = left.getFirstChild(); Node lr = ll.getNext(); // Left's right child MUST be a string. We would not want to fold // foo() + 2 + 'a' because we don't know what foo() will return, and // therefore we don't know if left is a string concat, or a numeric add. if (lr.isString()) { String leftString = NodeUtil.getStringValue(lr); String rightString = NodeUtil.getStringValue(right); if (leftString != null && rightString != null) { left.removeChild(ll); String result = leftString + rightString; n.replaceChild(left, ll); n.replaceChild(right, IR.string(result)); reportCodeChange(); return n; } } } if (NodeUtil.isLiteralValue(left, false) && right.isAdd()) { Node rl = right.getFirstChild(); Node rr = right.getLastChild(); // Left's right child MUST be a string. We would not want to fold // foo() + 2 + 'a' because we don't know what foo() will return, and // therefore we don't know if left is a string concat, or a numeric add. if (rl.isString()) { String leftString = NodeUtil.getStringValue(left); String rightString = NodeUtil.getStringValue(rl); if (leftString != null && rightString != null) { right.removeChild(rr); String result = leftString + rightString; n.replaceChild(right, rr); n.replaceChild(left, IR.string(result)); reportCodeChange(); return n; } } } return n; }
/** 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()); } }
private Node tryFoldInForcedStringContext(Node n) { // For now, we only know how to fold ctors. Preconditions.checkArgument(n.isNew()); Node objectType = n.getFirstChild(); if (!objectType.isName()) { return n; } if (objectType.getString().equals("String")) { Node value = objectType.getNext(); String stringValue = null; if (value == null) { stringValue = ""; } else { if (!NodeUtil.isImmutableValue(value)) { return n; } stringValue = NodeUtil.getStringValue(value); } if (stringValue == null) { return n; } Node parent = n.getParent(); Node newString = IR.string(stringValue); parent.replaceChild(n, newString); newString.useSourceInfoIfMissingFrom(parent); reportCodeChange(); return newString; } return n; }
/** 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; }