blob: a2d26f072a10c111eaeeed5177e4755be1ebbd34 [file] [log] [blame]
/*
* 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 throwableType;
public ImplicitUpcastAnalyzer(JProgram program) {
this.throwableType = program.getIndexedType("Throwable");
this.javaScriptObjectType = program.getJavaScriptObject();
}
@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().isNullType()) {
processIfTypesNotEqual(JReferenceType.NULL_TYPE, x.getLhs().getType(), x.getSourceInfo());
} else if (x.getLhs().getType().isNullType()) {
processIfTypesNotEqual(JReferenceType.NULL_TYPE, 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()
&& !x.getType().isPrimitiveType()) {
// if it is declared without an initial value, it defaults to null
processIfTypesNotEqual(JReferenceType.NULL_TYPE, x.getType(), x.getSourceInfo());
}
}
@Override
public void endVisit(JMethod x, Context ctx) {
// check for upcast in return type as compared to an overridden method
for (JMethod overridden : x.getOverriddenMethods()) {
processIfTypesNotEqual(x.getType(), overridden.getType(), x.getSourceInfo());
}
if (x.getBody() != null && x.getBody().isJsniMethodBody()) {
/*
* 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.getInitializers() != null) {
for (JExpression init : x.getInitializers()) {
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().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) {
// Ignore nullability when determining type inequality.
fromType = fromType.getUnderlyingType();
destType = destType.getUnderlyingType();
if (fromType != destType) {
processImplicitUpcast(fromType, destType, info);
}
}
}