blob: 2b173992b261fdb174bab49e3064021904c5f9c7 [file] [log] [blame]
/*
* Copyright 2007 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.JArrayLength;
import com.google.gwt.dev.jjs.ast.JArrayRef;
import com.google.gwt.dev.jjs.ast.JBinaryOperation;
import com.google.gwt.dev.jjs.ast.JBinaryOperator;
import com.google.gwt.dev.jjs.ast.JCastOperation;
import com.google.gwt.dev.jjs.ast.JConditional;
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JFieldRef;
import com.google.gwt.dev.jjs.ast.JIntLiteral;
import com.google.gwt.dev.jjs.ast.JLocalRef;
import com.google.gwt.dev.jjs.ast.JMethodCall;
import com.google.gwt.dev.jjs.ast.JNewArray;
import com.google.gwt.dev.jjs.ast.JNewInstance;
import com.google.gwt.dev.jjs.ast.JParameterRef;
import com.google.gwt.dev.jjs.ast.JPostfixOperation;
import com.google.gwt.dev.jjs.ast.JPrefixOperation;
import com.google.gwt.dev.jjs.ast.JReferenceType;
import com.google.gwt.dev.jjs.ast.JThisRef;
import com.google.gwt.dev.jjs.ast.JVisitor;
/**
* Analyzes an expression and make a number of static analysis flags available
* based on the information available solely through the expression.
*
* TODO: make this even smarter when we have real null analysis.
*/
public class ExpressionAnalyzer extends JVisitor {
private boolean accessesField;
private boolean accessesFieldNonFinal;
private boolean accessesLocal;
private boolean accessesParameter;
private boolean assignmentToField;
private boolean assignmentToLocal;
private boolean assignmentToParameter;
private boolean canThrowException;
private boolean createsObject;
private int inConditional;
/**
* Does this expression read or write fields within the scope of the
* expression?
*/
public boolean accessesField() {
return accessesField;
}
/**
* Does this expression read or write non-final fields within the scope of the
* expression?
*/
public boolean accessesFieldNonFinal() {
return accessesFieldNonFinal;
}
/**
* Does this expression read or write locals within the scope of the
* expression?
*/
public boolean accessesLocal() {
return accessesLocal;
}
/**
* Does this expression read or write parameters within the scope of the
* expression?
*/
public boolean accessesParameter() {
return accessesParameter;
}
public boolean canThrowException() {
return canThrowException;
}
public boolean createsObject() {
return createsObject;
}
@Override
public void endVisit(JArrayLength x, Context ctx) {
// TODO: Is setting accessesField necessary for array.length access?
accessesField = true;
// Can throw an NPE when the array instance is null at runtime.
JReferenceType refType = (JReferenceType) x.getInstance().getType();
canThrowException = refType.canBeNull();
}
@Override
public void endVisit(JArrayRef x, Context ctx) {
/*
* In Java, array references can throw IndexOutOfBoundsExceptions, but this
* isn't the case for current GWT generated code. If we add a strict array
* bounds check later, this flag would need to reflect it.
*/
// If JArrayRef is null, this can throw a NullPointerException.
canThrowException = true;
}
@Override
public void endVisit(JBinaryOperation x, Context ctx) {
if (x.isAssignment()) {
JExpression lhs = x.getLhs();
if (lhs instanceof JArrayRef) {
// Array store operations can throw ArrayStoreExceptions
canThrowException = true;
} else {
analyzeStore(lhs);
}
}
}
@Override
public void endVisit(JCastOperation x, Context ctx) {
// Can throw ClassCastException
canThrowException = true;
}
@Override
public void endVisit(JFieldRef x, Context ctx) {
accessesField = true;
if (!x.getTarget().isFinal()) {
accessesFieldNonFinal = true;
}
if (x.hasClinit()) {
recordMethodCall();
}
JExpression instance = x.getInstance();
if (instance == null) {
return;
}
// Field references using this are always safe
if (instance instanceof JThisRef) {
return;
}
if (x.getField().isStatic()) {
// Can throw exceptions IFF a clinit is triggered.
canThrowException = x.hasClinit();
} else {
// Can throw exceptions IFF the instance is null.
JReferenceType refType = (JReferenceType) instance.getType();
canThrowException = refType.canBeNull();
}
}
@Override
public void endVisit(JLocalRef x, Context ctx) {
accessesLocal = true;
}
@Override
public void endVisit(JMethodCall x, Context ctx) {
recordMethodCall();
}
@Override
public void endVisit(JNewArray x, Context ctx) {
/*
* If no array bounds, the new array is being automatically initialized. If
* there are side-effects, they'll show up when we visit the initializers.
*/
if (x.dims == null) {
return;
}
/*
* Can throw NegativeArraySizeException if we initialize an array with
* negative dimensions.
*/
for (JExpression expression : x.dims) {
if (expression instanceof JIntLiteral) {
int value = ((JIntLiteral) expression).getValue();
if (value >= 0) {
continue;
}
}
canThrowException = true;
}
}
@Override
public void endVisit(JNewInstance x, Context ctx) {
createsObject = true;
endVisit((JMethodCall) x, ctx);
}
@Override
public void endVisit(JParameterRef x, Context ctx) {
accessesParameter = true;
}
@Override
public void endVisit(JPostfixOperation x, Context ctx) {
// Unary operations that are modifying cause assignment side-effects.
if (x.getOp().isModifying()) {
analyzeStore(x.getArg());
}
}
@Override
public void endVisit(JPrefixOperation x, Context ctx) {
// Unary operations that are modifying cause assignment side-effects.
if (x.getOp().isModifying()) {
analyzeStore(x.getArg());
}
}
/**
* Does this expression make assignments to variables within the scope of the
* expression?
*/
public boolean hasAssignment() {
return assignmentToField || assignmentToLocal || assignmentToParameter;
}
/**
* Does this expression make assignments to fields within the scope of the
* expression?
*/
public boolean hasAssignmentToField() {
return assignmentToField;
}
/**
* Does this expression make assignments to locals within the scope of the
* expression?
*/
public boolean hasAssignmentToLocal() {
return assignmentToLocal;
}
/**
* Does this expression make assignments to parameters within the scope of the
* expression?
*/
public boolean hasAssignmentToParameter() {
return assignmentToParameter;
}
@Override
public boolean visit(JBinaryOperation x, Context ctx) {
if (x.getOp() == JBinaryOperator.AND || x.getOp() == JBinaryOperator.OR) {
accept(x.getLhs());
inConditional++;
accept(x.getRhs());
inConditional--;
return false;
}
return true;
}
@Override
public boolean visit(JConditional x, Context ctx) {
accept(x.getIfTest());
inConditional++;
accept(x.getThenExpr());
accept(x.getElseExpr());
inConditional--;
return false;
}
/**
* Determined if the current expression conditionally executes, based on its
* parent expressions.
*/
protected boolean isInConditional() {
return inConditional > 0;
}
private void analyzeStore(JExpression expr) {
if (expr instanceof JFieldRef) {
assignmentToField = true;
} else if (expr instanceof JParameterRef) {
assignmentToParameter = true;
} else if (expr instanceof JLocalRef) {
assignmentToLocal = true;
}
}
/**
* We can't assume anything about method calls right now, except that it can't
* access any of our locals or parameters.
*
* TODO: what about accessing arrays? Should be treated like field refs I
* guess.
*/
private void recordMethodCall() {
assignmentToField = true;
accessesField = true;
accessesFieldNonFinal = true;
canThrowException = true;
createsObject = true;
}
}