An Introduction to Python Exceptions for C Programmers Martin Pool == Abstract: Exception handling is one of the most useful features of Python, providing a clear and friendly way to handle abnormal program conditions. However, exceptions often causes trouble to programmers coming to Python from languages such as C that do not have them. Confusion, or lack of confidence with exceptions can severely impact program quality and maintainability. This paper attempts to explain exceptions in terms of good error-handling practice in C to make the derivation and common patterns clear. It presents and justifies a number of guidelines for using exceptions well. == Introduction and Generalities We can think of programs as sometimes encountering "normal, business-as-usual" situations and sometimes "exceptional or error" situations. I don't think a strict technical distinction can be made between the two; it is mostly in the mind of the programmer what is normal, and what is exceptional, and indeed some situations may be more unexpected than others. Part of the idea of an "exceptional condition" is that it causes an interruption to the normal flow of the program: something has occurred that means business as usual cannot continue, at least for the moment. The program may be able to do some recovery work and then continue, or it may need to stop the current operation and go back to a higher level, or it may not be able to continue at all. Various exceptional situations which may occur include: - invalid user input - invalid data discovered in a file or network stream - an internal inconsistency in the program - a resource problem, such as running out of memory or disk space - a hardware problem, such as a disk failure or loss of network connectivity This is not an exhaustive list, and the problems can be divided up in various different ways. Some of them, such as a typo in input, are perhaps more "expected" than others, such as a CPU failure, but most are in a sense "exceptional". I will prefer the term "exceptional case" in this document to "error" to make it clear that they're not the same as "program bugs", "programmer errors", or "user errors". A program bug may cause an exceptional case to arise, or it may be considered a bug in a program that it does handle a particular situation more gracefully, so errors and exceptional cases are related. But the two ideas are nevertheless distinct. One of the requirements that might be specified for a program, implicitly or explicitly, is what situations it is supposed to handle, and how. For example, it is generally considered reasonable that when a user types something invalid into a program it should not crash, but rather give some kind of explanatory or even helpful message. On the other hand, only a few highly-available programs are expected to gracefully recover from a motherboard failure. As a kind of consequence of Sod's Law ("if anything can go wrong, it will, and in the worst possible way"), there are typically many more ways for an operation to fail than to succeed. Consider the Linux open(2) call, for example: there is one success case (the file is opened), but at least 17 documented error returns, and the possibility of others. At every stage of the program, something might go wrong; to achieve the program's requirement all these exceptional cases must be handled appropriately. A great difficulty in making sure that a program handles exceptional cases properly is that they are often not tested: filesystems are normally not (quite!) full, users usually at least try to do the right thing, and printers are not normally on fire. Some errors can be very hard to reproduce in laboratory conditions without great effort in design-for-testability or infrastructure, which is out of the scope of this paper. If these arguments are not sufficient, consider that exceptional-condition handling is a persistent cause of security problems. I would venture that over half the security holes discovered in released software relate to improper handling of invalid input or other exception cases: rather than rejecting the input, the program instead allows some kind of security breach. In security-critical programs -- anything that spans a privilege boundary -- the number of possible error conditions, and the difficulty of exhaustive testing conspire against us. Enough preliminaries, let's have some code! == Error handling in C The normal method for a function to indicate success or failure in C is through its return value, possibly in combination with errno or some other indicator value. Typically 0 is used to indicate success, and either -1 or a positive integer to indicate an error. For functions that return a pointer, the pointer will often be NULL for error. *PROBLEM 1* We can already see one problem with the C way of doing things: methods for indicating errors vary widely between different codebases, and sometimes within a codebase. Naive C code that does not handle errors might look like this: void foo(void) { FILE *fp; int c; fp = fopen("myfile", "r"); fscanf(fp, "%d", &c); } If myfile exists and can be read, all is well. However, there are many exceptional conditions that can shatter our sweet dream: - we don't have permission to read it - the kernel or the application doesn't have enough memory to open another file - there is no such file - an IO error occurs while trying to read the file All of these will cause fopen() to return NULL and set errno. In this program, the NULL is not detected, and so we will probably crash in fscanf with a segmentation violation, and no explanation. Sinking without trace when a file does not exist is hardly very friendly to the user.