| /* |
| * 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) { |
| createsObject = true; |
| /* |
| * 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.getDimensionExpressions() == null) { |
| return; |
| } |
| |
| /* |
| * Can throw NegativeArraySizeException if we initialize an array with |
| * negative dimensions. |
| */ |
| for (JExpression expression : x.getDimensionExpressions()) { |
| 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; |
| } |
| } |