| /* |
| * Copyright 2008 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.dev.jjs.impl; |
| |
| import com.google.gwt.dev.jjs.ast.Context; |
| import com.google.gwt.dev.jjs.ast.JBinaryOperation; |
| import com.google.gwt.dev.jjs.ast.JBinaryOperator; |
| import com.google.gwt.dev.jjs.ast.JClassType; |
| import com.google.gwt.dev.jjs.ast.JExpression; |
| import com.google.gwt.dev.jjs.ast.JMethod; |
| import com.google.gwt.dev.jjs.ast.JMethodCall; |
| import com.google.gwt.dev.jjs.ast.JModVisitor; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| import com.google.gwt.dev.jjs.ast.JReferenceType; |
| import com.google.gwt.dev.jjs.ast.JType; |
| |
| /** |
| * <p> |
| * Rewrite Java <code>==</code> so that it will execute correctly in JavaScript. |
| * After this pass, Java's <code>==</code> is considered equivalent to |
| * JavaScript's <code>===</code>. |
| * </p> |
| * <p> |
| * Whenever possible, a Java <code>==</code> is replaced by a JavaScript |
| * <code>==</code>. This is shorter than <code>===</code>, and it avoids any |
| * complication due to GWT treating both <code>null</code> and |
| * <code>undefined</code> as a valid translation of a Java <code>null</code>. |
| * </p> |
| * <p> |
| * However, whenever something that may be a String is compared to something |
| * that may not be a <code>String</code>, use <code>===</code>. A Java object |
| * compared to a string should always yield false, but that's not true when |
| * comparing in JavaScript using <code>==</code>. The cases where |
| * <code>===</code> must be used are: |
| * </p> |
| * <ul> |
| * <li>One or both sides have unknown <code>String</code> status.</li> |
| * <li>One side is definitely <code>String</code> and one side is definitely ! |
| * <code>String</code>. <br/> |
| * TODO: This case could be optimized as |
| * <code>(a == null) & (b == null)</code>.</li> |
| * </ul> |
| * <p> |
| * Since <code>null !== undefined</code>, it is also necessary to normalize |
| * <code>null</code> vs. <code>undefined</code> if it's possible for one side to |
| * be <code>null</code> and the other to be <code>undefined</code>. |
| * </p> |
| */ |
| public class EqualityNormalizer { |
| |
| /** |
| * Breaks apart certain complex assignments. |
| */ |
| private class BreakupAssignOpsVisitor extends JModVisitor { |
| |
| @Override |
| public void endVisit(JBinaryOperation x, Context ctx) { |
| JBinaryOperator op = x.getOp(); |
| if (op != JBinaryOperator.EQ && op != JBinaryOperator.NEQ) { |
| return; |
| } |
| JExpression lhs = x.getLhs(); |
| JExpression rhs = x.getRhs(); |
| JType lhsType = lhs.getType(); |
| JType rhsType = rhs.getType(); |
| if (!(lhsType instanceof JReferenceType)) { |
| assert !(rhsType instanceof JReferenceType); |
| return; |
| } |
| |
| StringStatus lhsStatus = getStringStatus((JReferenceType) lhsType); |
| StringStatus rhsStatus = getStringStatus((JReferenceType) rhsType); |
| int strat = COMPARISON_STRAT[lhsStatus.getIndex()][rhsStatus.getIndex()]; |
| |
| switch (strat) { |
| case STRAT_TRIPLE: { |
| if (canBeNull(lhs) && canBeNull(rhs)) { |
| /* |
| * If it's possible for one side to be null and the other side |
| * undefined, then mask both sides. |
| */ |
| lhs = maskUndefined(lhs); |
| rhs = maskUndefined(rhs); |
| } |
| |
| JBinaryOperation binOp = |
| new JBinaryOperation(x.getSourceInfo(), x.getType(), x.getOp(), lhs, rhs); |
| ctx.replaceMe(binOp); |
| break; |
| } |
| |
| case STRAT_DOUBLE: { |
| boolean lhsNullLit = lhs == program.getLiteralNull(); |
| boolean rhsNullLit = rhs == program.getLiteralNull(); |
| if ((lhsNullLit && rhsStatus == StringStatus.NOTSTRING) |
| || (rhsNullLit && lhsStatus == StringStatus.NOTSTRING)) { |
| /* |
| * If either side is a null literal and the other is non-String, |
| * replace with a null-check. |
| */ |
| String methodName; |
| if (op == JBinaryOperator.EQ) { |
| methodName = "Cast.isNull"; |
| } else { |
| methodName = "Cast.isNotNull"; |
| } |
| JMethod isNullMethod = program.getIndexedMethod(methodName); |
| JMethodCall call = new JMethodCall(x.getSourceInfo(), null, isNullMethod); |
| call.addArg(lhsNullLit ? rhs : lhs); |
| ctx.replaceMe(call); |
| } else { |
| // Replace with a call to Cast.jsEquals, which does a == internally. |
| String methodName; |
| if (op == JBinaryOperator.EQ) { |
| methodName = "Cast.jsEquals"; |
| } else { |
| methodName = "Cast.jsNotEquals"; |
| } |
| JMethod eqMethod = program.getIndexedMethod(methodName); |
| JMethodCall call = new JMethodCall(x.getSourceInfo(), null, eqMethod); |
| call.addArgs(lhs, rhs); |
| ctx.replaceMe(call); |
| } |
| break; |
| } |
| } |
| } |
| |
| private StringStatus getStringStatus(JReferenceType type) { |
| JClassType stringType = program.getTypeJavaLangString(); |
| if (type == program.getTypeNull()) { |
| return StringStatus.NULL; |
| } else if (program.typeOracle.canTriviallyCast(type, stringType)) { |
| return StringStatus.STRING; |
| } else if (program.typeOracle.canTheoreticallyCast(type, stringType)) { |
| return StringStatus.UNKNOWN; |
| } else { |
| return StringStatus.NOTSTRING; |
| } |
| } |
| |
| private JExpression maskUndefined(JExpression lhs) { |
| assert ((JReferenceType) lhs.getType()).canBeNull(); |
| |
| JMethod maskMethod = program.getIndexedMethod("Cast.maskUndefined"); |
| JMethodCall lhsCall = new JMethodCall(lhs.getSourceInfo(), null, maskMethod, lhs.getType()); |
| lhsCall.addArg(lhs); |
| return lhsCall; |
| } |
| } |
| |
| /** |
| * Represents what we know about an operand type in terms of its type and |
| * <code>null</code> status. |
| */ |
| private enum StringStatus { |
| NOTSTRING(2), NULL(3), STRING(1), UNKNOWN(0); |
| |
| private int index; |
| |
| StringStatus(int index) { |
| this.index = index; |
| } |
| |
| public int getIndex() { |
| return index; |
| } |
| } |
| |
| /** |
| * A map of the combinations where each comparison strategy should be used. |
| */ |
| private static int[][] COMPARISON_STRAT = { |
| // ..U..S.!S..N |
| {1, 1, 1, 0,}, // UNKNOWN |
| {1, 0, 1, 0,}, // STRING |
| {1, 1, 0, 0,}, // NOTSTRING |
| {0, 0, 0, 0,}, // NULL |
| }; |
| |
| /** |
| * The comparison strategy of using ==. |
| */ |
| private static final int STRAT_DOUBLE = 0; |
| |
| /** |
| * The comparison strategy of using ===. |
| */ |
| private static final int STRAT_TRIPLE = 1; |
| |
| public static void exec(JProgram program) { |
| new EqualityNormalizer(program).execImpl(); |
| } |
| |
| private static boolean canBeNull(JExpression x) { |
| return ((JReferenceType) x.getType()).canBeNull(); |
| } |
| |
| private final JProgram program; |
| |
| private EqualityNormalizer(JProgram program) { |
| this.program = program; |
| } |
| |
| private void execImpl() { |
| BreakupAssignOpsVisitor breaker = new BreakupAssignOpsVisitor(); |
| breaker.accept(program); |
| } |
| |
| } |