Exception handling (programming) explained

In computer programming, several language mechanisms exist for exception handling. The term exception is typically used to denote a data structure storing information about an exceptional condition. One mechanism to transfer control, or raise an exception, is known as a throw; the exception is said to be thrown. Execution is transferred to a catch.

Usage

Programming languages differ substantially in their notion of what an exception is. Exceptions can be used to represent and handle abnormal, unpredictable, erroneous situations, but also as flow control structures to handle normal situations. For example, Python's iterators throw StopIteration exceptions to signal that there are no further items produced by the iterator.[1] There is disagreement within many languages as to what constitutes idiomatic usage of exceptions. For example, Joshua Bloch states that Java's exceptions should only be used for exceptional situations,[2] but Kiniry observes that Java's built-in is not at all an exceptional event.[3] Similarly, Bjarne Stroustrup, author of C++, states that C++ exceptions should only be used for error handling, as this is what they were designed for,[4] but Kiniry observes that many modern languages such as Ada, C++,Modula-3, ML and OCaml, Python, and Ruby use exceptions for flow control. Some languages such as Eiffel, C#, Common Lisp, and Modula-2 have made a concerted effort to restrict their usage of exceptions, although this is done on a social rather than technical level.[5]

History

Software exception handling developed in the 1960s and 1970s. LISP 1.5 (1958-1961)[6] allowed exceptions to be raised by the ERROR pseudo-function, similarly to errors raised by the interpreter or compiler. Exceptions were caught by the ERRORSET keyword, which returned NIL in case of an error, instead of terminating the program or entering the debugger.[7] PL/I introduced its own form of exception handling circa 1964, allowing interrupts to be handled with ON units.[8] MacLisp observed that ERRSET and ERR were used not only for error raising, but for non-local control flow, and thus added two new keywords, CATCH and THROW (June 1972). The cleanup behavior now generally called "finally" was introduced in NIL (New Implementation of LISP) in the mid- to late-1970s as UNWIND-PROTECT. This was then adopted by Common Lisp. Contemporary with this was dynamic-wind in Scheme, which handled exceptions in closures. The first papers on structured exception handling were and . Exception handling was subsequently widely adopted by many programming languages from the 1980s onward.

Syntax

Many computer languages have built-in syntactic support for exceptions and exception handling. This includes ActionScript, Ada, BlitzMax, C++, C#, Clojure, COBOL, D, ECMAScript, Eiffel, Java, ML, Object Pascal (e.g. Delphi, Free Pascal, and the like), PowerBuilder, Objective-C, OCaml, Perl,[9] PHP (as of version 5), PL/I, PL/SQL, Prolog, Python, REALbasic, Ruby, Scala, Seed7, Smalltalk, Tcl, Visual Prolog and most .NET languages.

Excluding minor syntactic differences, there are only a couple of exception handling styles in use. In the most popular style, an exception is initiated by a special statement (throw or raise) with an exception object (e.g. with Java or Object Pascal) or a value of a special extendable enumerated type (e.g. with Ada or SML). The scope for exception handlers starts with a marker clause (try or the language's block starter such as begin) and ends in the start of the first handler clause (catch, except, rescue). Several handler clauses can follow, and each can specify which exception types it handles and what name it uses for the exception object. As a minor variation, some languages use a single handler clause, which deals with the class of the exception internally.

Also common is a related clause (finally or ensure) that is executed whether an exception occurred or not, typically to release resources acquired within the body of the exception-handling block. Notably, C++ does not provide this construct, recommending instead the Resource Acquisition Is Initialization (RAII) technique which frees resources using destructors.[10] According to a 2008 paper by Westley Weimer and George Necula, the syntax of the try...finally blocks in Java is a contributing factor to software defects. When a method needs to handle the acquisition and release of 3–5 resources, programmers are apparently unwilling to nest enough blocks due to readability concerns, even when this would be a correct solution. It is possible to use a single try...finally block even when dealing with multiple resources, but that requires a correct use of sentinel values, which is another common source of bugs for this type of problem.

Python and Ruby also permit a clause (else) that is used in case no exception occurred before the end of the handler's scope was reached.

In its whole, exception handling code might look like this (in Java-like pseudocode):

try catch (EmptyLineException e) catch (Exception e) else finally

C does not have try-catch exception handling, but uses return codes for error checking. The setjmp and longjmp standard library functions can be used to implement try-catch handling via macros.[11]

Perl 5 uses die for throw and for try-catch. It has CPAN modules that offer try-catch semantics.[12]

Termination and resumption semantics

When an exception is thrown, the program searches back through the stack of function calls until an exception handler is found. Some languages call for unwinding the stack as this search progresses. That is, if function, containing a handler for exception, calls function, which in turn calls function, and an exception occurs in, then functions and may be terminated, and in will handle . This is said to be termination semantics.Alternately, the exception handling mechanisms may not unwind the stack on entry[13] to an exception handler, giving the exception handler the option to restart the computation, resume or unwind. This allows the program to continue the computation at exactly the same place where the error occurred (for example when a previously missing file has become available) or to implement notifications, logging, queries and fluid variables on top of the exception handling mechanism (as done in Smalltalk). Allowing the computation to resume where it left off is termed resumption semantics.

There are theoretical and design arguments in favor of either decision. C++ standardization discussions in 1989–1991 resulted in a definitive decision to use termination semantics in C++. Bjarne Stroustrup cites a presentation by Jim Mitchell as a key data point:

Exception-handling languages with resumption include Common Lisp with its Condition System, PL/I, Dylan, R,[14] and Smalltalk. However, the majority of newer programming languages follow C++ and use termination semantics.

Exception handling implementation

The implementation of exception handling in programming languages typically involves a fair amount of support from both a code generator and the runtime system accompanying a compiler. (It was the addition of exception handling to C++ that ended the useful lifetime of the original C++ compiler, Cfront.[15]) Two schemes are most common. The first, , generates code that continually updates structures about the program state in terms of exception handling.[16] Typically, this adds a new element to the stack frame layout that knows what handlers are available for the function or method associated with that frame; if an exception is thrown, a pointer in the layout directs the runtime to the appropriate handler code. This approach is compact in terms of space, but adds execution overhead on frame entry and exit. It was commonly used in many Ada implementations, for example, where complex generation and runtime support was already needed for many other language features. Microsoft's 32-bit Structured Exception Handling (SEH) uses this approach with a separate exception stack.[17] Dynamic registration, being fairly straightforward to define, is amenable to proof of correctness.[18]

The second scheme, and the one implemented in many production-quality C++ compilers and 64-bit Microsoft SEH, is a . This creates static tables at compile time and link time that relate ranges of the program counter to the program state with respect to exception handling.[19] Then, if an exception is thrown, the runtime system looks up the current instruction location in the tables and determines what handlers are in play and what needs to be done. This approach minimizes executive overhead for the case where an exception is not thrown. This happens at the cost of some space, but this space can be allocated into read-only, special-purpose data sections that are not loaded or relocated until an exception is actually thrown.[20] The location (in memory) of the code for handling an exception need not be located within (or even near) the region of memory where the rest of the function's code is stored. So if an exception is thrown then a performance hit – roughly comparable to a function call[21] – may occur if the necessary exception handling code needs to be loaded/cached. However, this scheme has minimal performance cost if no exception is thrown. Since exceptions in C++ are supposed to be exceptional (i.e. uncommon/rare) events, the phrase "zero-cost exceptions"[22] is sometimes used to describe exception handling in C++. Like runtime type identification (RTTI), exceptions might not adhere to C++'s zero-overhead principle as implementing exception handling at run-time requires a non-zero amount of memory for the lookup table.[23] For this reason, exception handling (and RTTI) can be disabled in many C++ compilers, which may be useful for systems with very limited memory (such as embedded systems). This second approach is also superior in terms of achieving thread safety.

Other definitional and implementation schemes have been proposed as well. For languages that support metaprogramming, approaches that involve no overhead at all (beyond the already present support for reflection) have been advanced.[24]

Exception handling based on design by contract

A different view of exceptions is based on the principles of design by contract and is supported in particular by the Eiffel language. The idea is to provide a more rigorous basis for exception handling by defining precisely what is "normal" and "abnormal" behavior. Specifically, the approach is based on two concepts:

The "Safe Exception Handling principle" as introduced by Bertrand Meyer in Object-Oriented Software Construction then holds that there are only two meaningful ways a routine can react when an exception occurs:

In particular, simply ignoring an exception is not permitted; a block must either be retried and successfully complete, or propagate the exception to its caller.

Here is an example expressed in Eiffel syntax. It assumes that a routine is normally the better way to send a message, but it may fail, triggering an exception; if so, the algorithm next uses, which will fail less often. If fails, the routine as a whole should fail, causing the caller to get an exception.

send (m: MESSAGE) is -- Send m through fast link, if possible, otherwise through slow link.local tried_fast, tried_slow: BOOLEANdo if tried_fast then tried_slow := True send_slow (m) else tried_fast := True send_fast (m) endrescue if not tried_slow then retry endend

The boolean local variables are initialized to False at the start. If fails, the body (clause) will be executed again, causing execution of . If this execution of fails, the clause will execute to the end with no (no clause in the final), causing the routine execution as a whole to fail.

This approach has the merit of defining clearly what "normal" and "abnormal" cases are: an abnormal case, causing an exception, is one in which the routine is unable to fulfill its contract. It defines a clear distribution of roles: the clause (normal body) is in charge of achieving, or attempting to achieve, the routine's contract; the clause is in charge of reestablishing the context and restarting the process, if this has a chance of succeeding, but not of performing any actual computation.

Although exceptions in Eiffel have a fairly clear philosophy, Kiniry (2006) criticizes their implementation because "Exceptions that are part of the language definition are represented by INTEGER values, developer-defined exceptions by STRING values. [...] Additionally, because they are basic values and not objects, they have no inherent semantics beyond that which is expressed in a helper routine which necessarily cannot be foolproof because of the representation overloading in effect (e.g., one cannotdifferentiate two integers of the same value)."[3]

Uncaught exceptions

Contemporary applications face many design challenges when considering exception handling strategies. Particularly in modern enterprise level applications, exceptions must often cross process boundaries and machine boundaries. Part of designing a solid exception handling strategy is recognizing when a process has failed to the point where it cannot be economically handled by the software portion of the process.[25]

If an exception is thrown and not caught (operationally, an exception is thrown when there is no applicable handler specified), the uncaught exception is handled by the runtime; the routine that does this is called the .[26] [27] The most common default behavior is to terminate the program and print an error message to the console, usually including debug information such as a string representation of the exception and the stack trace.[26] [28] [29] This is often avoided by having a top-level (application-level) handler (for example in an event loop) that catches exceptions before they reach the runtime.[26] [30]

Note that even though an uncaught exception may result in the program terminating abnormally (the program may not be correct if an exception is not caught, notably by not rolling back partially completed transactions, or not releasing resources), the process terminates normally (assuming the runtime works correctly), as the runtime (which is controlling execution of the program) can ensure orderly shutdown of the process.

In a multithreaded program, an uncaught exception in a thread may instead result in termination of just that thread, not the entire process (uncaught exceptions in the thread-level handler are caught by the top-level handler). This is particularly important for servers, where for example a servlet (running in its own thread) can be terminated without the server overall being affected.

This default uncaught exception handler may be overridden, either globally or per-thread, for example to provide alternative logging or end-user reporting of uncaught exceptions, or to restart threads that terminate due to an uncaught exception. For example, in Java this is done for a single thread via [https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#setUncaughtExceptionHandler-java.lang.Thread.UncaughtExceptionHandler- Thread.setUncaughtExceptionHandler] and globally via [https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#setDefaultUncaughtExceptionHandler- Thread.setDefaultUncaughtExceptionHandler]; in Python this is done by modifying [https://docs.python.org/3/library/sys.html#sys.excepthook sys.excepthook].

Checked exceptions

Java introduced the notion of checked exceptions,[31] [32] which are special classes of exceptions. The checked exceptions that a method may raise must be part of the method's signature. For instance, if a method might throw an, it must declare this fact explicitly in its method signature. Failure to do so raises a compile-time error. According to Hanspeter Mössenböck, checked exceptions are less convenient but more robust.[33] Checked exceptions can, at compile time, reduce the incidence of unhandled exceptions surfacing at runtime in a given application.

Kiniry writes that "As any Java programmer knows, the volume of [[try catch]] code in a typical Java application is sometimes larger than the comparable code necessary for explicit formal parameter and return value checking in other languages that do not have checked exceptions. In fact, the general consensus among in-the-trenches Java programmers is that dealing with checked exceptions is nearly as unpleasant a task as writing documentation. Thus, many programmers report that they “resent” checked exceptions.".[3] Martin Fowler has written "...on the whole I think that exceptions are good, but Java checked exceptions are more trouble than they are worth." As of 2006 no major programming language has followed Java in adding checked exceptions.[34] For example, C# does not require or allow declaration of any exception specifications, with the following posted by Eric Gunnerson:[35] [3] [34]

Anders Hejlsberg describes two concerns with checked exceptions:[36]

To work around these, Hejlsberg says programmers resort to circumventing the feature by using a declaration. Another circumvention is to use a

Notes and References

  1. Web site: Built-in Exceptions — Python 3.10.4 documentation . docs.python.org . 17 May 2022.
  2. Book: Bloch , Joshua . Effective Java . 241 . Item 57: Use exceptions only for exceptional situations . 2008 . 978-0-321-35668-0 . Addison-Wesley . registration . https://archive.org/details/effectivejava00bloc_0/page/241 . Second .
  3. Book: 10.1007/11818502_16. Exceptions in Java and Eiffel: Two Extremes in Exception Design and Application. Advanced Topics in Exception Handling Techniques. 4119. 288–300. Lecture Notes in Computer Science. 2006. Kiniry . J. R. . 978-3-540-37443-5. 33283674.
  4. Web site: Stroustrup: C++ Style and Technique FAQ. www.stroustrup.com. 5 May 2018. live. https://web.archive.org/web/20180202012417/http://www.stroustrup.com/bs_faq2.html#exceptions-what-not. 2 February 2018.
  5. Book: 10.1007/11818502_16. Exceptions in Java and Eiffel: Two Extremes in Exception Design and Application. Advanced Topics in Exception Handling Techniques. 4119. 288–300. Lecture Notes in Computer Science. 2006. Kiniry . J. R. . 978-3-540-37443-5. 33283674.
  6. Web site: McCarthy . John . History of Lisp . www-formal.stanford.edu . 13 January 2022 . 12 February 1979.
  7. Book: John. McCarthy. Michael I.. Levin. Paul W.. Abrahams. Daniel J.. Edwards. Timothy P.. Hart. LISP 1.5 programmer's manual . 14 July 1961 . 13 January 2022.
  8. IBM System/360 Operating System, PL/I Language Specifications . C28-6571-3 . July 1966 . 120 . The ON Statement . http://www.bitsavers.org/pdf/ibm/360/pli/C28-6571-3_PL_I_Language_Specifications_Jul66.pdf#page=120 . IBM .
  9. Web site: Exceptions - Documentation for exception handling in Perl .
  10. Web site: Stroustrup . Bjarne . C++ Style and Technique FAQ . www.stroustrup.com . 12 January 2022.
  11. Roberts . Eric S. . Implementing Exceptions in C . 21 March 1989 . 4 January 2022 . . SRC-RR-40.
  12. Book: Christiansen . Tom . Torkington . Nathan . Perl cookbook . 2003 . O'Reilly . Beijing . 0-596-00313-7 . 2nd . 10.12. Handling Exceptions.
  13. In, e.g., PL/I, a normal exit from an exception handler unwinds the stack.
  14. Web site: R: Condition Handling and Recovery . 2022-12-05 . search.r-project.org.
  15. [Scott Meyers]
  16. D. Cameron, P. Faust, D. Lenkov, M. Mehta, "A portable implementation of C++ exception handling", Proceedings of the C++ Conference (August 1992) USENIX.
  17. Web site: Windows Exception Handling - Peter Kleissner. Peter Kleissner. February 14, 2009. 2009-11-21 . https://web.archive.org/web/20131014204335/http://stoned-vienna.com/html/index.php?page=windows-exception-handling . October 14, 2013 . dead., Compiler based Structured Exception Handling section
  18. Graham Hutton, Joel Wright, "Compiling Exceptions Correctly ". Proceedings of the 7th International Conference on Mathematics of Program Construction, 2004.
  19. Exception handling – Supporting the runtime mechanism . Lajoie . Josée . C++ Report . 6 . 3 . March–April 1994.
  20. Optimizing away C++ exception handling . Schilling . Jonathan L. . . 33 . 8 . August 1998 . 40–47 . 10.1145/286385.286390. 1522664 . free .
  21. Web site: Modern C++ best practices for exceptions and error handling. Microsoft. 8 March 2021. 21 March 2022.
  22. There is "zero [processing] cost" only if no exception is throw (although there will be a memory cost since memory is needed for the lookup table). There is a (potentially significant) cost if an exception is thrown (that is, if throw is executed). Implementing exception handling may also limit the possible compiler optimizations that may be performed.
  23. Web site: Stroustrup. Bjarne. Bjarne Stroustrup. C++ exceptions and alternatives. 18 November 2019. 23 March 2022.
  24. M. Hof, H. Mössenböck, P. Pirkelbauer, "Zero-Overhead Exception Handling Using Metaprogramming ", Proceedings SOFSEM'97, November 1997, Lecture Notes in Computer Science 1338, pp. 423-431.
  25. All Exceptions Are Handled, Jim Wilcox, Web site: All Exceptions Are Handled . 22 February 2008 .
  26. Mac Developer Library, "Uncaught Exceptions "
  27. MSDN, AppDomain.UnhandledException Event
  28. The Python Tutorial, "8. Errors and Exceptions "
  29. Web site: Java Practices -> Provide an uncaught exception handler. www.javapractices.com. 5 May 2018. live. https://web.archive.org/web/20160909002524/http://www.javapractices.com/topic/TopicAction.do?Id=229. 9 September 2016.
  30. PyMOTW (Python Module Of The Week), "Exception Handling "
  31. Web site: Google Answers: The origin of checked exceptions . 2011-12-15 . live . http://archive.wikiwix.com/cache/20110806090553/http://answers.google.com/answers/threadview?id=26101 . 2011-08-06 .
  32. Java Language Specification, chapter 11.2. http://java.sun.com/docs/books/jls/third_edition/html/exceptions.html#11.2
  33. Web site: 2011-08-05 . 2002-03-25 . Hanspeter . Mössenböck . 32 . Institut für Systemsoftware, Johannes Kepler Universität Linz, Fachbereich Informatik . Advanced C#: Variable Number of Parameters . live . https://web.archive.org/web/20110920080842/http://ssw.jku.at/Teaching/Lectures/CSharp/Tutorial/Part2.pdf . 2011-09-20.
  34. Book: Eckel . Bruce . Thinking in Java . 2006 . Prentice Hall . Upper Saddle River, NJ . 0-13-187248-6 . 347–348 . 4th.
  35. Web site: Gunnerson . Eric . C# and exception specifications . https://web.archive.org/web/20060101083304/http://discuss.develop.com/archives/wa.exe?A2=ind0011A&L=DOTNET&P=R32820 . 9 November 2000. 1 January 2006. dead.
  36. Web site: The Trouble with Checked Exceptions: A Conversation with Anders Hejlsberg, Part II . Bill Venners . . August 18, 2003 . 4 January 2022.