/*
* This file is part of the X10 project (http://x10-lang.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* This file was originally derived from the Polyglot extensible compiler framework.
*
* (C) Copyright 2000-2007 Polyglot project group, Cornell University
* (C) Copyright IBM Corporation 2007-2012.
*/
package polyglot.visit;
import java.util.*;
import polyglot.ast.Node;
import polyglot.ast.NodeFactory;
import polyglot.frontend.Job;
import polyglot.types.*;
import polyglot.util.*;
import x10.util.CollectionFactory;
/** Visitor which checks if exceptions are caught or declared properly. */
public class ExceptionChecker extends ErrorHandlingVisitor
{
protected ExceptionChecker outer;
/**
* Set of exceptions that can be caught. Combined with the outer
* field, these sets form a stack of exceptions, representing
* all and only the exceptions that may be thrown at this point in
* the code.
*
* Note: Consider the following code, where A,B,C,D are Exception subclasses.
* void m() throws A, B {
* try {
* ...
* }
* catch (C ex) { ... }
* catch (D ex) { ... }
* }
*
* Inside the try-block, the stack of catchable sets is:
* { C }
* { D }
* { A, B }
*/
protected Set<Type> catchable;
/**
* The throws set, calculated bottom up.
*/
protected SubtypeSet throwsSet;
/**
* Responsible for creating an appropriate exception.
*/
protected UncaughtReporter reporter;
/**
* Should the propogation of eceptions upwards go past this point?
*/
protected boolean catchAllThrowable;
public ExceptionChecker(Job job, TypeSystem ts, NodeFactory nf) {
super(job, ts, nf);
this.outer = null;
this.catchAllThrowable = false;
}
public ExceptionChecker push(UncaughtReporter reporter) {
ExceptionChecker ec = this.push();
ec.reporter = reporter;
ec.throwsSet = new SubtypeSet(ts.CheckedThrowable());
return ec;
}
public ExceptionChecker push(Type catchableType) {
ExceptionChecker ec = this.push();
ec.catchable = Collections.<Type>singleton(catchableType);
ec.throwsSet = new SubtypeSet(ts.CheckedThrowable());
return ec;
}
public ExceptionChecker push(Collection<Type> catchableTypes) {
ExceptionChecker ec = this.push();
ec.catchable = CollectionFactory.newHashSet(catchableTypes);
ec.throwsSet = new SubtypeSet(ts.CheckedThrowable());
return ec;
}
public ExceptionChecker pushCatchAllThrowable() {
ExceptionChecker ec = this.push();
ec.throwsSet = new SubtypeSet(ts.CheckedThrowable());
ec.catchAllThrowable = true;
return ec;
}
public ExceptionChecker push() {
throwsSet(); // force an instantiation of the throwsset.
ExceptionChecker ec = (ExceptionChecker) this.shallowCopy();
ec.outer = this;
ec.catchable = null;
ec.catchAllThrowable = false;
return ec;
}
public ExceptionChecker pop() {
return outer;
}
protected NodeVisitor enterCall(Node n) throws SemanticException {
return n.del().exceptionCheckEnter(this);
}
protected NodeVisitor enterError(Node n) {
return push();
}
/**
* Call exceptionCheck(ExceptionChecker) on the node.
*
* @param old The original state of root of the current subtree.
* @param n The current state of the root of the current subtree.
* @param v The <code>NodeVisitor</code> object used to visit the children.
* @return The final result of the traversal of the tree rooted at
* <code>n</code>.
*/
protected Node leaveCall(Node old, Node n, NodeVisitor v)
throws SemanticException {
if (v instanceof PruningVisitor) {
// [DC] it seems this means no children were visited
// this means we are probably in a try, but we must visit the try
// doing the following to force that... probably wrong...
return n.del().exceptionCheck(this);
}
ExceptionChecker inner = (ExceptionChecker) v;
{
// code in this block checks the invariant that
// this ExceptionChecker must be an ancestor of inner, i.e.,
// inner must be the result of zero or more pushes.
boolean isAncestor = false;
ExceptionChecker ec = inner;
while (!isAncestor && ec != null) {
isAncestor = isAncestor || (ec == this);
ec = ec.outer;
}
if (!isAncestor) {
throw new InternalCompilerError("oops!");
}
}
// gather exceptions from this node.
return n.del().exceptionCheck(inner);
}
/**
* The ast nodes will use this callback to notify us that they throw an
* exception of type t. This method will throw a SemanticException if the
* type t is not allowed to be thrown at this point; the exception t will be
* added to the throwsSet of all exception checkers in the stack, up to (and
* not including) the exception checker that catches the exception.
* @param t The type of exception that the node throws.
*
* @throws SemanticException
*/
public void throwsException(Type t, Position pos) throws SemanticException {
if (! t.isUncheckedException()) {
// go through the stack of catches and see if the exception
// is caught.
boolean exceptionCaught = false;
ExceptionChecker ec = this;
while (!exceptionCaught && ec != null) {
if (ec.catchable != null) {
for (Iterator<Type> iter = ec.catchable.iterator(); iter.hasNext(); ) {
Type catchType = (Type)iter.next();
if (ts.isSubtype(t, catchType, ts.emptyContext())) {
exceptionCaught = true;
break;
}
}
}
if (!exceptionCaught && ec.throwsSet != null) {
// add t to ec's throwsSet.
ec.throwsSet.add(t);
}
if (ec.catchAllThrowable) {
// stop the propagation
exceptionCaught = true;
}
ec = ec.pop();
}
if (! exceptionCaught) {
reportUncaughtException(t, pos);
}
}
}
public SubtypeSet throwsSet() {
if (this.throwsSet == null) {
this.throwsSet = new SubtypeSet(ts.CheckedThrowable());
}
return this.throwsSet;
}
protected void reportUncaughtException(Type t, Position pos) throws SemanticException {
ExceptionChecker ec = this;
UncaughtReporter ur = null;
while (ec != null && ur == null) {
ur = ec.reporter;
ec = ec.outer;
}
if (ur == null) {
ur = new UncaughtReporter();
}
ur.uncaughtType(t, pos);
}
public static class UncaughtReporter {
/**
* This method must throw a SemanticException, reporting
* that the Exception type t must be caught.
* @throws SemanticException
*/
void uncaughtType(Type t, Position pos) throws SemanticException {
throw new SemanticException("The exception \"" + t +"\" must either be caught or declared to be thrown.", pos);
}
}
public static class CodeTypeReporter extends UncaughtReporter {
public final String codeType;
public CodeTypeReporter(String codeType) {
this.codeType = codeType;
}
void uncaughtType(Type t, Position pos) throws SemanticException {
SemanticException e = new SemanticException(codeType + " cannot throw a \"" + t + "\"; the exception must either be caught or declared to be thrown.", pos);
Map<String, Object> map = CollectionFactory.newHashMap();
map.put(CodedErrorInfo.ERROR_CODE_KEY, CodedErrorInfo.ERROR_CODE_SURROUND_THROW);
map.put("TYPE", t.toString());
e.setAttributes(map);
throw e;
}
}
}