/* * Copyright 2010 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.SourceInfo; 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.JConditional; import com.google.gwt.dev.jjs.ast.JDeclarationStatement; import com.google.gwt.dev.jjs.ast.JExpression; import com.google.gwt.dev.jjs.ast.JField; import com.google.gwt.dev.jjs.ast.JMethod; import com.google.gwt.dev.jjs.ast.JMethodCall; import com.google.gwt.dev.jjs.ast.JNewArray; import com.google.gwt.dev.jjs.ast.JParameter; import com.google.gwt.dev.jjs.ast.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JReturnStatement; import com.google.gwt.dev.jjs.ast.JThrowStatement; import com.google.gwt.dev.jjs.ast.JType; import com.google.gwt.dev.jjs.ast.JVisitor; import com.google.gwt.dev.jjs.ast.js.JsniMethodRef; import java.util.List; /** * This class will identify instances of an implicit upcast between * non-primitive types, and call the overridable processImplicitUpcast method. * * TODO(jbrosenberg): Consider extending to handle implicit upcasts between * primitive types. This is not as straightforward as for reference types, * because primitives can be boxed and unboxed implicitly as well. */ public class ImplicitUpcastAnalyzer extends JVisitor { protected JMethod currentMethod; private final JType javaScriptObjectType; private final JType nullType; private final JType throwableType; public ImplicitUpcastAnalyzer(JProgram program) { this.throwableType = program.getIndexedType("Throwable"); this.javaScriptObjectType = program.getJavaScriptObject(); this.nullType = program.getTypeNull(); } @Override public void endVisit(JBinaryOperation x, Context ctx) { if (x.isAssignment()) { processIfTypesNotEqual(x.getRhs().getType(), x.getLhs().getType(), x.getSourceInfo()); } else if (x.getRhs().getType() == nullType) { processIfTypesNotEqual(nullType, x.getLhs().getType(), x.getSourceInfo()); } else if (x.getLhs().getType() == nullType) { processIfTypesNotEqual(nullType, x.getRhs().getType(), x.getSourceInfo()); } else if (x.getOp() == JBinaryOperator.CONCAT || x.getOp() == JBinaryOperator.EQ || x.getOp() == JBinaryOperator.NEQ) { /* * Since we are not attempting to handle detection of upcasts between * primitive types, we limit handling here to CONCAT, EQ and NEQ. Need to * do both directions. */ processIfTypesNotEqual(x.getLhs().getType(), x.getRhs().getType(), x.getSourceInfo()); processIfTypesNotEqual(x.getRhs().getType(), x.getLhs().getType(), x.getSourceInfo()); } } @Override public void endVisit(JConditional x, Context ctx) { processIfTypesNotEqual(x.getThenExpr().getType(), x.getType(), x.getSourceInfo()); processIfTypesNotEqual(x.getElseExpr().getType(), x.getType(), x.getSourceInfo()); } @Override public void endVisit(JDeclarationStatement x, Context ctx) { if (x.getInitializer() != null) { processIfTypesNotEqual(x.getInitializer().getType(), x.getVariableRef().getType(), x .getSourceInfo()); } } @Override public void endVisit(JField x, Context ctx) { if (x.getInitializer() == null && !x.isFinal()) { if (!(x.getType() instanceof JPrimitiveType)) { // if it is declared without an initial value, it defaults to null processIfTypesNotEqual(nullType, x.getType(), x.getSourceInfo()); } } } @Override public void endVisit(JMethod x, Context ctx) { // check for upcast in return type as compared to an overridden method List<JMethod> overrides = x.getOverrides(); if (overrides != null && overrides.size() > 0) { // only check the first one, since other ones will be checked when those // overridden methods are visited, don't want to do redundant work processIfTypesNotEqual(x.getType(), overrides.get(0).getType(), x.getSourceInfo()); } if (x.getBody() != null && x.getBody().isNative()) { /* * Check if this method has a native (jsni) method body, in which case all * arguments passed in are implicitly cast to JavaScriptObject */ List<JParameter> params = x.getParams(); for (int i = 0; i < params.size(); i++) { processIfTypesNotEqual(params.get(i).getType(), javaScriptObjectType, x.getSourceInfo()); } /* * Check if this method has a non-void return type, in which case it will * be implicitly cast from JavaScriptObject */ if (x.getType() != JPrimitiveType.VOID) { processIfTypesNotEqual(javaScriptObjectType, x.getType(), x.getSourceInfo()); } } } @Override public void endVisit(JMethodCall x, Context ctx) { // check for upcast in argument passing List<JExpression> args = x.getArgs(); List<JParameter> params = x.getTarget().getParams(); for (int i = 0; i < args.size(); i++) { // make sure the param wasn't pruned if (i < params.size()) { processIfTypesNotEqual(args.get(i).getType(), params.get(i).getType(), x.getSourceInfo()); } } } @Override public void endVisit(JNewArray x, Context ctx) { JType elementType = x.getArrayType().getElementType(); if (x.initializers != null) { for (JExpression init : x.initializers) { processIfTypesNotEqual(init.getType(), elementType, x.getSourceInfo()); } } } @Override public void endVisit(JReturnStatement x, Context ctx) { if (x.getExpr() != null) { // check against the current method return type processIfTypesNotEqual(x.getExpr().getType(), currentMethod.getType(), x.getSourceInfo()); } } @Override public void endVisit(JsniMethodRef x, Context ctx) { // the return type of this method ref will be cast to JavaScriptObject if (x.getTarget().getType() != JPrimitiveType.VOID) { processIfTypesNotEqual(x.getTarget().getType(), javaScriptObjectType, x.getSourceInfo()); } // check referenced method's params, which are passed as JavaScriptObjects List<JParameter> params = x.getTarget().getParams(); for (int i = 0; i < params.size(); i++) { processIfTypesNotEqual(javaScriptObjectType, params.get(i).getType(), x.getSourceInfo()); } } @Override public void endVisit(JThrowStatement x, Context ctx) { // all things thrown are upcast to a Throwable JType type = x.getExpr().getType(); if (type instanceof JReferenceType) { type = ((JReferenceType) type).getUnderlyingType(); } processIfTypesNotEqual(type, throwableType, x.getSourceInfo()); } @Override public boolean visit(JMethod x, Context ctx) { // save this, so can use it later for checking JReturnStatement currentMethod = x; return true; } /** * An overriding method will be called for each detected implicit upcast. * * @param fromType * @param destType */ protected void processImplicitUpcast(JType fromType, JType destType, SourceInfo info) { // override } private void processIfTypesNotEqual(JType fromType, JType destType, SourceInfo info) { if (fromType != destType) { processImplicitUpcast(fromType, destType, info); } } }