blob: a702ffa1bbd1e7f373c305d5363ac4cc73060ab6 [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.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.JBlock;
import com.google.gwt.dev.jjs.ast.JDeclarationStatement;
import com.google.gwt.dev.jjs.ast.JDeclaredType;
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JIfStatement;
import com.google.gwt.dev.jjs.ast.JInstanceOf;
import com.google.gwt.dev.jjs.ast.JLocal;
import com.google.gwt.dev.jjs.ast.JLocalRef;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JMethodBody;
import com.google.gwt.dev.jjs.ast.JMethodCall;
import com.google.gwt.dev.jjs.ast.JModVisitor;
import com.google.gwt.dev.jjs.ast.JNewInstance;
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.JStatement;
import com.google.gwt.dev.jjs.ast.JThrowStatement;
import com.google.gwt.dev.jjs.ast.JTryStatement;
import com.google.gwt.dev.jjs.ast.JType;
import java.util.ArrayList;
import java.util.List;
/**
* Merge multi-catch blocks into a single catch block that uses instanceof tests
* to determine which user block to run.
*/
public class CatchBlockNormalizer {
/**
* Collapses all multi-catch blocks into a single catch block.
*/
private class CollapseCatchBlocks extends JModVisitor {
JMethod wrapMethod = program.getIndexedMethod("Exceptions.wrap");
@Override
public void endVisit(JMethodBody x, Context ctx) {
clearLocals();
currentMethodBody = null;
}
@Override
public void endVisit(JTryStatement x, Context ctx) {
if (x.getCatchClauses().isEmpty()) {
return;
}
SourceInfo catchInfo = x.getCatchClauses().get(0).getBlock().getSourceInfo();
JLocal exceptionVariable = newExceptionVariable(x.getSourceInfo());
JBlock newCatchBlock = new JBlock(catchInfo);
{
// $e = Exceptions.wrap($e)
JMethodCall call = new JMethodCall(catchInfo, null, wrapMethod);
call.addArg(new JLocalRef(catchInfo, exceptionVariable));
newCatchBlock.addStmt(JProgram.createAssignmentStmt(catchInfo, new JLocalRef(catchInfo,
exceptionVariable), call));
}
/*
* Build up a series of if, else if statements to test the type of the
* exception object against the types of the user's catch block. Each catch block might have
* multiple types in Java 7.
*
* Go backwards so we can nest the else statements in the correct order!
*/
// rethrow the current exception if no one caught it.
JStatement cur = new JThrowStatement(catchInfo, new JLocalRef(catchInfo, exceptionVariable));
for (int i = x.getCatchClauses().size() - 1; i >= 0; i--) {
JTryStatement.CatchClause clause = x.getCatchClauses().get(i);
JBlock block = clause.getBlock();
JLocalRef arg = clause.getArg();
List<JType> exceptionsTypes = clause.getTypes();
catchInfo = block.getSourceInfo();
// if ($e instanceof ArgType1 or $e instanceof ArgType2 ...) {
// var userVar = $e; <user code>
// }
// Handle the first Exception type.
JExpression ifTest = new JInstanceOf(catchInfo, (JReferenceType) exceptionsTypes.get(0),
new JLocalRef(catchInfo, exceptionVariable));
// Handle the rest of the Exception types if any.
for (int j = 1; j < exceptionsTypes.size(); j++) {
JExpression orExp = new JInstanceOf(catchInfo, (JReferenceType) exceptionsTypes.get(j),
new JLocalRef(catchInfo, exceptionVariable));
ifTest = new JBinaryOperation(catchInfo, JPrimitiveType.BOOLEAN, JBinaryOperator.OR,
ifTest, orExp);
}
JDeclarationStatement declaration =
new JDeclarationStatement(catchInfo, arg, new JLocalRef(catchInfo, exceptionVariable));
block.addStmt(0, declaration);
// nest the previous as an else for me
cur = new JIfStatement(catchInfo, ifTest, block, cur);
}
newCatchBlock.addStmt(cur);
// Replace with a single catch block.
x.getCatchClauses().clear();
List<JType> newCatchTypes = new ArrayList<JType>(1);
newCatchTypes.add(exceptionVariable.getType());
x.getCatchClauses().add(new JTryStatement.CatchClause(newCatchTypes,
new JLocalRef(newCatchBlock.getSourceInfo(), exceptionVariable), newCatchBlock));
}
@Override
public boolean visit(JMethodBody x, Context ctx) {
currentMethodBody = x;
clearLocals();
return true;
}
}
private class UnwrapJavaScriptExceptionVisitor extends JModVisitor {
JDeclaredType jseType =
program.getFromTypeMap("com.google.gwt.core.client.JavaScriptException");
JMethod unwrapMethod = program.getIndexedMethod("Exceptions.unwrap");
@Override
public void endVisit(JThrowStatement x, Context ctx) {
assert jseType != null;
JExpression expr = x.getExpr();
// Optimization: unwrap not needed if "new BlahException()"
if (expr instanceof JNewInstance && !expr.getType().equals(jseType)) {
return;
}
// Optimization: unwrap not needed if expression can never be JavaScriptException
if (!program.typeOracle.canTheoreticallyCast((JReferenceType) expr.getType(), jseType)) {
return;
}
// throw x; -> throw Exceptions.unwrap(x);
ctx.replaceMe(createUnwrappedThrow(x));
}
private JThrowStatement createUnwrappedThrow(JThrowStatement x) {
JMethodCall call = new JMethodCall(x.getSourceInfo(), null, unwrapMethod);
call.addArg(x.getExpr());
return new JThrowStatement(x.getSourceInfo(), call);
}
}
public static void exec(JProgram program) {
new CatchBlockNormalizer(program).execImpl();
}
private JMethodBody currentMethodBody;
private int catchVariableIndex;
private final JProgram program;
private CatchBlockNormalizer(JProgram program) {
this.program = program;
}
private void clearLocals() {
catchVariableIndex = 0;
}
private void execImpl() {
CollapseCatchBlocks collapser = new CollapseCatchBlocks();
collapser.accept(program);
UnwrapJavaScriptExceptionVisitor unwrapper = new UnwrapJavaScriptExceptionVisitor();
unwrapper.accept(program);
}
private JLocal newExceptionVariable(SourceInfo sourceInfo) {
return JProgram.createLocal(sourceInfo, "$e" + catchVariableIndex++,
program.getTypeJavaLangObject(), false, currentMethodBody);
}
}