blob: c0c067c7993818b02a59cc91a5d6617f8bff3a2b [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.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.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 com.google.gwt.dev.jjs.ast.RuntimeConstants;
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(RuntimeConstants.EXCEPTIONS_TO_JAVA);
@Override
public void endVisit(JMethodBody x, Context ctx) {
clearLocals();
currentMethodBody = null;
}
@Override
public void endVisit(JTryStatement x, Context ctx) {
if (x.getCatchClauses().isEmpty() &&
(x.getFinallyBlock() == null || x.getFinallyBlock().isEmpty())) {
// Remove {@code try { ... }} if there are not catch and/or finally blocks. Translating it
// to JavaScript as {@code try { ... }} without catch and/or finally is illegal.
ctx.replaceMe(x.getTryBlock());
}
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.toJava($e)
JMethodCall call =
new JMethodCall(catchInfo, null, wrapMethod, exceptionVariable.makeRef(catchInfo));
newCatchBlock.addStmt(
JProgram.createAssignmentStmt(catchInfo, exceptionVariable.makeRef(catchInfo), 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, exceptionVariable.makeRef(catchInfo));
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),
exceptionVariable.makeRef(catchInfo));
// 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),
exceptionVariable.makeRef(catchInfo));
ifTest = new JBinaryOperation(catchInfo, JPrimitiveType.BOOLEAN, JBinaryOperator.OR,
ifTest, orExp);
}
JDeclarationStatement declaration =
new JDeclarationStatement(catchInfo, arg, exceptionVariable.makeRef(catchInfo));
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,
exceptionVariable.makeRef(catchInfo), newCatchBlock));
}
@Override
public boolean visit(JMethodBody x, Context ctx) {
currentMethodBody = x;
clearLocals();
return true;
}
}
private class UnwrapThrowableVisitor extends JModVisitor {
JMethod unwrapMethod = program.getIndexedMethod(RuntimeConstants.EXCEPTIONS_TO_JS);
@Override
public void endVisit(JThrowStatement x, Context ctx) {
// throw x; -> throw Exceptions.toJs(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);
UnwrapThrowableVisitor unwrapper = new UnwrapThrowableVisitor();
unwrapper.accept(program);
}
private JLocal newExceptionVariable(SourceInfo sourceInfo) {
return JProgram.createLocal(sourceInfo, "$e" + catchVariableIndex++,
program.getTypeJavaLangObject(), false, currentMethodBody);
}
}