All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.dev.jjs.impl.EqualityNormalizer Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * 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.JProgram.DispatchType;
import com.google.gwt.dev.jjs.ast.JReferenceType;
import com.google.gwt.dev.jjs.ast.JType;

import java.util.Map;

/**
 * 

* Rewrite Java == so that it will execute correctly in JavaScript. * After this pass, Java's == is considered equivalent to * JavaScript's ===. *

*

* Whenever possible, a Java == is replaced by a JavaScript * ==. This is shorter than ===, and it avoids any * complication due to GWT treating both null and * undefined as a valid translation of a Java null. *

*

* However, whenever something that may be an unboxed type is compared to something * that may not be a unboxed type, use ===. A Java object * compared to a string should always yield false, but that's not true when * comparing in JavaScript using ==. The cases where * === must be used are: *

*
    *
  • One or both sides have unknown unboxed type status.
  • *
  • One side is definitely an unboxed type and one side is definitely ! * unboxed type.
    * TODO: This case could be optimized as * (a == null) & (b == null).
  • *
*

* Since null !== undefined, it is also necessary to normalize * null vs. undefined if it's possible for one side to * be null and the other to be undefined. *

*

An "unboxed type" is a String, Boolean, or Double that is represented as a naked raw * JS type. */ 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; } UnboxedTypeStatus lhsStatus = getUnboxedTypeStatus((JReferenceType) lhsType); UnboxedTypeStatus rhsStatus = getUnboxedTypeStatus((JReferenceType) rhsType); int comparisonStrategy = COMPARISON_TABLE[lhsStatus.getIndex()][rhsStatus.getIndex()]; switch (comparisonStrategy) { case T: { 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 D: { boolean lhsNullLit = lhs == program.getLiteralNull(); boolean rhsNullLit = rhs == program.getLiteralNull(); if ((lhsNullLit && rhsStatus == UnboxedTypeStatus.NOT_UNBOXEDTYPE) || (rhsNullLit && lhsStatus == UnboxedTypeStatus.NOT_UNBOXEDTYPE)) { /* * 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 UnboxedTypeStatus getUnboxedTypeStatus(JReferenceType type) { if (type.isNullType()) { return UnboxedTypeStatus.NULL; } else { for (Map.Entry nativeDispatchType : program.getRepresentedAsNativeTypesDispatchMap().entrySet()) { if (type.getUnderlyingType() == nativeDispatchType.getKey()) { switch (nativeDispatchType.getValue()) { case DOUBLE: return UnboxedTypeStatus.DOUBLE; case BOOLEAN: return UnboxedTypeStatus.BOOLEAN; case STRING: return UnboxedTypeStatus.STRING; default: throw new AssertionError("Shouldn't happen"); } } else if (!program.typeOracle.castFailsTrivially(type, nativeDispatchType.getKey())) { return UnboxedTypeStatus.UNKNOWN; } } return UnboxedTypeStatus.NOT_UNBOXEDTYPE; } } private JExpression maskUndefined(JExpression lhs) { assert lhs.getType().canBeNull(); JMethod maskMethod = program.getIndexedMethod("Cast.maskUndefined"); JMethodCall lhsCall = new JMethodCall(lhs.getSourceInfo(), null, maskMethod, lhs); lhsCall.overrideReturnType(lhs.getType()); return lhsCall; } } /** * Represents what we know about an operand type in terms of its type and * null status. */ private enum UnboxedTypeStatus { NOT_UNBOXEDTYPE(4), NULL(5), DOUBLE(3), BOOLEAN(2) ,STRING(1), UNKNOWN(0); private int index; UnboxedTypeStatus(int index) { this.index = index; } public int getIndex() { return index; } } /** * The comparison strategy of using ==. * Mnemonic: D = double eq */ private static final int D = 0; /** * The comparison strategy of using ===. * Mnemonic: T = triple eq */ private static final int T = 1; /** * A map of the combinations where each comparison strategy should be used. */ private static int[][] COMPARISON_TABLE = { // any type compared to unknown uses triple eq {T, T, T, T, T, D,}, // UNKNOWN // string type uses D only against String and null {T, D, T, T, T, D,}, // STRING // double type uses D only against Double and null {T, T, D, T, T, D,}, // DOUBLE // boolean type uses D only against Boolean and null {T, T, T, D, T, D,}, // BOOLEAN // non-unboxed type uses D only against other non-unboxed types and null {T, T, T, T, D, D,}, // NOT_UNBOXEDTYPE // null vs null is safe everywhere {D, D, D, D, D, D,}, // NULL }; public static void exec(JProgram program) { new EqualityNormalizer(program).execImpl(); } private static boolean canBeNull(JExpression x) { return x.getType().canBeNull(); } private final JProgram program; private EqualityNormalizer(JProgram program) { this.program = program; } private void execImpl() { BreakupAssignOpsVisitor breaker = new BreakupAssignOpsVisitor(); breaker.accept(program); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy