Use of Exceptions in the Fortress Implementation

This document lists how exceptions are currently used in the Fortress code base, and it gives a proposal for revising the exceptions declaration. The document consists of three top-level sections: (A) a listing of the current system, (B) a listing of the proposed system, and (C) the mechanism for moving from the current system to the proposed system. The numbering scheme in part (A) aligns with the numbering system in part (C).

Two design decisions motivate the proposed changes: (i) all exceptions classes should reside in one package (sun.com.fortress.exceptions) or a subpackage. We currently have 15 exception types that are located in 8 packages. Exceptions are a cross-cutting concern that should be easy to locate for a new developer learning to use our code base. And (ii) no exception class should be used to mean two different things in different contexts. We see examples of this phenomenon in cases (1), (9), and (10) of the current system.

What has not changed in the proposed system is the unchecked/checked distinction among exceptions. All exceptions remain unchecked exceptions (but shell.UserError was a checked exception so it stays a checked exception).

For succinctness the prefix "com.sun.fortress" has been removed from all package identifiers. This is for the document only, and the prefix will remain in the code base. All source code examples are from r1785.

CURRENT SYSTEM (all but one exception extend RuntimeException)

1) compiler.StaticError implements HasAt. We are using the type StaticError to mean two different things: it is either (A) an illegal Fortress program that is detected during static analysis (see ExprDisambiguator.java:131), or (B) a runtime exception caused by the static analysis, such as a FileNotFound exception (see FortressParser.java:86).

2) compiler.Fortress.WrappedException is a subclass of StaticError. Classes in the compiler package are using this to wrap Java runtime exceptions.

3) abstract class compiler.TypeError extends StaticError. These are static errors generated by the type checker (StaticError use case A).

4) syntax_abstractions.rats.RatsParserGenerator.ParserGeneratorError extends StaticError. This is dead code and is not used anywhere.

5) compiler.Parser.Error extends StaticError. This class is used to wrap exceptions generated by the Rats parser.

6) evaluation.transactions.exceptions.{AbortedException, GracefulException, PanicException} These are the transaction exceptions. They are used consistently and each type name describes the purpose of the type.

7) abstract interpreter.evaluator.FortressException. This is an abstract class with four subclasses: FortressError, InterpreterBug, ProgramError, and TaskError. We use this supertype to keep track of the call stack for error reporting.

8) interpreter.evaluator.FortressError extends FortressException. These denote Fortress exceptions thrown by the source Fortress program, as described in section 14 of the Fortress Language Specification.

9) interpreter.evaluator.InterpreterBug extends FortressException. (From JavaDoc) "An InterpreterBug should be thrown when the interpreter finds itself in an inconsistent state, and wants to provide feedback on the Fortress source program which will enable the inconsistency to be debugged and/or worked around." (From Skype) "InterpreterBug means the static checker did something wrong...I think InterpreterBug really isn't interpreter-specific." (identical to StaticError usage 1B)

10) interpreter.evaluator.ProgramError extends FortressException. This is the supertype of CircularDependenceError, NoDefinitionError, RedifinitionError, and UnificationError. Those subtypes denote static errors in the source Fortress program. ProgramError is also being used to throw runtime errors where the source Fortress program may be correct, but our program has exceptional behavior (see GraphRepository.java:578 or Fortress.java:293).

11) interpreter.evaluator.tasks.TaskError extends FortressException. Never used in the code base. This is dead code.

12) interpreter.evaluator.LabelException and interpreter.evaluator.NamedLabelException. These exceptions implement the named and unnamed exit expressions in the Fortress interpreter, as described in section 13.12 of the Fortress Language Specification.

13) shell.RepositoryError. These are either static Fortress program errors, or when our program has exceptional behavior, that relate to Components and APIs (see chapter 20 of the Fortress Language Specification).

14) shell.ShellException. These are exceptions when our Fortress shell has exceptional behavior (mainly a wrapper for IOExceptions in shell).

15) shell.UserError. These are exceptions when the user has asked us to perform an illegal operation in shell. This is the only checked exception in the Fortress code base.

PROPOSED SYSTEM (all but one exception extend RuntimeException)

RULE: All exceptions must live in sun.com.fortress.exceptions or in a subpackage.

RULE: All exceptions must have Javadoc class descriptions. We will use the descriptions below to fulfill this rule.

i) AbstractContextPreserveException : AbstractContextPreserveException is the supertype of IllegalProgramException, FortressLanguageException, and InterpreterException. We use this class in the interpreter to keep track of the call stack for error reporting. Eventually, IllegalProgramExceptions should all be detected statically and will no longer need to extends this superclass.

ii) IllegalProgramException extends AbstractContextPreserveException : this class denotes illegal Fortress programs. Some day all these exceptions will be detected during static analysis. In the current implementation, some illegal programs are detected during runtime. As a consequence, this class must extend AbstractContextPreserveException for the present time.

iii) FortressLanguageException extends AbstractContextPreserveException : this class denotes Fortress exceptions thrown by the source Fortress program, as outlined in chapter 14 of the Fortress Language Specification.

iv) (subpackage transactions) TransactionException : this class is the abstract superclass of {AbortedException, GracefulException, and PanicException}.

v) LabelException and NamedLabelException extends AbstractContextPreserveException: these exceptions implement the named and unnamed exit expressions in the Fortress interpreter, as described in section 13.12 of the Fortress Language Specification.

vi) InterpreterException extends AbstractContextPreserveException : An InterpreterException should be thrown when the interpreter finds itself in an inconsistent state, and wants to provide feedback on the Fortress source program which will enable the inconsistency to be debugged and/or worked around.

vii) StaticAnalysisException : A StaticAnalysisException should be thrown when any stage in static analysis finds itself in an inconsistent state, and wants to provide feedback on the Fortress source program which will enable the inconsistency to be debugged and/or worked around. Static analysis is broadly defined to include any stage in processing that is shared by the Fortress interpreter and Fortress compiler. This includes parsing, syntax-abstraction, desugaring, disambiguation, and type-checking. This class may be subclassed for each component of static analysis.

viii) RepositoryException : These are either illegal Fortress programs or when our interpreter/compiler has exceptional behavior, that relate to Components and APIs (see chapter 20 of the Fortress Language Specification).

ix) (subpackage shell) ShellException : A ShellException should be thrown then any stage in the shell finds itself in an inconsistent state, and wants to provide feedback on the Fortress source program which will enable the inconsistency to be debugged and/or worked around.

x) (subpackage shell) UserError. These are exceptions when the user has asked us to perform an illegal operation in shell. This is the only checked exception in the Fortress code base.

GETTING FROM THE CURRENT SYSTEM TO THE PROPOSED SYSTEM

1) move compiler.StaticError to exceptions.IllegalProgramException. Manually check for cases that are caused by bugs in the interpreter code and use InterpreterExceptions in those instances.

2) Replace all constructions of compiler.Fortress.WrappedException with calls to the Exception(Throwable cause) constructors of either StaticAnalysisException or IllegalProgramException, as appropriate depending on context. Then eliminate WrappedException.

3) move compiler.TypeError to exceptions.TypeError and make it extend IllegelProgramException.

4) Eliminate syntax_abstractions.rats.RatsParserGenerator.ParserGeneratorError.

5) move compiler.Parser.Error to exceptions.ParserError and make it extend IllegalProgramException.

6) move evaluation.transactions.exceptions.{AbortedException, GracefulException, PanicException} to exceptions.transactions.{AbortedException, GracefulException, PanicException}.

7) eliminate interpreter.evaluator.FortressException.

8) move interpreter.evaluator.FortressError to exceptions.FortressLanguageException.

9) move interpreter.evaluator.InterpreterBug to evaluation.exceptions.InterpreterException. Where InterpreterBug is used during static analysis throw a StaticAnalysisException.

10) interpreter.evaluator.ProgramErrors that are caused by interpreter code should be renamed InterpreterExceptions and illegal Fortress programs should be throwing IllegalProgramExceptions. Create CircularDependenceException, NoDefinitionException, RedifinitionException, and UnificationException as subclasses of IllegalProgramException.

11) Eliminate dead code interpreter.evaluator.tasks.TaskError.

12) move interpreter.evaluator.LabelException and interpreter.evaluator.NamedLabelException to exceptions.LabelException and exceptions.NamedLabelException and have them extend AbstractContextPreserveException.

13) move shell.RepositoryError to exceptions.shell.RepositoryError.

14) move shell.ShellException to exceptions.shell.ShellException.

15) move shell.UserError to exceptions.shell.UserError.